import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import { createRequire } from 'node:module' import { test } from 'node:test' import { capturePageContext } from '../extension/page-context.js' const { JSDOM } = createRequire(import.meta.url)('jsdom') const eventHub = () => ({ listeners: [], addListener(callback) { this.listeners.push(callback) }, emit(value) { this.listeners.forEach(callback => callback(value)) } }) const tick = () => new Promise(resolve => setImmediate(resolve)) async function settle(condition) { for (let count = 0; count < 100; count++) { if (condition()) return; await tick() } assert.ok(condition(), 'UI did not settle') } async function panel(t, respond) { const html = await readFile(new URL('../extension/panel.html', import.meta.url), 'utf8') const dom = new JSDOM(html, { url: 'https://extension-fixture.test/panel.html' }) const calls = [] const port = { onMessage: eventHub(), onDisconnect: eventHub(), disconnect() {}, postMessage(message) { calls.push(message) queueMicrotask(() => respond(message, frame => port.onMessage.emit(frame), port)) } } Object.assign(globalThis, { window: dom.window, document: dom.window.document, Option: dom.window.Option, chrome: { runtime: { connectNative: () => port }, storage: { local: { get: async () => ({}), set: async () => {} } } } }) await import(`../extension/panel.js?test=${Math.random()}`) t.after(() => { dom.window.close(); for (const key of ['window', 'document', 'Option', 'chrome']) delete globalThis[key] }) const get = id => dom.window.document.getElementById(id) await settle(() => !get('sessions').disabled) return { dom, calls, port, get, async select(id) { get('sessions').value = id get('sessions').dispatchEvent(new dom.window.Event('change')) await settle(() => !get('sessions').disabled) } } } function fixtureResponse(message, emit) { const result = message.action === 'status' ? { profile: 'default' } : message.action === 'sessions.list' ? { profile: 'default', sessions: [{ id: 'one', title: 'Birinci iş' }, { id: 'two', title: 'İkinci iş' }] } : message.action === 'session.open' ? { messages: [{ role: 'user', text: ' önceden yazıldı' }], model: 'IDE modeli' } : { messages: [] } emit({ id: message.id, result }) } test('panel safely renders real history and puts the submitted user before early streaming tokens', async t => { const p = await panel(t, (message, emit) => { if (message.action !== 'message.send') return fixtureResponse(message, emit) emit({ event: 'message.start', sessionId: 'one' }) emit({ event: 'message.delta', sessionId: 'one', text: 'İlk yanıt' }) emit({ id: message.id, result: { accepted: true } }) }) await p.select('one') assert.equal(p.get('transcript').querySelector('img'), null) assert.ok(p.get('transcript').textContent.includes(' p.get('message').value === '') const messages = [...p.get('transcript').querySelectorAll('.text')].map(node => node.textContent) assert.deepEqual(messages.slice(-2), ['Buradan devam edelim', 'İlk yanıt']) assert.equal(p.calls.filter(call => call.action === 'message.send').length, 1) assert.equal(p.calls.find(call => call.action === 'message.send').params.sessionId, 'one') }) test('task switching drops old events; disconnect keeps draft and does not retry a mutation', async t => { const p = await panel(t, (message, emit, port) => { if (message.action === 'message.send') { port.onDisconnect.emit(); return } fixtureResponse(message, emit) }) await p.select('one') await p.select('two') p.port.onMessage.emit({ event: 'message.delta', sessionId: 'one', text: 'YANLIŞ KONUŞMA' }) assert.ok(!p.get('transcript').textContent.includes('YANLIŞ')) p.get('message').value = 'Kaybolmasın' p.get('composer').dispatchEvent(new p.dom.window.Event('submit', { cancelable: true })) await settle(() => p.get('notice').textContent.includes('Taslağınız korundu')) assert.equal(p.get('message').value, 'Kaybolmasın') assert.equal(p.calls.filter(call => call.action === 'message.send').length, 1) assert.equal(p.get('send').disabled, true) }) test('events arriving while history opens are not silently lost', async t => { const p = await panel(t, (message, emit) => { if (message.action === 'session.open') { emit({ event: 'message.start', sessionId: message.params.sessionId }) emit({ event: 'message.delta', sessionId: message.params.sessionId, text: 'Açılırken gelen yanıt' }) } fixtureResponse(message, emit) }) await p.select('one') assert.ok(p.get('transcript').textContent.includes('Açılırken gelen yanıt')) }) test('explicit page capture excludes form values and strips URL credentials in query and fragment', t => { const dom = new JSDOM('

Görünür içerik

', { url: 'https://example.test/page?token=secret#private' }) Object.assign(globalThis, { window: dom.window, document: dom.window.document, location: dom.window.location, NodeFilter: dom.window.NodeFilter, getComputedStyle: dom.window.getComputedStyle }) dom.window.HTMLElement.prototype.getClientRects = () => [{ width: 100 }] t.after(() => { dom.window.close(); for (const key of ['window', 'document', 'location', 'NodeFilter', 'getComputedStyle']) delete globalThis[key] }) const context = capturePageContext() assert.equal(context.text, 'Görünür içerik') assert.equal(context.url, 'https://example.test/page') assert.equal(context.selection, false) })