104 lines
5.7 KiB
JavaScript
104 lines
5.7 KiB
JavaScript
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: '<img src=x onerror=alert(1)> ö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('<img'))
|
||
p.get('message').value = 'Buradan devam edelim'
|
||
p.get('composer').dispatchEvent(new p.dom.window.Event('submit', { cancelable: true }))
|
||
await settle(() => 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('<body><p>Görünür içerik</p><input value="secret"><textarea>private draft</textarea><div hidden>hidden secret</div><script>private code</script></body>',
|
||
{ 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)
|
||
})
|