test/contextual-mates-browser.test.cjs
Rama main · réplica de lectura del repositorio original.
var test = require('node:test');
var assert = require('node:assert/strict');
var cp = require('node:child_process');
var path = require('node:path');
var playwright = require('playwright');
test('two ordinary Mates and canonical retain isolated conversations through popover, strip hint and native workspace', async function (t) {
var child = cp.fork(path.join(__dirname, 'fixtures/contextual-clay-server.cjs'), [], { stdio: ['ignore', 'pipe', 'pipe', 'ipc'] });
t.after(function () { child.kill('SIGTERM'); }); var output = '';
child.stdout.on('data', function (v) { output += v; }); child.stderr.on('data', function (v) { output += v; });
var info = await new Promise(function (resolve, reject) { var timer = setTimeout(function () { reject(new Error(output)); }, 15000); child.once('message', function (value) { clearTimeout(timer); resolve(value); }); });
var browser = await playwright.chromium.launch(); t.after(async function () { await browser.close(); });
var page = await browser.newPage({ viewport: { width: 1280, height: 800 }, reducedMotion: 'reduce' }); page.setDefaultTimeout(10000);
var errors = []; page.on('pageerror', function (error) { errors.push(error.message); });
var origin = 'http://127.0.0.1:' + info.port; var mates = info.ordinaryMates;
await page.goto(origin + '/fixture/login?to=alpha');
await page.waitForFunction(function () { return !document.querySelector('.icon-strip-brand').disabled; });
var preference = { left: 420, top: 80, width: 510, height: 620 };
assert.equal((await page.request.put(origin + '/api/user/contextual-clay-geometry', { headers: { 'X-Clay-Settings': '1' }, data: preference })).status(), 200);
await page.reload(); await page.waitForFunction(function () { return !document.querySelector('.icon-strip-brand').disabled; });
await page.evaluate(async function () { window.mateStore = (await import('/p/alpha/modules/store.js')).store; });
var projectSession = await page.evaluate(function () { return window.mateStore.get('activeSessionId'); });
await page.locator('#input').fill('Original project writing');
function icon(mate) { return page.locator('.icon-strip-project-mate[data-mate-id="' + mate.id + '"]'); }
function pane(mate) { return page.frameLocator('#contextual-clay iframe[data-mate-id="' + mate.id + '"]'); }
async function ready(mate, index) { await pane(mate).locator('#messages').getByText('ORDINARY_MATE_' + index + '_PRIVATE_HISTORY', { exact: false }).waitFor(); await pane(mate).locator('#input-wrapper:not([inert])').waitFor(); }
await icon(mates[0]).click(); await ready(mates[0], 0);
var hint = page.locator('#contextual-mate-hint'); assert.match(await hint.textContent(), /Click again to open Arch/);
assert.equal(await icon(mates[0]).getAttribute('aria-describedby'), 'contextual-mate-hint');
assert.equal(await page.locator('#contextual-clay strong').textContent(), mates[0].name);
var box = await page.locator('#contextual-clay').boundingBox(); var badge = await hint.boundingBox(); assert.ok(badge.x + badge.width <= box.x - 6 || badge.y + badge.height <= box.y - 6);
await pane(mates[0]).locator('#input').fill('Arch owns this draft only.');
var heldUpload; var uploadReady; var started = new Promise(function (resolve) { uploadReady = resolve; });
var uploadPattern = '**/p/' + mates[0].slug + '/api/upload';
await page.route(uploadPattern, function (route) { heldUpload = route; uploadReady(); });
await pane(mates[0]).locator('#contextual-attach-btn').click();
var choose = page.waitForEvent('filechooser'); await pane(mates[0]).locator('#attach-file-btn').click(); await (await choose).setFiles({ name: 'arch-private.txt', mimeType: 'text/plain', buffer: Buffer.from('Arch attachment') });
await started;
await icon(mates[1]).click(); await ready(mates[1], 1);
assert.equal(await pane(mates[1]).locator('#image-preview-bar').getByText('arch-private.txt').count(), 0);
await heldUpload.continue(); await page.unroute(uploadPattern);
await icon(mates[0]).click();
await pane(mates[0]).locator('#image-preview-bar').getByText('arch-private.txt', { exact: false }).waitFor();
var question = page.request.get(origin + '/fixture/ordinary-question?index=0');
var card = pane(mates[0]).locator('.ask-user-container:not(.answered)'); await card.waitFor(); var questionId = await card.getAttribute('data-tool-id');
await icon(mates[1]).click(); await ready(mates[1], 1);
assert.equal(await pane(mates[1]).locator('#input').getAttribute('placeholder'), 'Message this Mate…');
assert.equal(await pane(mates[1]).locator('#input').getAttribute('aria-label'), 'Message ' + mates[1].name);
assert.equal(await pane(mates[1]).locator('#input').inputValue(), ''); assert.equal(await pane(mates[1]).locator('.ask-user-container').count(), 0);
assert.equal(await pane(mates[1]).locator('#image-preview-bar').getByText('arch-private.txt').count(), 0);
await pane(mates[1]).locator('#input').fill('Second Mate owns a different draft.');
assert.equal(await page.locator('#contextual-clay iframe:visible').count(), 1);
assert.ok((await hint.boundingBox()).x + (await hint.boundingBox()).width < 1280);
await icon(mates[1]).hover(); assert.equal(await page.locator('.icon-strip-tooltip').count(), 0, 'Persistent hint replaces the competing hover tooltip');
await page.screenshot({ path: '/tmp/contextual-mates-long-name-light.png', animations: 'disabled' });
var visualFrame = page.frames().find(function (frame) { return frame.url().includes(mates[1].slug); });
await page.evaluate(async function () { (await import('/p/alpha/modules/theme.js')).applyTheme('clay-dark'); });
await visualFrame.evaluate(async function () { (await import('./modules/theme.js')).applyTheme('clay-dark'); });
await page.screenshot({ path: '/tmp/contextual-mates-long-name-dark.png', animations: 'disabled' });
await page.evaluate(async function () { (await import('/p/alpha/modules/theme.js')).applyTheme('clay-light'); });
await visualFrame.evaluate(async function () { (await import('./modules/theme.js')).applyTheme('clay-light'); });
await page.locator('.icon-strip-brand').click();
var canonical = page.frameLocator('#contextual-clay iframe[data-canonical="true"]');
await canonical.locator('#messages').getByText('Isolated canonical integration conversation.', { exact: true }).waitFor(); await canonical.locator('#input-wrapper:not([inert])').waitFor();
assert.equal(await canonical.locator('#input').inputValue(), ''); await canonical.locator('#input').fill('Canonical private draft.');
assert.equal(await canonical.locator('#messages').getByText('ORDINARY_MATE_0_PRIVATE_HISTORY', { exact: false }).count(), 0);
for (var index of [0, 1]) {
for (var fresh of ['true', 'false']) {
var catalog = await (await page.request.get(origin + '/fixture/ordinary-catalog?index=' + index + '&fresh=' + fresh)).json();
assert.ok(Array.isArray(catalog.tools)); assert.ok(catalog.canonicalNames.length >= 28);
catalog.canonicalNames.forEach(function (name) { assert.equal(catalog.tools.some(function (tool) { return tool.name === name; }), false, 'Ordinary Mate lacks canonical ' + name); });
}
}
var ordinaryWindow = page.frames().find(function (frame) { return frame.url().includes(mates[1].slug); });
assert.equal(await ordinaryWindow.evaluate(async function () { return (await import('./modules/contextual-clay-session.js')).contextualScreenReference(); }), null);
assert.equal(await pane(mates[0]).locator('#messages').getByText('Isolated canonical integration conversation.', { exact: true }).count(), 0);
var foreign = await browser.newContext();
assert.equal((await foreign.request.get(origin + '/p/' + mates[0].slug + '/', { headers: { Cookie: 'relay_auth_user=fixture-foreign-token' } })).status(), 403);
await foreign.close();
await icon(mates[0]).click(); assert.equal(await pane(mates[0]).locator('#input').inputValue(), 'Arch owns this draft only.'); assert.equal(await card.getAttribute('data-tool-id'), questionId);
assert.equal(await page.locator('#input').inputValue(), 'Original project writing');
await icon(mates[0]).click(); await page.waitForURL('**/p/' + mates[0].slug + '/');
await page.waitForFunction(function () { return document.body.classList.contains('contextual-clay-native') && !document.getElementById('input-wrapper').inert; });
assert.equal(await page.evaluate(function () { return window.mateStore.get('activeSessionId'); }), mates[0].sessionId);
assert.equal(await page.locator('#input').inputValue(), 'Arch owns this draft only.'); assert.equal(await pane(mates[0]).locator('#input').inputValue(), '');
assert.equal(await hint.isVisible(), false); assert.equal(await page.locator('body.mate-workspace-active').count(), 1);
assert.match(await page.locator('#image-preview-bar').textContent(), /arch-private/);
var nativeCard = page.locator('.ask-user-container[data-tool-id="' + questionId + '"]'); await nativeCard.getByText('Tests', { exact: true }).click(); await nativeCard.locator('.ask-user-submit').click(); assert.equal((await (await question).json()).status, 'submitted');
await page.locator('#input').fill('Edited in native Arch workspace.');
await icon(mates[1]).click();
assert.equal(new URL(page.url()).pathname, '/p/' + mates[0].slug + '/');
assert.equal(await page.locator('#input').inputValue(), 'Edited in native Arch workspace.');
assert.equal(await page.evaluate(function () { return window.mateStore.get('activeSessionId'); }), mates[0].sessionId);
assert.equal(await pane(mates[1]).locator('#input').inputValue(), 'Second Mate owns a different draft.');
await page.locator('[data-clay-close]').click();
assert.equal(new URL(page.url()).pathname, '/p/' + mates[0].slug + '/');
await page.waitForFunction(function () { return document.body.classList.contains('contextual-clay-native'); });
assert.equal(await page.locator('#input').inputValue(), 'Edited in native Arch workspace.');
await page.locator('#contextual-workspace-back').click(); await page.waitForURL('**/p/alpha/');
await page.waitForFunction(function () { return document.getElementById('input').value === 'Original project writing'; });
assert.equal(await page.evaluate(function () { return window.mateStore.get('activeSessionId'); }), projectSession);
await icon(mates[1]).click(); assert.equal(await pane(mates[1]).locator('#input').inputValue(), 'Second Mate owns a different draft.');
await hint.click(); await page.waitForURL('**/p/' + mates[1].slug + '/'); await page.waitForFunction(function () { return document.body.classList.contains('contextual-clay-native'); });
assert.equal(await page.locator('#input').inputValue(), 'Second Mate owns a different draft.');
await page.locator('#contextual-workspace-back').click(); await page.waitForURL('**/p/alpha/'); await page.waitForFunction(function () { return !window.mateStore.get('contextualWorkspace'); });
await icon(mates[0]).focus(); await page.keyboard.press('Enter'); assert.equal(await pane(mates[0]).locator('#input').inputValue(), 'Edited in native Arch workspace.');
await pane(mates[0]).locator('#input').press('Escape'); assert.equal(await hint.isVisible(), false); assert.equal(await icon(mates[0]).getAttribute('aria-describedby'), null);
await icon(mates[0]).click({ button: 'right' }); await page.locator('.project-ctx-menu').waitFor(); assert.equal(await page.locator('#contextual-clay').isVisible(), false); await page.keyboard.press('Escape');
await icon(mates[0]).focus(); await page.keyboard.press('Shift+F10'); await page.locator('.project-ctx-menu').waitFor(); await page.keyboard.press('Escape');
assert.equal(await page.locator('#contextual-clay').isVisible(), false);
await icon(mates[1]).click(); await page.setViewportSize({ width: 390, height: 844 });
await page.waitForFunction(function () { return !!document.querySelector('.contextual-mate-mobile-trigger'); });
assert.equal(await icon(mates[1]).isVisible(), true); assert.equal(await hint.isVisible(), true);
badge = await hint.boundingBox(); box = await page.locator('#contextual-clay').boundingBox(); assert.ok(badge.x >= 46 && badge.x + badge.width <= 390 && badge.y + badge.height <= box.y);
await page.screenshot({ path: '/tmp/contextual-mates-narrow-light.png', animations: 'disabled' });
await page.evaluate(async function () { (await import('/p/alpha/modules/theme.js')).applyTheme('clay-dark'); });
var secondFrame = page.frames().find(function (frame) { return frame.url().includes(mates[1].slug); }); await secondFrame.evaluate(async function () { (await import('./modules/theme.js')).applyTheme('clay-dark'); });
await page.screenshot({ path: '/tmp/contextual-mates-narrow-dark.png', animations: 'disabled' });
assert.deepEqual((await (await page.request.get(origin + '/api/user/contextual-clay-geometry')).json()).geometry, preference);
await icon(mates[1]).click(); await page.waitForURL('**/p/' + mates[1].slug + '/'); await page.locator('#contextual-workspace-back').click(); await page.waitForURL('**/p/alpha/');
await page.setViewportSize({ width: 1280, height: 800 }); await page.waitForFunction(function () { return !window.mateStore.get('contextualWorkspace'); });
await icon(mates[1]).click();
await page.evaluate(function (slug) { window.savedProjectRegistry = window.mateStore.get('projectsHubList'); window.mateStore.set({ projectsHubList: window.savedProjectRegistry.map(function (project) { return project.slug === slug ? Object.assign({}, project, { contentAccess: false }) : project; }) }); }, mates[1].slug);
assert.equal(await page.locator('#contextual-clay').isVisible(), false); assert.equal(await hint.isVisible(), false);
await page.evaluate(function () { window.mateStore.set({ projectsHubList: window.savedProjectRegistry }); });
await page.evaluate(async function (id) { var list = window.mateStore.get('cachedMatesList'); window.savedMateRegistry = list; window.mateStore.set({ cachedMatesList: list.map(function (mate) { return mate.id === id ? Object.assign({}, mate, { createdBy: 'foreign' }) : mate; }) }); (await import('/p/alpha/modules/project-mate-navigation.js')).activateContextualMate(id); }, mates[1].id);
assert.equal(await page.locator('#contextual-clay').isVisible(), false);
await page.evaluate(function () { window.mateStore.set({ cachedMatesList: window.savedMateRegistry }); });
await icon(mates[0]).click();
await page.evaluate(function (id) { var items = window.mateStore.get('cachedMatesList'); window.mateStore.set({ cachedMatesList: items.map(function (mate) { return mate.id === id ? Object.assign({}, mate, { archived: true }) : mate; }) }); }, mates[0].id);
assert.equal(await page.locator('#contextual-clay').isVisible(), false); assert.equal(await hint.isVisible(), false);
await page.evaluate(async function (id) { await (await import('/p/alpha/modules/project-mate-navigation.js')).activateContextualMate(id); }, mates[0].id); assert.equal(await page.locator('#contextual-clay').isVisible(), false);
await icon(mates[1]).click(); await page.evaluate(function (id) { window.mateStore.set({ cachedMatesList: window.mateStore.get('cachedMatesList').filter(function (mate) { return mate.id !== id; }) }); }, mates[1].id);
assert.equal(await page.locator('#contextual-clay').isVisible(), false);
await page.locator('.icon-strip-brand').click(); assert.equal(await canonical.locator('#input').inputValue(), 'Canonical private draft.');
var direct = await browser.newPage({ viewport: { width: 1280, height: 800 } });
await direct.goto(origin + '/fixture/login?to=alpha'); await direct.goto(origin + '/p/' + mates[1].slug + '/');
await direct.locator('#messages').getByText('ORDINARY_MATE_1_PRIVATE_HISTORY', { exact: false }).waitFor();
await direct.evaluate(async function () { window.directStore = (await import('./modules/store.js')).store; });
await direct.waitForFunction(function (session) { return window.directStore.get('activeSessionId') === session && !window.directStore.get('replayingHistory'); }, mates[1].sessionId);
await direct.locator('#input').fill('Writing in a directly opened native Mate.');
var directIcon = direct.locator('.icon-strip-project-mate[data-mate-id="' + mates[1].id + '"]'); await directIcon.click();
var directPane = direct.frameLocator('#contextual-clay iframe'); await directPane.locator('#input').waitFor();
await direct.waitForFunction(function () { return document.getElementById('input-wrapper').inert && document.getElementById('input').value === ''; });
await direct.waitForFunction(function () { var frame = document.querySelector('#contextual-clay iframe'); return frame && frame.contentDocument.getElementById('input') && frame.contentDocument.getElementById('input').value === 'Writing in a directly opened native Mate.'; });
assert.equal(await directPane.locator('#input').inputValue(), 'Writing in a directly opened native Mate.');
await directPane.locator('#input').fill('Edited in direct-entry popover.');
await direct.locator('[data-clay-close]').click(); assert.equal(await direct.locator('#input').inputValue(), 'Edited in direct-entry popover.');
assert.equal(await direct.locator('#input-wrapper').evaluate(function (el) { return el.inert; }), false);
await directIcon.click(); await directPane.locator('#input-wrapper:not([inert])').waitFor(); await directIcon.click();
await direct.waitForFunction(function () { return document.body.classList.contains('contextual-clay-native'); });
assert.equal(await direct.locator('#input').inputValue(), 'Edited in direct-entry popover.');
await direct.locator('#contextual-workspace-back').click(); await direct.waitForURL('**/p/alpha/');
await direct.goto(origin + '/p/' + mates[1].slug + '/');
await direct.locator('#messages').getByText('ORDINARY_MATE_1_PRIVATE_HISTORY', { exact: false }).waitFor();
await directIcon.click();
await direct.evaluate(async function (id) { var state = (await import('./modules/store.js')).store; state.set({ cachedMatesList: state.get('cachedMatesList').filter(function (mate) { return mate.id !== id; }) }); }, mates[1].id);
assert.equal(await direct.locator('#contextual-clay').isVisible(), false);
assert.equal(await direct.locator('#input-wrapper').evaluate(function (el) { return el.inert; }), false, 'Removed native origin cannot trap project navigation');
await direct.close();
var touchContext = await browser.newContext({ viewport: { width: 390, height: 844 }, isMobile: true, hasTouch: true });
var touch = await touchContext.newPage(); await touch.goto(origin + '/fixture/login?to=alpha');
await touch.waitForFunction(function () { return !document.querySelector('.icon-strip-brand').disabled; });
await touch.getByRole('button', { name: 'Chat', exact: true }).tap();
await touch.locator('.mobile-chat-chip[data-mate-id="' + mates[1].id + '"]').tap();
var touchPane = touch.frameLocator('#contextual-clay iframe'); await touchPane.locator('#messages').getByText('ORDINARY_MATE_1_PRIVATE_HISTORY', { exact: false }).waitFor();
assert.match(await touch.locator('#contextual-mate-hint').textContent(), /^Tap again to open /);
var touchIcon = touch.locator('.contextual-mate-mobile-trigger'); assert.ok((await touchIcon.boundingBox()).width >= 44);
await touchPane.locator('#input-wrapper:not([inert])').waitFor(); await touchPane.locator('#input').fill('Touch presentation retains its exact Mate.');
await touchIcon.tap(); await touch.waitForURL('**/p/' + mates[1].slug + '/');
await touch.waitForFunction(function () { return document.body.classList.contains('contextual-clay-native'); });
assert.equal(await touch.locator('#input').inputValue(), 'Touch presentation retains its exact Mate.');
await touch.locator('#contextual-workspace-back').tap(); await touch.waitForURL('**/p/alpha/'); await touchContext.close();
assert.deepEqual(errors, []);
t.diagnostic('Two ordinary Mates plus canonical: isolated sessions/drafts/attachments/question, first/second clicks, native routes/Back, pill actions and placement, keyboard/right-click, narrow icon, archived/deleted refusal and ordinary canonical-catalog exclusion.');
});