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