284 lines
24 KiB
JavaScript
284 lines
24 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { startServer } from '../src/serve.mjs';
|
|
import { createDevOwner } from '../src/dev-owner.mjs';
|
|
import { createOrgAdmin } from '../src/org-admin.mjs';
|
|
import { browser } from './browser.mjs';
|
|
import { close } from './fixture.mjs';
|
|
|
|
test('organization header and footer never display null board values', async t => {
|
|
const organizations = { list: async () => [{ id: 'alpha', label: 'alpha', busHealthy: true }], resolve: async () => ({ reads: {}, bus: {} }) };
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, log: () => {} });
|
|
t.after(() => close(server));
|
|
const b = await browser({ machine: false }); t.after(() => b.close());
|
|
for (const [width, height] of [[1400, 900], [390, 844]]) {
|
|
await b.viewport(width, height);
|
|
await b.navigate(`http://127.0.0.1:${server.address().port}/#/orgs/alpha/`);
|
|
await b.evaluate(`(async()=>{for(let i=0;i<150;i++){if(/Board:|No board/.test(document.querySelector('#board-url').textContent))return;await new Promise(r=>setTimeout(r,30))}throw Error('footer not loaded')})()`);
|
|
const text = await b.evaluate(`document.querySelector('header').textContent + ' ' + document.querySelector('footer').textContent`);
|
|
assert.doesNotMatch(text, /\b(?:null|undefined)\b/i);
|
|
}
|
|
});
|
|
|
|
test('an invalid organization is explained instead of appearing as an empty registry', async t => {
|
|
const organizations = { list: async () => [{ id: 'alpha', label: 'alpha', invalid: true, reason: 'organization document and registry disagree' }], resolve: async () => { throw Error('unavailable'); } };
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, log: () => {} }); t.after(() => close(server));
|
|
const b = await browser({ machine: false }); t.after(() => b.close());
|
|
for (const [width, height] of [[1400, 900], [390, 844]]) {
|
|
await b.viewport(width, height);
|
|
await b.navigate(`http://127.0.0.1:${server.address().port}/#/`);
|
|
await b.evaluate(`(async()=>{for(let i=0;i<150;i++){if(document.querySelector('#held [role=alert]'))return;await new Promise(r=>setTimeout(r,30))}throw Error('admission error missing')})()`);
|
|
const body = await b.evaluate('document.body.textContent');
|
|
assert.match(body, /alpha.*invalid.*document and registry disagree/is);
|
|
assert.doesNotMatch(body, /No organizations yet/);
|
|
}
|
|
});
|
|
|
|
test('fresh dashboard points to organization setup while real list errors remain errors', async t => {
|
|
for (let broken of [false, true]) {
|
|
const organizations = { list: async () => { if (broken) throw Error('registry down'); return []; }, resolve: async () => null };
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, board: 'http://127.0.0.1:9', log: () => {} });
|
|
const b = await browser({ machine: false });
|
|
try {
|
|
await b.navigate(`http://127.0.0.1:${server.address().port}/#/`);
|
|
await b.evaluate(`(async()=>{for(let i=0;i<150;i++){if(document.querySelector('#held .banner, #held a[href="#/settings/organizations"]'))return;await new Promise(r=>setTimeout(r,30))}throw Error('held roles did not settle')})()`);
|
|
const held = await b.evaluate(`({ text: document.querySelector('#held').textContent, error: !!document.querySelector('#held .banner.err, #held .banner.ref'), link: !!document.querySelector('#held a[href="#/settings/organizations"]') })`);
|
|
if (broken) {
|
|
assert.equal(held.error, true, 'a real registry error remains visible'); assert.match(held.text, /^Organization registry could not be readRead again$/, 'the fixed message, once, with no server error text');
|
|
broken = false; await b.evaluate(`document.querySelector('#held [data-retry]').click()`);
|
|
await b.evaluate(`(async()=>{for(let i=0;i<150;i++){if(document.querySelector('#held a[href="#/settings/organizations"]'))return;await new Promise(r=>setTimeout(r,30))}throw Error('registry retry did not recover')})()`);
|
|
}
|
|
else { assert.match(held.text, /No organizations yet/); assert.equal(held.link, true); assert.equal(held.error, false); }
|
|
} finally { await b.close(); await close(server); }
|
|
}
|
|
});
|
|
|
|
test('creating another organization opens its setup, receipt, header and theme', async t => {
|
|
const makeDoc = id => ({ businessVersion: 1, id, human: 'local-dev-owner', arbiters: { delivery: 'chief', technical: 'chief' }, roles: { chief: { definition: 'chief-of-staff' } }, projects: { work: {} }, appearance: { theme: id === 'beta' ? 'cobalt' : 'harbor', mode: id === 'beta' ? 'light' : 'dark' } });
|
|
const docs = new Map([['alpha', makeDoc('alpha')]]), records = new Map();
|
|
const missing = () => { throw Object.assign(Error('not set up'), { code: 'org-unavailable' }); };
|
|
const service = {
|
|
orgPresets: async () => [{ name: 'fixture', label: 'Test model', description: 'Canned replies', available: { ok: true }, thinking: { levels: ['off'], default: 'off' } }],
|
|
templateRender: async id => makeDoc(id), validateOrg: async () => ({ ok: true, errors: [] }),
|
|
createOrg: async (_ctx, id, doc) => { docs.set(id, doc); const receipt = { op: 'org.create', org: id, notifications: 'none' }; records.set(id, receipt); return receipt; },
|
|
showOrg: async (ctx, id) => ({ id, doc: docs.get(id), revision: 1, root: `/data/${id}`, principal: ctx.principal }),
|
|
notificationShow: async (_ctx, id) => records.get(id), queueShow: missing, projectRepoShow: missing,
|
|
};
|
|
const registry = { listOrgs: async () => [...docs.keys()].map(id => ({ id, state: 'active' })) };
|
|
const organizations = {
|
|
list: async () => [...docs.keys()].map(id => ({ id, label: id, busHealthy: true })),
|
|
resolve: async id => docs.has(id) ? { busHealthy: true, send: async () => ({}), reads: { business: async () => ({ business: { id, projects: ['work'], appearance: docs.get(id).appearance } }), busStatus: async () => ({ live: true }) }, bus: {} } : null,
|
|
};
|
|
const orgAdmin = createOrgAdmin({ service, registry, context: async () => ({ configDir: '/cfg', dataRoot: '/data', instance: 'test' }) });
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, orgAdmin, send: async () => ({}), board: 'http://127.0.0.1:9', log: () => {} }); t.after(() => close(server));
|
|
const b = await browser({ machine: false }); t.after(() => b.close());
|
|
const wait = expression => b.evaluate(`(async()=>{for(let i=0;i<150;i++){if(${expression})return;await new Promise(r=>setTimeout(r,30))}throw Error(${JSON.stringify(expression)})})()`);
|
|
await b.navigate(`http://127.0.0.1:${server.address().port}/#/orgs/alpha/settings/organizations`);
|
|
await wait('document.querySelector("#org-new-form [data-write]") && !document.querySelector("#org-new-form [data-write]").disabled');
|
|
const shown = selector => b.evaluate(`(()=>{const e=document.querySelector(${JSON.stringify(selector)});return !!e && getComputedStyle(e).display !== 'none' && e.getClientRects().length > 0})()`);
|
|
assert.equal(await b.evaluate('!!document.querySelector(".settings-readonly")'), false, 'org setup is not shown as read only');
|
|
assert.equal(await shown('#org-new-form [data-write]'), true, 'New organization is visible, not only clickable');
|
|
await b.evaluate(`(()=>{const e=document.querySelector('#org-new-id');e.value='beta';e.dispatchEvent(new Event('input',{bubbles:true}));document.querySelector('#org-new-form [data-write]').click()})()`);
|
|
await wait('document.querySelector("#org-advanced-open") && !document.querySelector("#org-paths").hidden');
|
|
await b.evaluate(`document.querySelector('#org-advanced-open').click()`);
|
|
await wait('document.querySelector("#org-editor") && !document.querySelector("#org-editor").hidden');
|
|
assert.equal(await shown('#org-save'), true, 'Save organization is visible, not only clickable');
|
|
await b.evaluate(`document.querySelector('#org-save').click()`);
|
|
let switched = false;
|
|
for (let i = 0; i < 150 && !switched; i++) {
|
|
try { switched = await b.evaluate('window.MosaicOrg?.id === "beta" && document.querySelector("#org-setup") && !document.querySelector("#org-setup").hidden'); } catch { /* the organization switch reloads the page */ }
|
|
if (!switched) await new Promise(r => setTimeout(r, 30));
|
|
}
|
|
assert.ok(switched, 'new organization setup loaded after the switch');
|
|
await b.evaluate('window.MosaicAppearance.ready');
|
|
assert.match(await b.evaluate('location.hash'), /^#\/orgs\/beta\/settings\/organizations\?org=beta&step=setup/);
|
|
assert.match(await b.evaluate('document.querySelector("header").textContent'), /beta/);
|
|
assert.equal(await b.evaluate('document.documentElement.dataset.palette'), 'cobalt');
|
|
assert.match(await b.evaluate('document.querySelector("#org-receipt").textContent'), /Organization created.*beta/s);
|
|
});
|
|
|
|
test('global organization admin requests reach the real routes with an organization selected', async t => {
|
|
const organizations = { list: async () => [{ id: 'alpha', label: 'Alpha', busHealthy: true }], resolve: async () => ({ reads: {}, bus: {} }) };
|
|
const service = { validateOrg: async () => ({ ok: true, errors: [] }) };
|
|
const registry = { listOrgs: async () => [{ id: 'alpha', state: 'active' }] };
|
|
const orgAdmin = createOrgAdmin({ service, registry, context: async () => ({ configDir: '/cfg', dataRoot: '/data', instance: 'test' }) });
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, orgAdmin, send: async () => ({}), log: () => {} });
|
|
t.after(() => close(server));
|
|
const b = await browser({ machine: false }); t.after(() => b.close());
|
|
await b.navigate(`http://127.0.0.1:${server.address().port}/#/orgs/alpha/`);
|
|
const result = await b.evaluate(`(async () => {
|
|
const list = await fetch('/api/org-admin');
|
|
const check = await fetch('/api/org-validate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: 'alpha', doc: {} }) });
|
|
return { listStatus: list.status, list: await list.json(), checkStatus: check.status, check: await check.json() };
|
|
})()`);
|
|
assert.equal(result.listStatus, 200); assert.deepEqual(result.list.orgs, [{ id: 'alpha', state: 'active' }]);
|
|
assert.equal(result.checkStatus, 200); assert.equal(result.check.ok, false);
|
|
});
|
|
|
|
test('project creation offers repositories from the scoped business read', async t => {
|
|
const organizations = {
|
|
list: async () => [{ id: 'alpha', label: 'Alpha', busHealthy: true }],
|
|
resolve: async () => ({ busHealthy: true, send: async () => ({}), reads: { business: async () => ({ business: { id: 'alpha', projects: ['work'] } }), busStatus: async () => ({ live: true }) },
|
|
bus: { inbox: async () => [], agents: async () => [], messages: async () => ({ messages: [] }), tasks: async () => [] }, chat: { projects: async () => [], threads: async () => [] } }),
|
|
};
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, log: () => {} }); t.after(() => close(server));
|
|
const b = await browser({ machine: false }); t.after(() => b.close());
|
|
await b.navigate(`http://127.0.0.1:${server.address().port}/#/orgs/alpha/projects`);
|
|
await b.evaluate(`(async()=>{for(let i=0;i<150;i++){if(document.querySelector('#chat-repo option[value=work]'))return;await new Promise(r=>setTimeout(r,30))}throw Error('repository choice missing')})()`);
|
|
assert.equal(await b.evaluate(`document.querySelector('#chat-repo option[value=work]').value`), 'work');
|
|
});
|
|
|
|
test('Settings lists organizations and switching changes the route and header', async t => {
|
|
const organizations = {
|
|
list: async () => [{ id: 'alpha', label: 'Alpha', theme: null, busHealthy: true }, { id: 'beta', label: 'Beta', theme: null, busHealthy: false }],
|
|
resolve: async id => ['alpha', 'beta'].includes(id) ? {
|
|
reads: { business: async () => ({ business: { id } }), settings: async () => ({}), busStatus: async () => { await new Promise(r => setTimeout(r, 200)); return { live: id === 'alpha', down: false }; } },
|
|
bus: { inbox: async () => [], agents: async () => [], messages: async () => ({ messages: [] }), tasks: async () => [] },
|
|
} : null,
|
|
};
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, board: 'http://127.0.0.1:9' });
|
|
t.after(() => close(server));
|
|
const b = await browser({ machine: false }); t.after(() => b.close());
|
|
await b.viewport(390, 900);
|
|
await b.navigate(`http://127.0.0.1:${server.address().port}/#/orgs/alpha/settings/business`);
|
|
await b.evaluate(`(async()=>{for(let i=0;i<150;i++){if(document.querySelector('#org-picker'))return;await new Promise(r=>setTimeout(r,30))}throw Error('Organization picker did not appear')})()`);
|
|
assert.match(await b.evaluate(`document.querySelector('header').textContent`), /Alpha/);
|
|
assert.equal(await b.evaluate(`document.querySelector('#settings-page-title').textContent`), 'Organization');
|
|
assert.deepEqual(await b.evaluate(`[...document.querySelector('#org-picker').options].map(o=>o.value)`), ['alpha', 'beta']);
|
|
await b.evaluate(`(()=>{const e=document.querySelector('#org-picker');e.value='beta';e.dispatchEvent(new Event('change',{bubbles:true}))})()`);
|
|
let switched = false;
|
|
for (let i = 0; i < 150 && !switched; i++) {
|
|
try { switched = await b.evaluate(`location.hash.startsWith('#/orgs/beta/') && document.querySelector('header')?.textContent.includes('Beta')`); } catch { /* reload replaced the page */ }
|
|
if (!switched) await new Promise(r => setTimeout(r, 30));
|
|
}
|
|
assert.equal(switched, true, 'switch completed after reload');
|
|
assert.match(await b.evaluate(`document.querySelector('body').textContent`), /Bus unavailable/);
|
|
await new Promise(r => setTimeout(r, 400));
|
|
assert.equal(await b.evaluate(`document.querySelector('#bus-down').hidden`), false, 'offline banner remains visible after the boot-status read');
|
|
assert.match(await b.evaluate(`document.querySelector('#bus-down').textContent`), /Bus unavailable/);
|
|
});
|
|
|
|
test('a chat draft is kept on Stay and discarded on an organization switch', async t => {
|
|
const project = '10000000-0000-4000-8000-000000000001', thread = '20000000-0000-4000-8000-000000000002';
|
|
const organizations = {
|
|
list: async () => [{ id: 'alpha', label: 'Alpha', busHealthy: true }, { id: 'beta', label: 'Beta', busHealthy: true }],
|
|
resolve: async id => ({
|
|
send: async () => ({}),
|
|
reads: { business: async () => ({ business: { id } }), settings: async () => ({}), busStatus: async () => ({ live: true, down: false }) },
|
|
bus: { inbox: async () => [], agents: async () => [], messages: async () => ({ messages: [] }), tasks: async () => [] },
|
|
chat: { projects: async () => [{ id: project, name: 'Project' }], threads: async ({ shared }) => shared ? [] : [{ id: thread, project, name: 'Conversation', instance: 'assistant' }], sessions: async () => [{ id: thread, name: 'default', default: true }], shares: async () => [], messages: async () => [] },
|
|
}),
|
|
};
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, send: async () => ({}), board: 'http://127.0.0.1:9', log: () => {} });
|
|
t.after(() => close(server));
|
|
const b = await browser({ machine: false }); t.after(() => b.close());
|
|
await b.navigate(`http://127.0.0.1:${server.address().port}/#/orgs/alpha/chat/threads/${thread}`);
|
|
const wait = expression => b.evaluate(`(async()=>{for(let i=0;i<150;i++){if(${expression})return;await new Promise(r=>setTimeout(r,30))}throw Error(${JSON.stringify(expression)})})()`);
|
|
await wait('!!document.querySelector("#chat-body")');
|
|
assert.equal(await b.evaluate(`!!document.querySelector('#chat-session-form, #chat-share-form, #chat-clear')`), false, 'unmapped session and sharing controls are unavailable');
|
|
await b.evaluate(`(()=>{const el=document.querySelector('#chat-body');el.value='unsent alpha';el.dispatchEvent(new Event('input',{bubbles:true}))})()`);
|
|
await b.evaluate(`location.hash='#/settings/business'`);
|
|
await wait('!!document.querySelector("#org-picker")');
|
|
await b.evaluate(`(()=>{const el=document.querySelector('#org-picker');el.value='beta';el.dispatchEvent(new Event('change',{bubbles:true}))})()`);
|
|
await wait('!!document.querySelector("dialog[open] [data-stay]")');
|
|
await b.evaluate(`document.querySelector('[data-stay]').click()`);
|
|
assert.match(await b.evaluate('location.hash'), /^#\/orgs\/alpha\//);
|
|
assert.equal(await b.evaluate(`document.querySelector('#org-picker').value`), 'alpha');
|
|
await b.evaluate(`location.hash='#/chat/threads/${thread}'`);
|
|
await wait('!!document.querySelector("#chat-body")');
|
|
assert.equal(await b.evaluate(`document.querySelector('#chat-body').value`), 'unsent alpha');
|
|
await b.evaluate(`location.hash='#/orgs/beta/settings/business'`);
|
|
await wait('!!document.querySelector("dialog[open] [data-discard]")');
|
|
await b.evaluate(`document.querySelector('[data-discard]').click()`).catch(() => {});
|
|
let switched = false;
|
|
for (let i = 0; i < 150 && !switched; i++) {
|
|
try { switched = await b.evaluate(`location.hash.startsWith('#/orgs/beta/') && document.querySelector('header')?.textContent.includes('Beta')`); } catch {}
|
|
if (!switched) await new Promise(r => setTimeout(r, 30));
|
|
}
|
|
assert.equal(switched, true);
|
|
assert.equal(await b.evaluate(`document.querySelector('#chat-body')?.value ?? ''`), '');
|
|
});
|
|
|
|
test('organization switch isolates browser board preferences', async t => {
|
|
const organizations = {
|
|
list: async () => [{ id: 'alpha', label: 'Alpha', busHealthy: true }, { id: 'beta', label: 'Beta', busHealthy: true }],
|
|
resolve: async id => ({ reads: { business: async () => ({ business: { id } }), settings: async () => ({}), busStatus: async () => ({ live: true, down: false }) }, bus: {} }),
|
|
};
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, board: 'http://127.0.0.1:9', log: () => {} });
|
|
t.after(() => close(server));
|
|
const b = await browser({ machine: false }); t.after(() => b.close());
|
|
const base = `http://127.0.0.1:${server.address().port}/`;
|
|
await b.navigate(base + '#/orgs/alpha/');
|
|
await b.evaluate(`document.querySelector('#show-machine').click()`);
|
|
await b.navigate(base + '#/orgs/beta/');
|
|
await b.evaluate(`(async()=>{for(let i=0;i<100;i++){if(window.MosaicOrg?.id === 'beta')return;await new Promise(r=>setTimeout(r,30))}throw Error('beta did not load')})()`);
|
|
assert.equal(await b.evaluate(`document.querySelector('#show-machine').checked`), false);
|
|
});
|
|
|
|
|
|
test('organization creation draft survives Stay and is discarded on switch', async t => {
|
|
const organizations = {
|
|
list: async () => [{ id: 'alpha', label: 'Alpha', busHealthy: true }, { id: 'beta', label: 'Beta', busHealthy: true }],
|
|
resolve: async id => ({ reads: { business: async () => ({ business: { id } }), settings: async () => ({}), busStatus: async () => ({ live: true, down: false }) }, bus: {} }),
|
|
};
|
|
const service = { orgPresets: async () => [{ name: 'fixture', label: 'Test model', description: 'Canned replies', available: { ok: true }, thinking: { levels: ['off'], default: 'off' } }], templateRender: async id => ({ businessVersion: 1, id, human: 'local-dev-owner', arbiters: { delivery: 'chief', technical: 'chief' }, roles: { chief: { definition: 'chief-of-staff' } }, projects: { work: {} } }) };
|
|
const orgAdmin = createOrgAdmin({ service, registry: { listOrgs: async () => [] }, context: async () => ({ configDir: '/cfg', dataRoot: '/data', instance: 'test' }) });
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, orgAdmin, send: async () => ({}), board: 'http://127.0.0.1:9', log: () => {} });
|
|
t.after(() => close(server));
|
|
const b = await browser({ machine: false }); t.after(() => b.close());
|
|
const wait = expression => b.evaluate(`(async()=>{for(let i=0;i<150;i++){if(${expression})return;await new Promise(r=>setTimeout(r,30))}throw Error(${JSON.stringify(expression)})})()`);
|
|
await b.navigate(`http://127.0.0.1:${server.address().port}/#/orgs/alpha/settings/business`);
|
|
await b.evaluate(`location.hash='#/orgs/alpha/settings/organizations'`);
|
|
await wait('!!document.querySelector("#org-new-id")');
|
|
await wait('!document.querySelector("#org-new-form [data-write]").disabled');
|
|
await b.evaluate(`(()=>{const el=document.querySelector('#org-new-id');el.value='draft';el.dispatchEvent(new Event('input',{bubbles:true}));document.querySelector('#org-new-form [data-write]').click()})()`);
|
|
await wait('document.querySelector("#org-paths") && !document.querySelector("#org-paths").hidden');
|
|
await b.evaluate(`document.querySelector('#org-advanced-open').click()`);
|
|
await wait('!document.querySelector("#org-editor").hidden');
|
|
assert.match(await b.evaluate('location.hash'), /^#\/orgs\/alpha\/settings\/organizations/);
|
|
await b.evaluate(`(()=>{const el=document.querySelector('[data-path="$.human"]');el.value='unsaved';el.dispatchEvent(new Event('input',{bubbles:true}))})()`);
|
|
const before = await b.evaluate(`({ owner: document.querySelector('[data-path="$.human"]').value, json: document.querySelector('#org-json-text').value, hash: location.hash })`);
|
|
await b.evaluate('history.back()');
|
|
await wait('!!document.querySelector("dialog[open] [data-stay]")');
|
|
await b.evaluate(`document.querySelector('[data-stay]').click()`);
|
|
assert.deepEqual(await b.evaluate(`({ owner: document.querySelector('[data-path="$.human"]').value, json: document.querySelector('#org-json-text').value, hash: location.hash })`), before);
|
|
await b.evaluate(`location.hash='#/orgs/beta/settings/organizations'`);
|
|
await wait('!!document.querySelector("dialog[open] [data-stay]")');
|
|
await b.evaluate(`document.querySelector('[data-stay]').click()`);
|
|
assert.equal(await b.evaluate(`document.querySelector('[data-path="$.human"]').value`), 'unsaved');
|
|
assert.match(await b.evaluate('location.hash'), /^#\/orgs\/alpha\//);
|
|
await b.evaluate(`location.hash='#/orgs/beta/settings/organizations'`);
|
|
await wait('!!document.querySelector("dialog[open] [data-discard]")');
|
|
await b.evaluate(`document.querySelector('[data-discard]').click()`).catch(() => {});
|
|
await wait('window.MosaicOrg?.id === "beta"');
|
|
});
|
|
|
|
test('a stopped organization bus has truthful chat guidance', async t => {
|
|
const organizations = {
|
|
list: async () => [{ id: 'alpha', label: 'Alpha', busHealthy: false }],
|
|
resolve: async () => ({ busHealthy: false, reads: { business: async () => ({ business: { id: 'alpha', projects: [] } }) }, bus: {}, chat: { projects: async () => [], threads: async () => [] } }),
|
|
};
|
|
const server = await startServer({ port: 0, sso: createDevOwner(), organizations, send: async () => ({}), log: () => {} }); t.after(() => close(server));
|
|
const b = await browser({ machine: false }); t.after(() => b.close());
|
|
await b.navigate(`http://127.0.0.1:${server.address().port}/#/orgs/alpha/projects`);
|
|
await b.evaluate(`(async()=>{for(let i=0;i<200;i++){if(document.querySelector('.chat-area')?.innerText.includes('The bus is stopped'))return;await new Promise(r=>setTimeout(r,30))}throw Error('stopped-bus guidance missing')})()`);
|
|
const text = await b.evaluate(`document.querySelector('.chat-area').innerText`);
|
|
assert.match(text, /bus is stopped.*Start the bus/i);
|
|
assert.doesNotMatch(text, /This Console only reads/);
|
|
});
|
|
|
|
test('organization conversation recovers send capability and preserves its draft after an outage', async t => {
|
|
const { fixture } = await import('./fixture.mjs');
|
|
const { P, T, stub } = await import('./helpers/projects-review-fixture.mjs');
|
|
const f=await fixture(); t.after(()=>f.close());
|
|
const b=await browser({machine:false}); t.after(()=>b.close());
|
|
await b.call('Page.addScriptToEvaluateOnNewDocument',{source:stub + `\nwindow.MosaicOrg={id:'alpha',route:()=>'/projects/threads/${T}',list:Promise.resolve([]),readList:async()=>[]};window.configUp=false;{const old=fetch;window.fetch=(url,init)=>String(url)==='/api/config'?Promise.resolve(new Response(JSON.stringify({chatSend:window.configUp,orgSetup:true,devOwner:true,sso:true}),{status:200})):old(url,init)}`});
|
|
await b.navigate(f.base+'/#/projects/threads/'+T);
|
|
const wait=expr=>b.evaluate(`(async()=>{for(let i=0;i<200;i++){if(${expr})return;await new Promise(r=>setTimeout(r,30))}throw Error(${JSON.stringify(expr)})})()`);
|
|
await wait(`document.querySelector('#chat-body')`);
|
|
assert.equal(await b.evaluate(`document.querySelector('#chat-body').disabled`),true);
|
|
await b.evaluate(`const el=document.querySelector('#chat-body');el.value='draft kept';el.dispatchEvent(new Event('input',{bubbles:true}));window.configUp=true;document.querySelector('#refresh').click()`);
|
|
await wait(`!document.querySelector('#chat-body').disabled`);
|
|
assert.equal(await b.evaluate(`document.querySelector('#chat-body').value`),'draft kept');
|
|
});
|