Files
search-with-chatgpt-powered…/test/dialog.test.js
T

57 lines
4.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { DEFAULT_SETTINGS } from '../lib/defaults.js';
const { createDialogController } = await import('../dialog/controller.js').catch(()=>({}));
const { showActionDialog } = await import('../background/dialogs.js').catch(()=>({}));
const payload = source=>({request:{actionId:'translate',source,settings:{...DEFAULT_SETTINGS}},preparation:{kind:'language'}});
function fixture(data=payload('hello')) {
const opened=[];let closed=0;let state;
const controller=createDialogController({payload:data,open:async(url)=>opened.push(url),render:s=>{state=s;},close:()=>closed++});
return {controller,opened,get state(){return state;},get closed(){return closed;}};
}
test('dialog validates language, cancel never launches, source snapshots are independent',async()=>{
assert.equal(typeof createDialogController,'function');
const a=fixture(),b=fixture(payload('second'));
await a.controller.submit(' ');assert.equal(a.opened.length,0);assert.match(a.state.message,/language/i);
a.controller.cancel();await a.controller.submit('Spanish');assert.equal(a.opened.length,0);assert.equal(a.closed,1);
await b.controller.submit('French');assert.equal(b.opened.length,1);assert.ok(new URL(b.opened[0]).searchParams.get('q').includes('second'));assert.equal(b.closed,1);
});
test('long translation requires a separate explicit shortening confirmation',async()=>{
assert.equal(typeof createDialogController,'function');
const f=fixture(payload('x'.repeat(30000)));
await f.controller.submit('Spanish');assert.equal(f.opened.length,0);assert.equal(f.state.kind,'shortened');
await f.controller.submit();assert.equal(f.opened.length,1);assert.ok(f.opened[0].length<=8000);
});
test('pending launch blocks double submit and failure permits retry',async()=>{
assert.equal(typeof createDialogController,'function');
let count=0,release,state,closed=0;
const controller=createDialogController({payload:payload('hello'),render:s=>state=s,close:()=>closed++,open:()=>{count++;return new Promise((resolve,reject)=>release={resolve,reject});}});
const pending=controller.submit('Spanish');await controller.submit('Spanish');assert.equal(count,1);
release.reject(Error('fail'));await pending;assert.equal(closed,0);assert.match(state.message,/try again/i);
const retry=controller.submit();release.resolve();await retry;assert.equal(count,2);assert.equal(closed,1);
});
function event() { const listeners=new Set();return {addListener:f=>listeners.add(f),removeListener:f=>listeners.delete(f),emit:(...args)=>{for(const f of [...listeners])f(...args);},get size(){return listeners.size;}}; }
function browserFixture() {
let id=0;const tabs=[];
return {tabs,runtime:{id:'extension',getURL:p=>'moz-extension://test/'+p,onConnect:event()},windows:{create:async({url})=>{const tab={id:++id};tabs.push({url,...tab});return {tabs:[tab]};}},tabsApi:{onRemoved:event()}};
}
function connect(b,tab,index=0) {
const messages=[];
const port={name:'action-dialog:'+new URL(tab.url).searchParams.get('request'),sender:{id:'extension',url:tab.url,tab:{id:tab.id+index}},onMessage:event(),onDisconnect:event(),postMessage:v=>messages.push(v),disconnect(){this.onDisconnect.emit();}};
b.runtime.onConnect.emit(port);return {port,messages};
}
test('handoff scopes data to matching dialog tab and releases background after acknowledgment',async()=>{
assert.equal(typeof showActionDialog,'function');
const b=browserFixture();b.tabs=b.tabsApi;
// Capture opened tabs outside the browser API object.
const opened=[];let id=0;b.windows.create=async({url})=>{const tab={id:++id,url};opened.push(tab);return {tabs:[tab]};};
const first=showActionDialog(b,payload('first')),second=showActionDialog(b,payload('second'));
await new Promise(r=>setImmediate(r));
const bad=connect(b,opened[0],100);await new Promise(r=>setImmediate(r));assert.equal(bad.messages.length,0);
const a=connect(b,opened[0]),c=connect(b,opened[1]);await new Promise(r=>setImmediate(r));
assert.equal(a.messages[0].payload.request.source,'first');assert.equal(c.messages[0].payload.request.source,'second');
a.port.onMessage.emit({type:'received'});c.port.onMessage.emit({type:'received'});await Promise.all([first,second]);
assert.equal(b.runtime.onConnect.size,0);assert.equal(b.tabs.onRemoved.size,0);
assert.equal(a.messages[0].payload.request.source,'first');
});