test/contextual-clay-corrections.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');
var browser, child, origin, page, frame, input, surface, trigger, sourceSlug;
var draft = 'Help me organise this project. Keep my current session open while we review the next steps.';
test.before(async function () {
child = cp.fork(path.join(__dirname, 'fixtures/contextual-clay-server.cjs'), [], { stdio: ['ignore', 'pipe', 'pipe', 'ipc'] });
var output = '';
child.stdout.on('data', function (value) { output += value; });
child.stderr.on('data', function (value) { output += value; });
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); });
child.once('exit', function () { clearTimeout(timer); reject(new Error(output)); });
});
origin = 'http://127.0.0.1:' + info.port; sourceSlug = info.sourceSlug;
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(5000);
await page.goto(origin + '/fixture/login?to=alpha');
trigger = page.locator('.icon-strip-brand');
await page.waitForFunction(function () { return !document.querySelector('.icon-strip-brand').disabled; });
surface = page.locator('#contextual-clay');
frame = page.frameLocator('#contextual-clay iframe');
input = frame.locator('#input');
});
test.afterEach(async function () { await page.close(); });
async function open() {
await trigger.click();
await frame.locator('#messages').getByText('Isolated canonical integration conversation.', { exact: true }).waitFor();
await input.waitFor();
await frame.locator('#input-wrapper:not([inert])').waitFor();
}
async function geometry(t, label) {
var result = await input.evaluate(function (element) {
var stop = document.getElementById('generation-stop').getBoundingClientRect();
var controls = ['stt-btn', 'stt-btn-picker', 'send-btn'].map(function (id) { var r = document.getElementById(id).getBoundingClientRect(); return { id: id, left: r.left, right: r.right, top: r.top, bottom: r.bottom }; });
return { tracks: getComputedStyle(document.getElementById('input-row')).gridTemplateColumns, voiceArea: getComputedStyle(document.querySelector('.stt-controls')).gridArea, height: element.clientHeight, scrollHeight: element.scrollHeight, scrollTop: element.scrollTop, overflow: getComputedStyle(element).overflowY, caret: element.selectionEnd, value: element.value, stop: { left: stop.left, right: stop.right, top: stop.top, bottom: stop.bottom }, controls: controls };
});
t.diagnostic(label + ': ' + JSON.stringify(result));
assert.equal(result.value, draft);
assert.equal(result.caret, draft.length);
assert.ok(result.scrollHeight <= result.height + 1 || result.overflow === 'auto', 'Capped draft has actual scrolling');
assert.ok(result.scrollHeight - result.height - result.scrollTop <= 1, 'The final line and caret remain visible after resize');
if (label.includes('running')) result.controls.forEach(function (r) {
var stop = result.stop;
assert.ok(stop.right <= r.left || stop.left >= r.right || stop.bottom <= r.top || stop.top >= r.bottom, 'Stop does not overlap ' + r.id);
});
}
test('1: exact desktop draft retains its final line and separate running controls at 390x844', async function (t) {
await open();
await input.fill(draft);
await page.setViewportSize({ width: 390, height: 844 });
await page.waitForTimeout(200);
await geometry(t, 'narrow idle');
await page.request.get(origin + '/fixture/stream');
await frame.locator('#generation-stop').waitFor({ state: 'visible' });
await page.waitForTimeout(100);
await geometry(t, 'narrow running');
await input.fill(Array(20).fill(draft).join('\n'));
assert.equal(await input.evaluate(function (element) { return getComputedStyle(element).overflowY === 'auto' && element.scrollHeight > element.clientHeight && element.offsetHeight <= 121; }), true);
});
test('2: Open Clay navigates to native Mate shell and Back restores the project', async function (t) {
await open();
await page.setViewportSize({ width: 390, height: 844 });
await page.getByRole('button', { name: 'Open Clay', exact: true }).click();
await page.waitForURL('**/p/' + sourceSlug + '/');
await page.locator('#contextual-workspace-back').waitFor();
assert.equal(await page.locator('body').evaluate(function (body) { return body.classList.contains('mate-workspace-active'); }), true);
assert.equal(await surface.isVisible(), false);
await page.getByRole('button', { name: 'Back to project', exact: true }).click();
await page.waitForURL('**/p/alpha/');
await page.locator('#header-title').getByText('Alpha destination', { exact: true }).waitFor();
assert.equal(await page.locator('#header-full-access-btn').isVisible(), true);
t.diagnostic('Actual canonical Mate route/shell, hidden popover, Back to original project verified.');
});
test('3: real authenticated review leads with named intent, with the unchanged exact payload initially collapsed', async function (t) {
await open();
var proposal = page.request.get(origin + '/fixture/propose?viewer=alpha&target=beta');
var review = frame.locator('.ask-user-container:not(.answered)').last();
await review.locator('.product-review-action').waitFor();
assert.equal(await review.locator('.product-review-action').textContent(), 'Open conversation: Beta destination');
assert.equal(await review.locator('.product-review pre').isVisible(), false);
assert.doesNotMatch(await review.locator('.product-review-action, .product-review > p').allTextContents().then(function (items) { return items.join('\n'); }), /viewer:|session:|"action"/);
var exact = await review.locator('.product-review pre').textContent();
assert.match(exact, /Selected viewer: viewer:/);
assert.match(exact, /"action":"navigate"/);
await review.locator('summary').click();
assert.equal(await review.locator('pre').isVisible(), true);
assert.equal(await review.locator('pre').textContent(), exact);
var reviewId = await review.getAttribute('data-tool-id');
await review.getByText('Keep unchanged', { exact: true }).click();
await review.locator('.ask-user-submit').click();
assert.equal((await (await proposal).json()).status, 'cancelled');
assert.equal(await frame.locator('.ask-user-container[data-tool-id=\"' + reviewId + '\"]').locator('.ask-user-answer-label').textContent(), 'Open conversation');
t.diagnostic('Named action and target visible; exact viewer/session/action payload retained; real authenticated decline cancelled operation.');
});
test('4: mouse/keyboard opens enter the ready composer; delayed loading does not steal focus; inner Escape wins', async function (t) {
var release;
var gate = new Promise(function (resolve) { release = resolve; });
await page.route('**/*contextual=1*', async function (route) { await gate; await route.continue(); });
await trigger.click();
await page.locator('#input').focus();
release();
await frame.locator('#messages').getByText('Isolated canonical integration conversation.', { exact: true }).waitFor();
await frame.locator('#input-wrapper:not([inert])').waitFor();
assert.equal(await page.locator('#input').evaluate(function (element) { return document.activeElement === element; }), true, 'Readiness must not take focus back after the user moves away');
await surface.locator('[data-clay-close]').click();
await trigger.click();
assert.equal(await input.evaluate(function (element) { return document.activeElement === element; }), true, 'Mouse opening enters the ready composer');
await page.keyboard.press('Escape');
assert.equal(await surface.isVisible(), false, 'Composer Escape closed the popover before keyboard reopening');
await trigger.focus(); await page.keyboard.press('Enter');
t.diagnostic('Keyboard mode: ' + await surface.getAttribute('data-mode'));
assert.equal(await input.evaluate(function (element) { return document.activeElement === element; }), true, 'Keyboard opening enters the ready composer');
await frame.locator('#stt-btn-picker').click();
await frame.locator('#speech-picker').waitFor();
await page.keyboard.press('Escape');
assert.equal(await frame.locator('#speech-picker').count(), 0);
assert.equal(await surface.isVisible(), true);
await input.focus(); await page.keyboard.press('Escape');
assert.equal(await surface.isVisible(), false);
await trigger.click(); await trigger.click();
await page.waitForURL('**/p/' + sourceSlug + '/');
await page.locator('#contextual-workspace-back').waitFor();
await trigger.click();
await page.waitForURL('**/p/alpha/');
t.diagnostic('Delayed readiness preserved project focus; both entry methods focus the composer; inner voice menu consumes Escape; rapid cycle closes.');
});