Files
stack/packages/webui/tests/w15-conversations-browser.test.mjs

109 lines
8.4 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { setup, P, T, S, S2 } from './helpers/w15-conversations.mjs';
test('canonical project and session routes preserve identity, drafts and optional mounts', async t=>{
const {b,render,shot,wait}=await setup(t);
await b.evaluate(`window.MosaicRequests={mount(){},projectSummary(o){window.__mounts.push(['summary',o.requestIds,o.project]);o.container.textContent='Fixture request summary';}}`);
await render(`/projects/${P}`);
await wait('document.querySelector("[data-project-content]")?.textContent.includes("Showing requests from recent messages")');
assert.match(await b.evaluate('document.querySelector(".chat-area").textContent'),/Showing requests from recent messages/);
for(const w of [1440,390]) await shot('project-overview-unloaded',w);
await render(`/projects/${P}/settings`);
assert.deepEqual(await b.evaluate('window.__mounts.at(-1)'),['remote',P,'repo-key']);
await render(`/projects/threads/${T}/sessions/${S}`);
assert.equal(await b.evaluate('document.querySelector(".chat-conversation").dataset.session'),S);
assert.deepEqual(await b.evaluate('window.__contexts.at(-1)'),{projectId:P,repositoryKey:'repo-key',thread:T,session:S});
assert.ok(await b.evaluate(`!!document.querySelector('.project-tabs a[href$="/files"]') && !document.querySelector('[data-chat-files]')`));
assert.equal(await b.evaluate(`!!document.querySelector('.project-chat [data-chat-summary]') || window.__mounts.some(x=>x[0]==='summary')`),false);
await b.evaluate('document.querySelector("#chat-body").value="first draft";document.querySelector("#chat-body").dispatchEvent(new Event("input",{bubbles:true}))');
for(const w of [1440,390]) await shot('conversation-progress',w);
await b.evaluate('document.querySelector(".chat-details").open=true');
for(const w of [1440,390]) await shot('conversation-details',w);
await render(`/projects/threads/${T}/sessions/${S2}`);
assert.equal(await b.evaluate('document.querySelector("#chat-body").value'),'');
await b.evaluate('document.querySelector("#chat-body").value="second draft";document.querySelector("#chat-body").dispatchEvent(new Event("input",{bubbles:true}))');
for(const w of [1440,390]) await shot('two-named-sessions',w);
await render(`/projects/threads/${T}/sessions/${S}`);
assert.equal(await b.evaluate('document.querySelector("#chat-body").value'),'first draft');
await render(`/projects/${P}`);
await wait('document.querySelector("[data-project-content]")?.textContent.includes("Showing requests from recent messages")');
assert.match(await b.evaluate('document.querySelector("[data-project-content]").textContent'),/Showing requests from recent messages/);
for(const w of [1440,390]) await shot('project-overview-known',w);
await b.evaluate('delete window.MosaicRequests'); await render(`/projects/${P}`);
await wait('document.querySelector("[data-project-content]")?.textContent.includes("Not available yet")');
await render(`/projects/threads/${T}/sessions/${S}`);
await b.evaluate('window.__failRead=true');await render(`/projects/threads/${T}/sessions/${S}`);
assert.equal(await b.evaluate('document.querySelector("#chat-body").value'),'first draft');
for(const w of [1440,390]) await shot('read-failed-draft-kept',w);
assert.equal(await b.evaluate('window.__writes.length'),0);
});
test('room picker serializes selected instance and retains unresolved drafts', async t=>{
const {b,render,shot}=await setup(t);await render(`/projects/rooms/${P}`);
assert.match(await b.evaluate('document.querySelector(".chat-room").textContent'),/Each reply stays in that member/);
for(const w of [1440,390]) await shot('room-picker',w);
await b.evaluate('document.querySelector("[data-room-member=bob]").click()');
assert.match(await b.evaluate('document.querySelector("#chat-room-body").value'),/^@alice /);
assert.match(await b.evaluate('document.querySelector("#chat-room-tags").textContent'),/To:.*alice/);
await b.evaluate('document.querySelector("#chat-room-body").value="@alice Please ask @bob too";document.querySelector("#chat-room-body").dispatchEvent(new Event("input",{bubbles:true}))');
assert.match(await b.evaluate('document.querySelector("#chat-room-tags").textContent'),/To:.*alice/);
assert.equal(await b.evaluate('document.querySelector("[data-room-offer=bob]") !== null'),true);
await b.evaluate('document.querySelector("[data-room-offer=bob]").click()');
assert.match(await b.evaluate('document.querySelector("#chat-room-tags").textContent'),/alice.*bob/);
await b.evaluate('document.querySelector("#chat-room-body").value="@unknown message";document.querySelector("#chat-room-body").dispatchEvent(new Event("input",{bubbles:true}));document.querySelector("[data-room-member=bob]").click()');
assert.equal(await b.evaluate('document.querySelector("#chat-room-body").value'),'@unknown message');
assert.match(await b.evaluate('document.querySelector("#chat-room-receipt").textContent'),/Repair/);
for(const w of [1440,390]) await shot('room-invalid',w);
assert.equal(await b.evaluate('window.__writes.length'),0);
});
test('granted routes do not expose owner room or remote controls', async t=>{
const {b}=await setup(t);
await b.evaluate(`MosaicChat.render({path:'/projects/rooms/${P}',container:document.getElementById('bus-view'),canSend:true,user:true})`);
assert.match(await b.evaluate('document.querySelector(".chat-area").textContent'),/Rooms require the business owner/);
assert.equal(await b.evaluate('!!document.querySelector("#chat-room-send-form")'),false);
await b.evaluate(`MosaicChat.render({path:'/projects/${P}/settings',container:document.getElementById('bus-view'),canSend:true,user:true})`);
assert.equal(await b.evaluate('window.__mounts.some(x=>x[0]==="remote")'),false);
assert.equal(await b.evaluate('window.__writes.length'),0);
});
test('pending clear, room receipt and shared history retain truthful state', async t=>{
const {b,render,wait,shot}=await setup(t);
await render(`/projects/threads/${T}/sessions/${S}`);
await b.evaluate('document.querySelector(".chat-details").open=true;window.__holdClear=true;document.querySelector("#chat-clear").click()');
await wait('window.__writes.some(w=>w.path.endsWith("/clear"))');
assert.match(await b.evaluate('document.querySelector(".chat-log").textContent'),/fixture message/);
assert.match(await b.evaluate('document.querySelector("#chat-clear-receipt").textContent'),/requesting clear/);
for(const w of [1440,390]) await shot('clear-pending',w);
await b.evaluate('window.__releaseClear()');
await render(`/projects/rooms/${P}`);
await b.evaluate('window.__roomAccept=true;document.querySelector("[data-room-member=bob]").click();const e=document.querySelector("#chat-room-body");e.value="@alice hello";e.dispatchEvent(new Event("input",{bubbles:true}));document.querySelector("#chat-room-send-form").dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}))');
await wait('document.querySelector("#chat-room-receipt").textContent.includes("Accepted")');
for(const w of [1440,390]) await shot('room-sent',w);
await b.evaluate('window.__shared=true');
await render(`/projects/threads/${T}`);
assert.match(await b.evaluate('document.querySelector(".chat-area").textContent'),/Read-only thread/);
assert.equal(await b.evaluate('!!document.querySelector("#chat-send-form")'),false);
for(const w of [1440,390]) await shot('shared-readonly',w);
});
test('room recipient controls accept keyboard and pointer at both widths', async t=>{
const {b,render}=await setup(t);
for(const width of [1440,390]) {
await b.viewport(width,1000); await render(`/projects/rooms/${P}`);
await b.evaluate('document.querySelector("[data-room-chip=bob]").focus()');
await b.key(' ');
assert.match(await b.evaluate('document.querySelector("#chat-room-tags").textContent'),/alice/);
assert.equal(await b.evaluate('document.activeElement.dataset.roomChip'),'bob');
await b.key('Tab'); await b.key('Tab','Tab',8); await b.key('Escape');
assert.notEqual(await b.evaluate('document.activeElement.tagName'),'BODY');
const box=await b.evaluate('(()=>{const r=document.querySelector("[data-room-chip=bob]").getBoundingClientRect();return {x:r.x+r.width/2,y:r.y+r.height/2}})()');
await b.call('Input.dispatchMouseEvent',{type:'mousePressed',x:box.x,y:box.y,button:'left',clickCount:1});
await b.call('Input.dispatchMouseEvent',{type:'mouseReleased',x:box.x,y:box.y,button:'left',clickCount:1});
assert.match(await b.evaluate('document.querySelector("#chat-room-tags").textContent'),/bob/);
}
});