60 lines
5.8 KiB
JavaScript
60 lines
5.8 KiB
JavaScript
import { writeFile, mkdir } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
import { fixture } from '../fixture.mjs';
|
|
import { browser } from '../browser.mjs';
|
|
export const P = '10000000-0000-4000-8000-000000000001';
|
|
export const T = '20000000-0000-4000-8000-000000000001';
|
|
export const S = '30000000-0000-4000-8000-000000000001';
|
|
export const S2 = '30000000-0000-4000-8000-000000000002';
|
|
const stub = `(() => {
|
|
const real = fetch.bind(window), P=${JSON.stringify(P)}, T=${JSON.stringify(T)}, S=${JSON.stringify(S)}, S2=${JSON.stringify(S2)};
|
|
const json=(v,status=200)=>new Response(JSON.stringify(v),{status,headers:{'content-type':'application/json'}});
|
|
window.__writes=[]; window.__unexpected=[];window.__roomRows=[];window.confirm=()=>true; window.__mounts=[]; window.__contexts=[];
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.MosaicFiles={mount:o=>{window.__mounts.push(['files',o.requestIds]);o.container.textContent='Fixture files';}};
|
|
window.MosaicProjectRemote={mount:o=>{window.__mounts.push(['remote',o.projectId,o.repositoryKey]);o.container.textContent='Fixture remote';}};
|
|
});
|
|
document.addEventListener('mosaic-project-context',e=>window.__contexts.push(e.detail));
|
|
window.fetch=async (url,init={})=>{
|
|
const path=String(url), method=init.method||'GET'; if(!path.startsWith('/api/')) return real(url,init);
|
|
if(method!=='GET'){window.__writes.push({path,body:JSON.parse(init.body||'{}')}); if(window.__holdClear && path.endsWith('/clear'))return new Promise(r=>window.__releaseClear=()=>r(json({error:'fixture-refusal',sent:false},409))); if(window.__roomAccept && path.includes('/rooms/') && path.endsWith('/messages')){const id='50000000-0000-4000-8000-000000000001';window.__roomRows.push({id,ids:[id],from_role:'human',to:['alice'],body:JSON.parse(init.body).body,at:'2026-11-05T12:00:00Z'});return json({ids:[id],to:['alice']});} return json({error:'fixture-refusal',sent:false},409);}
|
|
if(path==='/api/config')return json({send:true,chatGrants:[]});
|
|
if(path.startsWith('/api/bus/'))return json({rows:[],unread:0,at:'2026-11-05T12:00:00Z'});
|
|
if(path==='/api/business')return json({projects:['repo-key']});
|
|
if(path.startsWith('/api/library/status'))return json({error:'not-configured'},404);
|
|
if(path==='/api/chat/projects')return json({rows:[{id:P,name:'Active project',repo:'repo-key'},{id:'10000000-0000-4000-8000-000000000002',name:'Empty project',repo:null},{id:'10000000-0000-4000-8000-000000000003',name:'Archived project',repo:null,archived:true}]});
|
|
if(path==='/api/chat/instances')return json({rows:[{instance:'alice',name:'Alice'},{instance:'bob',name:'Bob'}]});
|
|
if(path.includes('/shares'))return json({rows:[]});
|
|
if(path.endsWith('/sessions'))return json({rows:[{id:S,name:'Planning',default:true,state:'active',revision:1},{id:S2,name:'Review',state:'active',revision:1}]});
|
|
if(path.includes('/rooms/') && path.includes('/messages'))return json({rows:window.__roomRows,after:null,hasMore:false});
|
|
if(path.includes('/messages')) { if(window.__failRead)return json({error:'offline'},503); return json({rows:[{id:'40000000-0000-4000-8000-000000000001',from_role:'human',body:'A fixture message',request:'request-1',at:'2026-11-05T12:00:00Z'}],after:null,hasMore:false}); }
|
|
if(path.startsWith('/api/chat/threads'))return json({rows:path.includes('shared=true')?(window.__shared?[{id:T,project:P,name:'Planning conversation',instance:'alice',owner:'Owner'}]:[]):window.__shared?[]:[{id:T,project:P,name:'Planning conversation',instance:'alice'},{id:'20000000-0000-4000-8000-000000000002',project:P,name:'alice',instance:'alice'},{id:'20000000-0000-4000-8000-000000000003',project:P,name:'bob',instance:'bob'}]});
|
|
window.__unexpected.push(path);return json({error:'unexpected-fixture-api'},403);
|
|
};
|
|
})();`;
|
|
export async function setup(t) {
|
|
const f=await fixture(); t.after(()=>f.close());
|
|
const b=await browser({machine:false}); t.after(()=>b.close());
|
|
const errors=[];
|
|
b.on('Runtime.exceptionThrown', p=>errors.push(p.exceptionDetails.text));
|
|
await b.call('Fetch.enable',{patterns:[{urlPattern:'*'}]});
|
|
b.on('Fetch.requestPaused', p=>{void b.call(p.request.url.startsWith(f.base+'/')?'Fetch.continueRequest':'Fetch.failRequest',p.request.url.startsWith(f.base+'/')?{requestId:p.requestId}:{requestId:p.requestId,errorReason:'BlockedByClient'}).catch(()=>{});});
|
|
await b.call('Page.addScriptToEvaluateOnNewDocument',{source:stub});
|
|
await b.viewport(1440,1000); await b.navigate(f.base+'/#/chat');
|
|
const wait=expr=>b.evaluate(`(async()=>{for(let i=0;i<100;i++){if(${expr})return true;await new Promise(r=>setTimeout(r,30));}throw new Error(${JSON.stringify(expr)});})()`);
|
|
await wait('document.querySelector(".chat-projects")');
|
|
// The shell owns the canonical route dispatcher and arrives at integration. Exercise the actual
|
|
// conversation renderer with canonical paths without changing the unowned baseline shell.
|
|
const render=async path=>{await b.evaluate(`MosaicChat.render({path:${JSON.stringify(path)},container:document.getElementById('bus-view'),canSend:true,navigated:false})`);};
|
|
const shot=async (name,width)=>{
|
|
if(!process.env.MOSAIC_UX_SHOTS)return;
|
|
await mkdir(process.env.MOSAIC_UX_SHOTS,{recursive:true});await b.viewport(width,1000);await b.evaluate('document.fonts.ready');
|
|
if (await b.evaluate('document.documentElement.scrollWidth > document.documentElement.clientWidth')) throw new Error('Document overflows at '+width);
|
|
const file=join(process.env.MOSAIC_UX_SHOTS,`conversations-${name}-${width}.png`);await b.screenshot(file);
|
|
const {cssContentSize:s}=await b.call('Page.getLayoutMetrics');
|
|
const {data}=await b.call('Page.captureScreenshot',{format:'png',captureBeyondViewport:true,clip:{x:0,y:0,width:Math.ceil(s.width),height:Math.ceil(s.height),scale:1}});
|
|
await writeFile(file.replace('.png','-full.png'),Buffer.from(data,'base64'));
|
|
};
|
|
return {f,b,wait,render,shot,errors};
|
|
}
|