Ctrl K
ComunidadGuías…

test/contextual-composer-browser.test.cjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
var test = require('node:test'), assert = require('node:assert/strict'), cp = require('node:child_process'), path = require('node:path'), playwright = require('playwright');
test('floating composers contain attachment/model controls and use real chooser and model acknowledgement paths', 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 (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 page = await browser.newPage({ viewport: { width: 1280, height: 800 }, reducedMotion: 'reduce' }); page.setDefaultTimeout(10000);
  await page.addInitScript(function () {
    // Exercise the production speech lifecycle without opening a microphone or
    // contacting a recognition provider.
    window.AudioContext = undefined; window.webkitAudioContext = undefined;
    window.SpeechRecognition = function () { window.composerRecognition = this; };
    window.SpeechRecognition.prototype.start = function () { if (this.onstart) this.onstart(); };
    window.SpeechRecognition.prototype.abort = function () {};
  });
  var errors = []; page.on('pageerror', function (error) { errors.push(error.message); });
  await page.goto('http://127.0.0.1:' + info.port + '/fixture/login?to=alpha');
  assert.equal(await page.locator('#contextual-attach-btn').count(), 0, 'Native project composer is unchanged');
  var mates = [{ id: info.sourceSlug.slice(5), slug: info.sourceSlug, canonical: true }, info.ordinaryMates[0]];
  for (var mate of mates) {
    await page.setViewportSize({ width: 1280, height: 800 });
    var icon = mate.canonical ? page.locator('.icon-strip-brand') : page.locator('.icon-strip-project-mate[data-mate-id="' + mate.id + '"]');
    await icon.click();
    var pane = page.frameLocator('#contextual-clay iframe[data-mate-id="' + mate.id + '"]');
    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);
    var windowFrame = page.frames().find(function (frame) { return frame.url().includes(mate.slug); });
    var input = pane.locator('#input');
    await input.fill('A multiline draft\nwith its caret and attachments retained.');
    for (var width of [1280, 390]) {
      await page.setViewportSize({ width: width, height: 844 });
      await pane.locator('#contextual-attach-btn').focus(); await page.keyboard.press('Enter');
      await pane.getByRole('dialog', { name: 'Attachments and context' }).waitFor();
      assert.equal(await pane.locator('#attach-file-btn').evaluate(function (node) { return node === document.activeElement; }), true);
      await page.keyboard.press('ArrowDown');
      assert.equal(await pane.locator('#attach-image-btn').evaluate(function (node) { return node === document.activeElement; }), true);
      var bounds = await pane.locator('#contextual-attach-popover').evaluate(function (node) { var r = node.getBoundingClientRect(); return r.left >= 0 && r.top >= 0 && r.right <= innerWidth && r.bottom <= innerHeight; });
      assert.equal(bounds, true, 'Attachment popover stays within iframe');
      await page.keyboard.press('Escape');
      assert.equal(await pane.locator('#contextual-attach-btn').getAttribute('aria-expanded'), 'false');
      assert.equal(await page.locator('#contextual-clay').isVisible(), true, 'Menu Escape leaves chat open');
      await pane.locator('#contextual-attach-btn').click();
      await pane.locator('#context-sources-add').focus(); await page.keyboard.press('Tab');
      await pane.locator('#contextual-attach-popover').waitFor({ state: 'hidden' });
      assert.equal(await page.locator('#contextual-clay').isVisible(), true, 'Tab exits the menu without dismissing chat');
      var geometry = await windowFrame.evaluate(function () {
        var area = document.getElementById('input-row').getBoundingClientRect();
        return ['contextual-attach-btn', 'config-chip', 'stt-btn', 'send-btn'].map(function (id) { var r = document.getElementById(id).getBoundingClientRect(); return { id: id, inside: r.left >= area.left && r.right <= area.right + 1 && r.top >= area.top && r.bottom <= area.bottom + 1 }; });
      });
      geometry.forEach(function (entry) { assert.equal(entry.inside, true, entry.id + ' is inside composer at ' + width); });
      await pane.locator('#config-chip').click(); await pane.locator('#config-popover:not(.hidden)').waitFor();
      assert.match(await pane.locator('#config-chip-label').textContent(), /Fixture/);
      assert.equal(await pane.locator('#config-popover').evaluate(function (node) { var r = node.getBoundingClientRect(); return r.left >= 0 && r.top >= 0 && r.right <= innerWidth && r.bottom <= innerHeight; }), true);
      await page.keyboard.press('Escape'); assert.equal(await page.locator('#contextual-clay').isVisible(), true);
      await page.screenshot({ path: '/tmp/contextual-composer-' + (mate.canonical ? 'clay' : 'ordinary') + '-' + width + '.png', animations: 'disabled' });
    }
    for (var kind of ['file', 'image']) {
      t.diagnostic('Chooser: ' + mate.id + ' / ' + kind);
      await pane.locator('#contextual-attach-btn').click();
      var chooser = page.waitForEvent('filechooser'); await pane.locator('#attach-' + kind + '-btn').click();
      await (await chooser).setFiles(kind === 'file' ? { name: 'composer-note.txt', mimeType: 'text/plain', buffer: Buffer.from('Composer attachment') } : { name: 'composer-pixel.png', mimeType: 'image/png', buffer: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aN1sAAAAASUVORK5CYII=', 'base64') });
      assert.equal(await pane.locator('#contextual-attach-btn').getAttribute('aria-expanded'), 'false');
    }
    await pane.locator('#image-preview-bar img').waitFor();
    await pane.locator('#image-preview-bar').getByText('composer-note.txt', { exact: false }).waitFor();
    await windowFrame.evaluate(async function () { (await import('./modules/ws-ref.js')).getWs().addEventListener('message', function (event) { var msg = JSON.parse(event.data); if (msg.type === 'model_selection_result') window.composerModelAck = msg; }); });
    await pane.locator('#config-chip').click();
    await pane.locator('#config-model-list button[data-model="fixture-model-alternative"]').click();
    await windowFrame.waitForFunction(function () { return window.composerModelAck && window.composerModelAck.ok && window.composerModelAck.model === 'fixture-model-alternative'; });
    assert.match(await pane.locator('#config-chip-label').textContent(), /Fixture alternative/);
    assert.equal(await input.inputValue(), 'A multiline draft\nwith its caret and attachments retained.');
    await pane.locator('#stt-btn-picker').click(); await pane.locator('#speech-picker').waitFor();
    await page.keyboard.press('Escape'); assert.equal(await page.locator('#contextual-clay').isVisible(), true);
    await pane.locator('#stt-btn').click(); await pane.locator('#input-row.stt-recording').waitFor();
    await windowFrame.evaluate(function () { window.composerRecognition.onresult({ results: [[{ transcript: 'Voice continuation' }]] }); });
    assert.match(await input.inputValue(), /retained\. Voice continuation$/);
    assert.equal(await pane.locator('.stt-cancel').evaluate(function (node) { var r = node.getBoundingClientRect(), row = document.getElementById('input-row').getBoundingClientRect(); return r.left >= row.left && r.right <= row.right && r.bottom <= row.bottom; }), true);
    await page.screenshot({ path: '/tmp/contextual-composer-voice-' + (mate.canonical ? 'clay' : 'ordinary') + '.png', animations: 'disabled' });
    await pane.locator('#stt-btn').click();
    assert.equal(await pane.locator('#input-row.stt-recording').count(), 0);
    assert.match(await input.inputValue(), /Voice continuation$/);
    await input.fill('A multiline draft\nwith its caret and attachments retained.');
    await input.press('Escape'); await page.setViewportSize({ width: 1280, height: 800 }); await icon.click();
    assert.equal(await input.inputValue(), 'A multiline draft\nwith its caret and attachments retained.');
    assert.match(await pane.locator('#config-chip-label').textContent(), /Fixture alternative/);
    await page.locator('[data-clay-close]').click();
  }
  assert.deepEqual(errors, []);
  t.diagnostic('Canonical and ordinary: inside-composer geometry at desktop/narrow, keyboard attachment menu, bounded model menu, native File/Image chooser, actual alternative-model ACK, draft/attachment/voice and dismissal continuity; native project composer unchanged.');
});