test/contextual-mate-overlay-browser.test.cjs
Rama main · réplica de lectura del repositorio original.
var test = require('node:test'), assert = require('node:assert/strict'), cp = require('node:child_process'), path = require('node:path'), playwright = require('playwright');
test('different Mate popovers preserve native workspace ownership and nested Back in both directions', async function (t) {
var child = cp.fork(path.join(__dirname, 'fixtures/contextual-clay-server.cjs'), [], { env: Object.assign({}, process.env, { CLAY_CONTEXT_ACCESS_FIXTURE: '1' }), stdio: ['ignore', 'pipe', 'pipe', 'ipc'] });
t.after(function () { child.kill('SIGTERM'); });
var output = ''; child.stdout.on('data', function (data) { output += data; }); child.stderr.on('data', function (data) { output += data; });
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 origin = 'http://127.0.0.1:' + info.port;
var clay = { id: info.sourceSlug.slice(5), slug: info.sourceSlug, canonical: true };
var pairs = [[clay, info.ordinaryMates[0]], [info.ordinaryMates[0], clay], [info.ordinaryMates[0], info.ordinaryMates[1]]];
for (var pair of pairs) {
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 from = pair[0], to = pair[1];
function icon(mate) { return mate.canonical ? page.locator('.icon-strip-brand') : 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) {
await page.waitForFunction(function (id) { var f = document.querySelector('#contextual-clay iframe[data-mate-id="' + id + '"]'); return f && f.conversation && f.conversation.identity().ready; }, mate.id);
await pane(mate).locator('#input-wrapper:not([inert])').waitFor();
}
async function native(mate) {
await page.waitForURL('**/p/' + mate.slug + '/');
await page.waitForFunction(function (slug) { var s = window.overlayStore; return s.get('currentSlug') === slug && s.get('contextualWorkspace') && s.get('contextualWorkspace').stage === 'workspace' && !document.getElementById('input-wrapper').inert; }, mate.slug);
}
await page.goto(origin + '/fixture/login?to=alpha');
await page.evaluate(async function () { window.overlayStore = (await import('/p/alpha/modules/store.js')).store; });
await page.locator('#input').fill('Origin project draft');
await icon(from).click(); await ready(from);
await pane(from).locator('#input').fill('Native owner ' + from.id);
await icon(from).click();
await native(from);
var session = await page.evaluate(function () { return window.overlayStore.get('activeSessionId'); });
var header = await page.locator('#header-left').textContent();
var chooser = page.waitForEvent('filechooser'); await page.locator('#attach-file-btn').click();
await (await chooser).setFiles({ name: 'native-owner.txt', mimeType: 'text/plain', buffer: Buffer.from('Native attachment') });
await page.locator('#image-preview-bar').getByText('native-owner.txt', { exact: false }).waitFor();
var review, reviewId;
if (from.canonical) {
await page.request.get(origin + '/fixture/stream');
await page.locator('#messages').getByText('CONTEXTUAL_STREAM_CONTINUES', { exact: false }).waitFor();
} else {
review = page.request.get(origin + '/fixture/ordinary-question?index=0');
await page.locator('.ask-user-container:not(.answered)').waitFor();
reviewId = await page.locator('.ask-user-container:not(.answered)').getAttribute('data-tool-id');
}
await icon(to).focus(); await page.keyboard.press('Enter');
await ready(to);
assert.equal(new URL(page.url()).pathname, '/p/' + from.slug + '/');
assert.equal(await page.evaluate(function () { return window.overlayStore.get('activeSessionId'); }), session);
assert.equal(await page.locator('#header-left').textContent(), header);
assert.equal(await page.locator('#input').inputValue(), 'Native owner ' + from.id);
assert.equal(await page.locator('#input-wrapper').evaluate(function (node) { return node.inert; }), false);
assert.equal(await pane(from).locator('#input').inputValue(), '', 'Hidden source pane does not own the native draft');
assert.equal(await pane(from).locator('#input').evaluate(function (node) { return node.readOnly; }), true);
assert.equal(await pane(to).locator('#image-preview-bar').getByText('native-owner.txt').count(), 0);
await pane(to).locator('#input').fill('Separate overlay ' + to.id);
if (!to.canonical) {
await page.locator('[data-clay-context]').filter({ hasText: 'Unavailable' }).waitFor();
var targetFrame = page.frames().find(function (frame) { return frame.url().includes(to.slug); });
assert.equal(await targetFrame.evaluate(async function () { return (await import('./modules/contextual-mate-context.js')).contextualAccessReference(); }), null, 'Private background Mate does not become an ordinary context grant');
}
if (from.canonical) assert.match(await page.locator('#messages').textContent(), /CONTEXTUAL_STREAM_CONTINUES/);
else assert.equal(await page.locator('.ask-user-container:not(.answered)').getAttribute('data-tool-id'), reviewId);
await pane(to).locator('#input').press('Escape');
assert.equal(await page.locator('#contextual-clay').isVisible(), false);
assert.equal(new URL(page.url()).pathname, '/p/' + from.slug + '/');
assert.equal(await page.locator('#input').inputValue(), 'Native owner ' + from.id);
await icon(to).click(); await ready(to);
await page.setViewportSize({ width: 390, height: 844 });
assert.equal(await pane(to).locator('#input').inputValue(), 'Separate overlay ' + to.id);
await page.locator('[data-clay-close]').click();
assert.equal(new URL(page.url()).pathname, '/p/' + from.slug + '/');
await page.setViewportSize({ width: 1280, height: 800 });
await icon(to).click(); await ready(to);
if (to.canonical) await icon(to).click();
else await page.locator('[data-clay-expand]').click();
await native(to);
assert.match(await page.locator('#contextual-workspace-back').textContent(), from.canonical ? /Back to Clay/ : /Back to Arch/);
assert.equal(await page.locator('#input').inputValue(), 'Separate overlay ' + to.id);
await page.locator('#input').fill('Edited destination ' + to.id);
await page.locator('#contextual-workspace-back').click(); await native(from);
assert.equal(await page.locator('#input').inputValue(), 'Native owner ' + from.id);
assert.match(await page.locator('#image-preview-bar').textContent(), /native-owner/);
assert.equal(await pane(to).locator('#input').inputValue(), 'Edited destination ' + to.id);
if (review) {
var card = page.locator('.ask-user-container[data-tool-id="' + reviewId + '"]');
await card.getByText('Tests', { exact: true }).click(); await card.locator('.ask-user-submit').click();
assert.equal((await (await review).json()).status, 'submitted');
}
await page.locator('#contextual-workspace-back').click(); await page.waitForURL('**/p/alpha/');
await page.waitForFunction(function () { return !window.overlayStore.get('contextualWorkspace') && document.getElementById('input').value === 'Origin project draft'; });
assert.deepEqual(errors, []);
await page.close();
}
for (var pair of pairs.slice(0, 2)) {
var page = await browser.newPage({ viewport: { width: 1280, height: 800 }, reducedMotion: 'reduce' }); page.setDefaultTimeout(10000);
var from = pair[0], to = pair[1];
function directIcon(mate) { return mate.canonical ? page.locator('.icon-strip-brand') : page.locator('.icon-strip-project-mate[data-mate-id="' + mate.id + '"]'); }
await page.goto(origin + '/fixture/login?to=alpha');
await page.goto(origin + '/p/' + from.slug + '/');
await page.locator('#messages').getByText(from.canonical ? 'Isolated canonical integration conversation.' : 'ORDINARY_MATE_0_PRIVATE_HISTORY', { exact: false }).waitFor();
await page.evaluate(async function () { window.directStore = (await import('./modules/store.js')).store; });
await page.waitForFunction(function () { return window.directStore.get('activeSessionId') && !window.directStore.get('replayingHistory'); });
var session = await page.evaluate(function () { return window.directStore.get('activeSessionId'); });
await page.locator('#input').fill('Direct native owner');
await directIcon(to).click();
var overlay = page.frameLocator('#contextual-clay iframe[data-mate-id="' + to.id + '"]');
await page.waitForFunction(function (id) { var frame = document.querySelector('#contextual-clay iframe[data-mate-id="' + id + '"]'); return frame && frame.conversation && frame.conversation.identity().ready; }, to.id);
assert.equal(new URL(page.url()).pathname, '/p/' + from.slug + '/');
assert.equal(await page.locator('#input').inputValue(), 'Direct native owner');
assert.equal(await page.evaluate(function () { return window.directStore.get('activeSessionId'); }), session);
await overlay.locator('#input').fill('Direct overlay owner');
await overlay.locator('#input').press('Escape');
assert.equal(new URL(page.url()).pathname, '/p/' + from.slug + '/');
await directIcon(to).click(); await directIcon(to).click();
await page.waitForURL('**/p/' + to.slug + '/');
await page.waitForFunction(function () { return window.directStore.get('contextualWorkspace').stage === 'workspace'; });
assert.equal(await page.locator('#input').inputValue(), 'Direct overlay owner');
await page.locator('#contextual-workspace-back').click(); await page.waitForURL('**/p/' + from.slug + '/');
await page.waitForFunction(function () { return !window.directStore.get('contextualWorkspace') && !document.getElementById('input-wrapper').inert && document.getElementById('input').value === 'Direct native owner'; });
assert.equal(await page.evaluate(function () { return window.directStore.get('activeSessionId'); }), session);
await page.close();
}
t.diagnostic('Clay → Arch, Arch → Clay and Arch → ordinary: first keyboard click and Escape/close preserve native route/session/header/draft/attachment and pending stream/review; explicit Open transfers exact draft; Back restores native owner then project. Private Mate context grants remain unavailable.');
});