test/clay-control-activity-browser.test.cjs
Rama main · réplica de lectura del repositorio original.
var test = require('node:test'), assert = require('node:assert/strict');
var cp = require('node:child_process'), path = require('node:path'), playwright = require('playwright');
var child, browser, origin, page, frame, events;
test.before(async function () {
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'] });
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 (data) { clearTimeout(timer); resolve(data); });
child.once('exit', function () { clearTimeout(timer); reject(new Error(output)); });
});
origin = 'http://127.0.0.1:' + info.port; browser = await playwright.chromium.launch();
});
test.after(async function () { if (browser) await browser.close(); if (child) child.kill('SIGTERM'); });
test.beforeEach(async function () {
page = await browser.newPage({ viewport: { width: 1280, height: 800 } }); page.setDefaultTimeout(6000); events = [];
page.on('websocket', function (ws) { ws.on('framereceived', function (event) {
try { var data = JSON.parse(event.payload); if (data.type === 'clay_control_activity' || data.type === 'clay_navigation_apply') events.push(data); } catch (error) {}
}); });
await page.goto(origin + '/fixture/login?to=alpha');
await page.locator('.icon-strip-brand:not([disabled])').waitFor(); await page.locator('.icon-strip-brand').click();
frame = page.frameLocator('#contextual-clay iframe[data-canonical="true"]');
await page.waitForFunction(function () { var f = document.querySelector('#contextual-clay iframe[data-canonical="true"]'); return f && f.conversation && f.conversation.identity().ready; });
await page.request.get(origin + '/fixture/activity-reset');
var header = page.locator('#contextual-clay > header');
var box = await header.boundingBox();
await page.mouse.move(box.x + 100, box.y + 15); await page.mouse.down(); await page.mouse.move(870, box.y + 15); await page.mouse.up();
});
test.afterEach(async function () { if (page) { await page.request.get(origin + '/fixture/activity-release'); await page.request.get(origin + '/fixture/activity-reset'); await page.close(); } });
async function propose(mode) {
var pending = page.request.get(origin + '/fixture/activity?mode=' + mode);
pending.catch(function () {});
await frame.locator('.ask-user-container:not(.answered)').last().waitFor();
await page.locator('#clay-control-activity').waitFor();
return { pending: pending, review: frame.locator('.ask-user-container:not(.answered)').last() };
}
async function answer(proposal, choice, keyboard) {
var option = proposal.review.getByRole('button', { name: choice || 'Apply exact change', exact: true });
if (keyboard) { await option.focus(); await page.keyboard.press('Space'); }
else await option.click();
var submit = proposal.review.getByRole('button', { name: 'Submit', exact: true });
if (keyboard) { await submit.focus(); await page.keyboard.press('Enter'); } else await submit.click();
}
async function phase(value) {
await page.waitForFunction(async function (expected) { var m = await import('./modules/store.js'); var a = m.store.get('clayControlActivity'); return a && a.message.phase === expected; }, value);
}
async function geometry() {
return page.evaluate(function () {
var element = document.getElementById('clay-control-activity'), row = document.querySelector('.session-item[data-session-id]');
var rect = element.getBoundingClientRect(), target = row ? row.getBoundingClientRect() : {left:0,right:0,top:0,bottom:0}, style = getComputedStyle(element);
var identity = element.querySelector('.clay-control-identity'), name = element.querySelector('.clay-control-name');
var bubble = element.querySelector('.clay-control-bubble').getBoundingClientRect();
var composer = document.querySelector('#input-area').getBoundingClientRect();
var panel = document.getElementById('contextual-clay').getBoundingClientRect();
function box(r) { return { left: r.left, right: r.right, top: r.top, bottom: r.bottom }; }
return { rect: box(rect), target: box(target), bubble: box(bubble), composer: box(composer), panel: box(panel),
targeted: element.dataset.targeted, pointerEvents: style.pointerEvents, live: element.getAttribute('aria-live'),
tipX: rect.left + parseFloat(style.getPropertyValue('--tip-x')), tipY: rect.top + parseFloat(style.getPropertyValue('--tip-y')),
avatarBottom: element.querySelector('img').getBoundingClientRect().bottom,
identity: box(identity.getBoundingClientRect()), name: name.textContent, grouped: identity.contains(element.querySelector('img')) && identity.contains(name),
identityHeight: identity.getBoundingClientRect().height, nameWidth: name.clientWidth, nameOverflow: getComputedStyle(name).textOverflow,
avatarAlt: element.querySelector('img').alt, statusCount: document.querySelectorAll('#clay-control-activity [role=status]').length,
cursorTransform: getComputedStyle(element.querySelector('.clay-control-pointer')).transform };
});
}
function disjoint(a, b) { return a.right <= b.left || a.left >= b.right || a.bottom <= b.top || a.top >= b.bottom; }
test('real review/running/persisted events place the pointer on the exact visible session and follow layout', async function (t) {
var p = await propose('hold'); await phase('review');
var g = await geometry(); t.diagnostic(JSON.stringify(g));
assert.equal(g.targeted, 'true'); assert.equal(g.pointerEvents, 'none'); assert.equal(g.live, 'polite');
assert.ok(Math.abs(g.tipX - (g.target.right - 2)) < 1); assert.ok(Math.abs(g.tipY - (g.target.top + g.target.bottom) / 2) < 1);
assert.equal(g.grouped, true); assert.equal(g.name, 'Clay'); assert.equal(g.identityHeight, 26);
assert.equal(g.avatarAlt, ''); assert.equal(g.statusCount, 0); assert.ok(g.nameWidth > 20);
assert.ok(g.bubble.top >= g.identity.bottom + 3); assert.ok(g.bubble.top >= g.avatarBottom); assert.ok(disjoint(g.rect, g.target)); assert.ok(disjoint(g.rect, g.composer)); assert.ok(disjoint(g.rect, g.panel));
await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.dataset.savedOrigin = row.dataset.controlSessionOrigin; row.dataset.controlSessionOrigin = 'replaced'; });
await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'false'; });
await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.dataset.controlSessionOrigin = row.dataset.savedOrigin; });
await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'true'; });
await page.evaluate(function () { var row = document.querySelector('.session-item[data-session-id]').getBoundingClientRect(); var cover = document.createElement('div'); cover.id = 'fixture-occlusion'; cover.style.cssText = 'position:fixed;z-index:500;left:' + row.left + 'px;top:' + row.top + 'px;width:' + row.width + 'px;height:' + row.height + 'px;background:var(--bg)'; document.body.appendChild(cover); });
await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'false'; });
await page.locator('#fixture-occlusion').evaluate(function (element) { element.remove(); });
await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'true'; });
await page.evaluate(function () {
var row = document.querySelector('.session-item[data-session-id]').getBoundingClientRect();
var obstacle = document.createElement('div'); obstacle.id = 'fixture-placement'; obstacle.setAttribute('role', 'dialog');
obstacle.style.cssText = 'position:fixed;left:' + (row.right + 4) + 'px;top:' + (row.top + row.height / 2 + 85) + 'px;width:250px;height:100px;'; document.body.appendChild(obstacle);
});
await page.waitForFunction(function () { return document.getElementById('clay-control-activity').classList.contains('is-flipped'); });
var flipped = await geometry(); assert.equal(flipped.targeted, 'true'); assert.match(flipped.cursorTransform, /-1/);
assert.ok(flipped.bubble.top > flipped.identity.bottom); assert.ok(Math.abs(flipped.tipY - g.tipY) < 1); assert.ok(disjoint(flipped.rect, flipped.target));
await page.screenshot({ path: '/tmp/clay-control-pill-flipped.png' });
await page.locator('#fixture-placement').evaluate(function (element) { element.remove(); });
await page.evaluate(function () {
var row = document.querySelector('.session-item[data-session-id]'), marker = document.createElement('span'); marker.id = 'fixture-row-home';
row.before(marker); document.body.appendChild(row); row.style.cssText = 'position:fixed;left:550px;top:350px;width:120px;z-index:150;';
});
await page.waitForFunction(function () { return document.getElementById('clay-control-activity').classList.contains('is-reversed'); });
var reversed = await geometry(); assert.equal(reversed.targeted, 'true'); assert.match(reversed.cursorTransform, /-1/);
assert.ok(Math.abs(reversed.tipX - (reversed.target.left + 2)) < 1); assert.ok(Math.abs(reversed.identity.right - reversed.rect.right) < 1);
assert.ok(reversed.bubble.top > reversed.identity.bottom); assert.ok(disjoint(reversed.rect, reversed.target));
await page.screenshot({ path: '/tmp/clay-control-pill-reversed.png' });
await page.evaluate(function () { var row = document.querySelector('.session-item[data-session-id]'); document.getElementById('fixture-row-home').replaceWith(row); row.removeAttribute('style'); });
await answer(p); await phase('running');
await page.screenshot({ path: '/tmp/clay-control-activity-desktop-light.png' });
await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.style.marginTop = '90px'; window.dispatchEvent(new Event('resize')); });
await page.waitForTimeout(80); var moved = await geometry(); assert.ok(moved.tipY > g.tipY + 80);
await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.hidden = true; });
await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'false'; });
await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.hidden = false; row.style.marginTop = '1600px'; window.dispatchEvent(new Event('scroll')); });
await page.waitForTimeout(80); assert.equal((await geometry()).targeted, 'false');
await page.locator('.session-item[data-session-id]').evaluate(function (row) { row.remove(); });
await page.waitForTimeout(80); assert.equal((await geometry()).targeted, 'false');
var released = await (await page.request.get(origin + '/fixture/activity-release')).json(); t.diagnostic(JSON.stringify(released));
assert.equal(released.released, true); var result = await (await p.pending).json(); assert.equal(result.persisted, true); await phase('persisted');
});
test('real decline, persistence failure and uncertain provider response retain truthful outcomes', async function () {
for (var mode of ['normal', 'fail', 'unknown']) {
var p = await propose(mode); await answer(p, mode === 'normal' ? 'Keep unchanged' : null);
var result = await (await p.pending).json();
assert.equal(result.status, mode === 'normal' ? 'cancelled' : 'failed');
await phase(mode === 'normal' ? 'cancelled' : mode === 'unknown' ? 'unknown' : 'failed');
}
});
test('narrow light/dark and reduced motion retain a non-interactive status, keyboard reviews and writing', async function () {
await page.setViewportSize({ width: 390, height: 844 });
await page.emulateMedia({ reducedMotion: 'reduce' });
var p = await propose('hold'); await phase('review');
assert.equal((await geometry()).targeted, 'false'); await answer(p, null, true); await phase('running');
await page.screenshot({ path: '/tmp/clay-control-activity-narrow-light.png' });
await page.evaluate(async function () { (await import('./modules/theme.js')).applyTheme('clay-dark'); });
await frame.locator('body').evaluate(async function () { (await import('./modules/theme.js')).applyTheme('clay-dark'); });
await page.screenshot({ path: '/tmp/clay-control-activity-narrow-dark.png' });
var g = await geometry(); assert.ok(g.rect.left >= 0 && g.rect.right <= 390); assert.ok(disjoint(g.rect, g.composer));
assert.equal(await page.locator('#clay-control-activity img').evaluate(function (img) { return getComputedStyle(img).animationName; }), 'none');
await frame.locator('#input').fill('A preserved keyboard draft');
assert.equal(await frame.locator('#input').inputValue(), 'A preserved keyboard draft');
await page.request.get(origin + '/fixture/activity-release'); await p.pending;
await page.setViewportSize({ width: 1280, height: 800 }); await page.screenshot({ path: '/tmp/clay-control-activity-desktop-dark.png' });
});
test('Stop withdraws immediately and delayed replies cannot revive the activity', async function () {
var p = await propose('hold'); await answer(p); await phase('running');
await frame.locator('#input').fill('Keep this draft after Stop');
await page.request.get(origin + '/fixture/stream');
await frame.locator('#generation-stop:visible, #send-btn.stop:visible').first().click();
await page.locator('#clay-control-activity').waitFor({ state: 'detached' });
await page.request.get(origin + '/fixture/activity-release'); await p.pending;
await page.waitForTimeout(350); assert.equal(await page.locator('#clay-control-activity').count(), 0);
});
test('a delayed older result cannot overwrite a newer reviewed activity', async function () {
var first = await propose('hold'); await answer(first); await phase('running');
var second = await propose('normal'); await phase('review'); await answer(second);
assert.equal((await (await second.pending).json()).status, 'failed'); await phase('failed');
var current = await page.evaluate(async function () { return (await import('./modules/store.js')).store.get('clayControlActivity').message.id; });
await page.request.get(origin + '/fixture/activity-release'); await first.pending;
await page.waitForTimeout(150);
assert.equal(await page.evaluate(async function () { return (await import('./modules/store.js')).store.get('clayControlActivity').message.id; }), current);
await phase('failed');
});
test('reviewed navigation separates service delivery and actual rendered conversation evidence', async function (t) {
var pending = page.request.get(origin + '/fixture/propose?viewer=alpha&target=alpha'); pending.catch(function () {});
var review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor();
await phase('review'); await answer({ review: review }); var result = await (await pending).json();
assert.equal(result.delivered, true); assert.equal(result.visible, 'unknown');
await page.waitForTimeout(500); t.diagnostic(JSON.stringify(events.map(function (item) { return { domain: item.domain, phase: item.phase }; })));
assert.ok(events.some(function (item) { return item.domain === 'screen' && item.phase === 'running'; }));
assert.equal(events.some(function (item) { return item.domain === 'screen' && ['persisted', 'completed'].includes(item.phase); }), false);
var observed = await (await page.request.get(origin + '/fixture/activity-navigation?deliveryId=' + result.deliveryId)).json();
assert.equal(observed.visible, true); assert.equal(observed.renderStatus, 'rendered');
assert.ok(events.some(function (item) { return item.domain === 'screen' && item.phase === 'rendered'; }));
});
test('delayed source packets cannot cross a same-ID source replacement, document reload or live Mate replacement', async function () {
var p = await propose('normal'); await phase('review');
var old = events.find(function (item) { return item.phase === 'review'; });
await answer(p, 'Keep unchanged'); await p.pending;
var originalLocalId = old.sourceSessionId;
async function reopen() {
await page.reload(); await page.locator('.icon-strip-brand:not([disabled])').waitFor(); await page.locator('.icon-strip-brand').click();
frame = page.frameLocator('#contextual-clay iframe[data-canonical="true"]');
await page.waitForFunction(function () { var f = document.querySelector('#contextual-clay iframe[data-canonical="true"]'); return f && f.conversation && f.conversation.identity().ready; });
}
async function replay(message) {
await frame.locator('body').evaluate(function (body, packet) { window.parent.postMessage({ type: 'clay-control-presentation', message: packet }, location.origin); }, message);
await page.waitForTimeout(100);
}
try {
var priorViewer = old.recipientViewerRef;
await page.locator('#contextual-clay iframe[data-canonical="true"]').evaluate(function (element) { element.contentWindow.location.reload(); });
await page.waitForFunction(function (viewer) { var f = document.querySelector('#contextual-clay iframe[data-canonical="true"]'); return f && f.conversation && f.conversation.document === f.contentDocument && f.conversation.identity().ready && f.conversation.identity().viewerRef !== viewer; }, priorViewer);
await replay(Object.assign({}, old, { order: Number.MAX_SAFE_INTEGER, sequence: Number.MAX_SAFE_INTEGER }));
await phase('cancelled');
var afterReload = await propose('normal'); await phase('review'); await answer(afterReload, 'Keep unchanged'); await afterReload.pending;
var replaced = await (await page.request.get(origin + '/fixture/activity-source-replace')).json();
assert.equal(replaced.localId, originalLocalId); assert.notEqual(replaced.origin, old.sourceSessionOriginId);
await reopen(); await replay(old);
assert.equal(await page.locator('#clay-control-activity').count(), 0, 'An unseen delayed packet from the old durable source cannot render');
var fresh = await propose('normal'); await phase('review');
var id = await page.evaluate(async function () { return (await import('./modules/store.js')).store.get('clayControlActivity').message.id; });
await replay(Object.assign({}, old, { order: Number.MAX_SAFE_INTEGER, sequence: Number.MAX_SAFE_INTEGER }));
assert.equal(await page.evaluate(async function () { return (await import('./modules/store.js')).store.get('clayControlActivity').message.id; }), id, 'Identity rejection happens before receipt order changes');
await answer(fresh, 'Keep unchanged'); await fresh.pending; await phase('cancelled');
var beforeMate = events.filter(function (item) { return item.id === id; }).at(-1);
await page.request.get(origin + '/fixture/activity-mate-replace'); await reopen(); await replay(beforeMate);
assert.equal(await page.locator('#clay-control-activity').count(), 0);
var next = await propose('normal'); await phase('review'); await answer(next, 'Keep unchanged'); await next.pending; await phase('cancelled');
var live = events.filter(function (item) { return item.phase === 'review'; }).at(-1);
assert.notEqual(live.sourceMateStamp, beforeMate.sourceMateStamp);
// Preserve the live connection and origin to isolate the registry stamp guard.
await replay(Object.assign({}, live, { sourceMateStamp: beforeMate.sourceMateStamp, order: Number.MAX_SAFE_INTEGER, sequence: Number.MAX_SAFE_INTEGER }));
await phase('cancelled');
} finally { await page.request.get(origin + '/fixture/activity-source-restore'); }
});
test('ordinary restricted navigation retains its real long Mate identity across compact themes and image failure', async function () {
await page.locator('.icon-strip-project-mate').nth(1).click();
var ordinary = page.frameLocator('#contextual-clay iframe[data-canonical="false"]').last();
await ordinary.locator('#input-wrapper:not([inert])').waitFor();
await page.waitForFunction(function () { var f = Array.from(document.querySelectorAll('#contextual-clay iframe[data-canonical="false"]')).find(function (item) { return !item.hidden; }); return f && f.conversation && f.conversation.identity().ready; });
await page.locator('[data-clay-context]').filter({ hasText: 'Alpha integration · Next message' }).waitFor();
await ordinary.locator('#input').fill('Review this selected conversation.'); await ordinary.locator('#send-btn').click();
await ordinary.locator('#messages').getByText('Review this selected conversation.', { exact: true }).waitFor();
await page.locator('[data-clay-context]').filter({ hasText: 'Alpha integration · Shared' }).waitFor();
async function call(name, args) { return (await page.request.get(origin + '/fixture/context-tool?index=1&name=' + name + '&args=' + encodeURIComponent(JSON.stringify(args || {})))).json(); }
var context = await call('read_current_context'); assert.equal(context.status, 'shared_snapshot');
var nav = call('open_context_conversation', { contextRef: context.contextRef, requestId: 'pill-long-name', why: 'Open the reviewed conversation' });
var review = ordinary.locator('.ask-user-container:not(.answered)').last(); await review.waitFor();
await review.getByRole('button', { name: 'Apply exact change', exact: true }).click(); await review.getByRole('button', { name: 'Submit', exact: true }).click();
assert.equal((await nav).delivered, true);
await page.locator('#clay-control-activity .clay-control-identity').waitFor();
var actor = await page.locator('#clay-control-activity .clay-control-name').textContent();
assert.match(actor, /Release planning/);
var avatar = await page.locator('#clay-control-activity img').getAttribute('src');
assert.doesNotMatch(avatar, /lead-clay/);
for (var variant of [{ width: 1280, theme: 'light' }, { width: 1280, theme: 'dark' }, { width: 390, theme: 'light' }, { width: 390, theme: 'dark' }]) {
await page.setViewportSize({ width: variant.width, height: variant.width === 390 ? 844 : 800 });
await page.evaluate(async function (theme) { (await import('./modules/theme.js')).applyTheme('clay-' + theme); }, variant.theme);
await page.waitForTimeout(50);
var g = await geometry(); assert.equal(g.name, actor); assert.equal(g.grouped, true); assert.equal(g.nameOverflow, 'ellipsis');
assert.ok(g.nameWidth > 100); assert.ok(g.identity.right <= g.rect.right); assert.ok(g.bubble.top >= g.identity.bottom + 3);
assert.ok(g.rect.left >= 0 && g.rect.right <= variant.width); assert.ok(disjoint(g.rect, g.composer));
await page.screenshot({ path: '/tmp/clay-control-pill-ordinary-' + variant.width + '-' + variant.theme + '.png' });
}
await page.route('**/fixture/missing-avatar.png', function (route) { return route.abort(); });
await page.locator('#clay-control-activity img').evaluate(function (image) { image.src = '/fixture/missing-avatar.png'; });
await page.locator('#clay-control-activity img').waitFor({ state: 'hidden' });
assert.equal(await page.locator('#clay-control-activity img').isVisible(), false);
assert.equal(await page.locator('#clay-control-activity .clay-control-name').textContent(), actor);
assert.equal(await page.locator('#clay-control-activity .clay-control-bubble').isVisible(), true);
});
async function objectProposal(kind) {
var pending = page.request.get(origin + '/fixture/activity-object?kind=' + kind); pending.catch(function () {});
var review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor(); await phase('review');
return { pending: pending, review: review };
}
async function uncoverWorkbench() {
await page.setViewportSize({ width: 1600, height: 900 });
var handle = await page.locator('#contextual-clay > [data-clay-resize="s"]').boundingBox();
await page.mouse.move(handle.x + handle.width / 2, handle.y + handle.height / 2); await page.mouse.down(); await page.mouse.move(handle.x + handle.width / 2, handle.y - 280, { steps: 8 }); await page.mouse.up();
var header = page.locator('#contextual-clay > header'), box = await header.boundingBox();
await page.mouse.move(box.x + 45, box.y + 16); await page.mouse.down(); await page.mouse.move(160, 500, { steps: 8 }); await page.mouse.up();
assert.ok((await header.boundingBox()).y > 400);
}
async function targetGeometry(kind) {
for (var attempt = 0; attempt < 120; attempt++) {
var value = await page.evaluate(async function (expected) {
var state = (await import('./modules/store.js')).store;
var targets = await import('./modules/clay-control-targets.js');
var activity = state.get('clayControlActivity'), el = document.getElementById('clay-control-activity');
if (!activity || !activity.message.target || activity.message.target.kind !== expected || !el || el.dataset.targeted !== 'true') return false;
var target = targets.controlTarget(activity.message.target); if (!target || el.getAnimations().some(function (a) { return a.playState === 'running'; })) return false;
var rect = target.getBoundingClientRect(), box = el.getBoundingClientRect(), style = getComputedStyle(el), reversed = el.classList.contains('is-reversed');
var x = box.left + parseFloat(style.getPropertyValue('--tip-x')), y = box.top + parseFloat(style.getPropertyValue('--tip-y'));
if (!Number.isFinite(x) || !Number.isFinite(y)) return false;
if (Math.abs(x - (reversed ? rect.left + 2 : rect.right - 2)) >= 1 || Math.abs(y - (rect.top + rect.height / 2)) >= 1) return false;
return { kind: activity.message.target.kind, x: x, y: y, left: rect.left, right: rect.right, middle: rect.top + rect.height / 2, reversed: reversed };
}, kind);
if (value) return value;
await page.waitForTimeout(50);
}
await page.screenshot({ path: '/tmp/clay-object-failure.png' });
throw new Error('The exact ' + kind + ' target did not acquire a visible pointer');
}
function exactTip(g) { assert.ok(Math.abs(g.x - (g.reversed ? g.left + 2 : g.right - 2)) < 1); assert.ok(Math.abs(g.y - g.middle) < 1); }
test('visible Mate and owned project rail adapters use real profile and assignment proposals', async function () {
var p = await objectProposal('mate'); exactTip(await targetGeometry('mate'));
await page.screenshot({ path: '/tmp/clay-control-adapter-mate.png' });
await answer(p, 'Keep unchanged'); assert.equal((await (await p.pending).json()).status, 'cancelled');
var result = await (await page.request.get(origin + '/fixture/activity-object?kind=assignment')).json(); assert.equal(result.status, 'proposed');
exactTip(await targetGeometry('project'));
await page.screenshot({ path: '/tmp/clay-control-adapter-project-assignment.png' });
var card = frame.locator('[data-assignment-id]').last();
await card.getByRole('button', { name: /Cancel|Decline|Keep/i }).click();
await phase('cancelled');
});
test('file, Issue and Log adapters require the actual visible object revision', async function () {
var seeded = await (await page.request.get(origin + '/fixture/activity-object?kind=seed')).json(); assert.ok(seeded.issue.ref, JSON.stringify(seeded));
await uncoverWorkbench();
for (var kind of ['file', 'issue', 'log']) {
await page.evaluate(async function (item) {
if (item === 'file') (await import('./modules/filebrowser.js')).openFile('activity-target.txt');
if (item === 'issue') (await import('./modules/issues.js')).openIssues();
if (item === 'log') (await import('./modules/project-logs.js')).openProjectLogs();
}, kind);
var visible = kind === 'file' ? '#file-viewer-path .current' : kind === 'issue' ? '.issue-row' : '.project-log-row';
await page.locator(visible).first().waitFor();
var p = await objectProposal(kind); exactTip(await targetGeometry(kind === 'file' ? 'file' : 'record'));
await page.screenshot({ path: '/tmp/clay-control-adapter-' + kind + '.png' });
if (kind === 'file') {
await page.emulateMedia({ reducedMotion: 'reduce', colorScheme: 'dark' });
await page.evaluate(async function () { (await import('./modules/theme.js')).applyTheme('clay-dark'); });
await page.setViewportSize({ width: 390, height: 844 });
await page.waitForFunction(function () { var e = document.getElementById('clay-control-activity'); return e && e.dataset.targeted === 'false' && e.classList.contains('is-inline'); });
var visibleStatus = await page.locator('#clay-control-activity').evaluate(function (element) { var r = element.getBoundingClientRect(); var hit = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2); return hit && element.parentElement.contains(hit) && element.parentElement.id === 'file-viewer'; }); assert.equal(visibleStatus, true);
var narrow = await geometry(); assert.equal(narrow.name, 'Clay'); assert.equal(narrow.grouped, true); assert.ok(narrow.rect.right <= 390); assert.ok(disjoint(narrow.rect, narrow.composer));
await page.screenshot({ path: '/tmp/clay-control-adapter-file-narrow-dark-reduced.png' });
await page.setViewportSize({ width: 1600, height: 900 });
await page.emulateMedia({ reducedMotion: 'no-preference', colorScheme: 'light' });
await page.evaluate(async function () { (await import('./modules/theme.js')).applyTheme('clay-light'); });
}
if (kind !== 'file') {
await page.locator(visible).first().evaluate(function (row) { row.dataset.savedRevision = row.dataset.controlRevision; row.dataset.controlRevision = 'replaced'; });
await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'false'; });
await page.locator(visible).first().evaluate(function (row) { row.dataset.controlRevision = row.dataset.savedRevision; });
await targetGeometry('record');
}
await answer(p, 'Keep unchanged'); assert.equal((await (await p.pending).json()).status, 'cancelled');
}
});
test('navigation receipt cannot confirm an invisible transcript or a different durable origin', async function () {
await page.locator('#messages').evaluate(function (element) { element.style.visibility = 'hidden'; });
var pending = page.request.get(origin + '/fixture/propose?viewer=alpha&target=alpha');
var review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor(); await answer({ review: review });
var delivery = await (await pending).json(); assert.equal(delivery.delivered, true);
await page.waitForTimeout(5200);
var receipt = await (await page.request.get(origin + '/fixture/activity-navigation?deliveryId=' + delivery.deliveryId)).json();
assert.equal(receipt.visible, 'unknown'); assert.equal(receipt.renderStatus, 'unknown');
assert.equal(events.some(function (event) { return event.phase === 'rendered'; }), false);
await page.locator('#messages').evaluate(function (element) { element.style.visibility = ''; element.dataset.controlSessionOrigin = 'old-rendered-origin'; });
pending = page.request.get(origin + '/fixture/propose?viewer=alpha&target=alpha');
review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor(); await answer({ review: review });
delivery = await (await pending).json(); await page.waitForTimeout(5200);
receipt = await (await page.request.get(origin + '/fixture/activity-navigation?deliveryId=' + delivery.deliveryId)).json();
assert.equal(receipt.visible, 'unknown'); assert.equal(receipt.renderStatus, 'unknown');
assert.equal(events.some(function (event) { return event.phase === 'rendered'; }), false);
});
test('project schedule row adapter follows the final human creation and exact task revision', async function () {
await uncoverWorkbench();
var p = await objectProposal('schedule'); await answer(p);
assert.equal((await (await p.pending).json()).status, 'proposed');
await page.getByRole('button', { name: 'Create task', exact: true }).click();
await page.locator('.scheduled-task-row[data-task-id]').first().waitFor().catch(async function (error) { console.log(await page.locator('#scheduled-tasks-panel').textContent()); throw error; });
if (!(await page.locator('#contextual-clay').isVisible())) await page.locator('.icon-strip-brand').click();
await uncoverWorkbench();
p = await objectProposal('schedule'); exactTip(await targetGeometry('schedule'));
await page.screenshot({ path: '/tmp/clay-control-adapter-schedule.png' });
await page.locator('.scheduled-task-row').first().evaluate(function (row) { row.dataset.controlRevision = 'replacement-task-revision'; });
await page.waitForFunction(function () { return document.getElementById('clay-control-activity').dataset.targeted === 'false'; });
await answer(p, 'Keep unchanged'); assert.equal((await (await p.pending).json()).status, 'cancelled');
});
test('Git path and shell terminal adapters use authorised repository and terminal identities', async function () {
await page.setViewportSize({ width: 1600, height: 900 });
var p = await objectProposal('terminal_create'); await answer(p);
var created = await (await p.pending).json(); assert.equal(created.runtimeEffect, 'shell_created');
await page.evaluate(async function () { (await import('./modules/terminal.js')).openTerminal(); });
await page.locator('.terminal-tab').first().waitFor().catch(async function (error) { await page.screenshot({path:'/tmp/clay-terminal-failure.png'}); console.log(await page.locator('#terminal-container').evaluate(function(e){return e.outerHTML.slice(0,1500);})); throw error; });
var terminalHeader = await page.locator('#contextual-clay > header').boundingBox();
await page.mouse.move(terminalHeader.x + 45, terminalHeader.y + 16); await page.mouse.down(); await page.mouse.move(160, 48, { steps: 8 }); await page.mouse.up();
p = await objectProposal('terminal'); exactTip(await targetGeometry('terminal'));
await page.screenshot({ path: '/tmp/clay-control-adapter-terminal.png' }); await answer(p, 'Keep unchanged'); await p.pending;
await page.evaluate(async function () { (await import('./modules/terminal.js')).closeTerminal(); });
await (await page.request.get(origin + '/fixture/activity-object?kind=git_seed')).json();
await page.evaluate(async function () { await (await import('./modules/git-panel.js')).refreshGitSummary(); });
await page.getByRole('button', { name: 'Close Clay conversation', exact: true }).click();
await page.locator('#git-placard-more').click();
await page.locator('.git-file-row').first().waitFor();
await page.locator('.icon-strip-brand').click();
await page.waitForTimeout(300);
var box = await page.locator('#contextual-clay > header').boundingBox();
await page.mouse.move(box.x + 100, box.y + 16); await page.mouse.down(); await page.mouse.move(1120, 100, { steps: 8 }); await page.mouse.up();
assert.ok((await page.locator('#contextual-clay').boundingBox()).x > 500);
await page.evaluate(async function () { await (await import('./modules/git-panel.js')).refreshGitStatus(); });
p = await objectProposal('git'); exactTip(await targetGeometry('git'));
await page.screenshot({ path: '/tmp/clay-control-adapter-git.png' }); await answer(p, 'Keep unchanged'); await p.pending;
});
test('render acknowledgement requires the reviewed visible entry and stops on a changed or hidden viewer', async function () {
var pending = page.request.get(origin + '/fixture/propose?viewer=alpha&target=alpha&entry=0');
var review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor(); await answer({ review: review });
var delivery = await (await pending).json();
await page.waitForFunction(function () { var e = document.getElementById('messages'); return e && e.dataset.controlHistoryRevision; });
for (var attempt = 0; attempt < 50; attempt++) {
var receipt = await (await page.request.get(origin + '/fixture/activity-navigation?deliveryId=' + delivery.deliveryId)).json();
if (receipt.visible === true) break;
await page.waitForTimeout(50);
}
assert.equal(receipt.visible, true); assert.equal(receipt.renderStatus, 'rendered');
assert.ok(events.find(function (event) { return event.type === 'clay_navigation_apply' && event.target.entryIndex === 0 && event.target.historyRevision; }));
for (var invalidation of ['hidden', 'disconnect', 'changed-route']) {
await page.locator('#messages').evaluate(function (element) { element.style.visibility = 'hidden'; });
pending = page.request.get(origin + '/fixture/propose?viewer=alpha&target=alpha');
review = frame.locator('.ask-user-container:not(.answered)').last(); await review.waitFor(); await answer({ review: review });
delivery = await (await pending).json();
await page.waitForFunction(async function () { return !!(await import('./modules/store.js')).store.get('clayNavigationRendering'); });
await page.evaluate(async function (change) {
if (change === 'hidden') { Object.defineProperty(document, 'hidden', { configurable: true, value: true }); document.dispatchEvent(new Event('visibilitychange')); }
if (change === 'disconnect') (await import('./modules/ws-ref.js')).getWs().close();
if (change === 'changed-route') (await import('./modules/store.js')).store.set({ currentSlug: 'changed-view' });
}, invalidation);
await page.waitForTimeout(5200);
receipt = await (await page.request.get(origin + '/fixture/activity-navigation?deliveryId=' + delivery.deliveryId)).json();
assert.equal(receipt.visible, 'unknown', invalidation); assert.notEqual(receipt.renderStatus, 'rendered', invalidation);
await page.goto(origin + '/fixture/login?to=alpha');
await page.locator('.icon-strip-brand:not([disabled])').waitFor(); await page.locator('.icon-strip-brand').click();
frame = page.frameLocator('#contextual-clay iframe[data-canonical="true"]');
await page.waitForFunction(function () { var f = document.querySelector('#contextual-clay iframe'); return f && f.conversation && f.conversation.identity().ready; });
}
});