Ctrl K
ComunidadGuías…

test/markdown-viewer-link-open.test.js

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

Archivo original ↗Preguntar a KiBot
// Real chat Markdown link -> exact pane/shell file viewer. Uses synthetic
// Markdown only, in a temp project served by the isolated pane fixture
// (isolated HOME/CLAY_HOME, provider and Git-write fences set before imports).
var test = require('node:test'), assert = require('node:assert/strict'), cp = require('node:child_process');
var fs = require('node:fs'), path = require('node:path');
var chromium = require('playwright').chromium;
var hasSavedViews = fs.existsSync(path.join(__dirname, '../lib/public/modules/split-view-presentations.js'));

test('markdown view mode defaults to rendered, honors line/diff targets and project-scoped choices', async function () {
  global.navigator = { userAgent: 'node', platform: '', maxTouchPoints: 0 };
  var store = await import('file://' + path.join(__dirname, '../lib/public/modules/store.js'));
  store.createStore({ fileViewerMarkdownModes: {} });
  var mode = await import('file://' + path.join(__dirname, '../lib/public/modules/markdown-view-mode.js'));
  ['a.md', 'constructor', '__proto__.md', 'toString.md', 'hasOwnProperty'].forEach(function (name) {
    assert.equal(mode.initialMarkdownRendered('p1', name, null, null), true, name);
  });
  assert.equal(mode.initialMarkdownRendered('p1', 'a.md', { path: 'a.md', line: 3 }, null), false);
  assert.equal(mode.initialMarkdownRendered('p1', 'a.md', { path: 'other.md', line: 3 }, null), true);
  assert.equal(mode.initialMarkdownRendered('p1', 'a.md', null, { type: 'diff' }), false);
  mode.rememberMarkdownViewMode('p1', 'docs/a.md', false);
  assert.equal(mode.initialMarkdownRendered('p1', 'docs/a.md', null, null), false);
  assert.equal(mode.initialMarkdownRendered('p2', 'docs/a.md', null, null), true, 'same relative path in another project is independent');
  mode.rememberMarkdownViewMode('p1', '__proto__.md', false);
  assert.equal(mode.initialMarkdownRendered('p1', '__proto__.md', null, null), false);
  assert.equal(mode.initialMarkdownRendered('p1', 'constructor', null, null), true);
  mode.resetMarkdownViewModes();
  assert.equal(mode.initialMarkdownRendered('p1', 'docs/a.md', null, null), true);
  delete global.navigator;
});

async function startFixture(t, env) {
  var child = cp.fork(path.join(__dirname, 'fixtures/pane-reuse-server.cjs'), [], { env: Object.assign({}, process.env, { CLAY_PANE_BENCH: '' }, env), stdio: ['ignore', 'pipe', 'pipe', 'ipc'] });
  t.after(function () { child.kill('SIGTERM'); });
  var info = await new Promise(function (resolve, reject) { child.once('message', resolve); child.once('error', reject); });
  var cwd = fs.realpathSync(path.join(info.root, 'reuse'));
  fs.mkdirSync(path.join(cwd, 'docs'));
  fs.writeFileSync(path.join(cwd, 'docs/SYNTH.ko.md'), '# Synthetic heading\n\nBody **bold** text.\n\n- item\n');
  fs.writeFileSync(path.join(cwd, 'plain.js'), 'var synthetic = 1;\n');
  var browser = await chromium.launch(); t.after(function () { return browser.close(); });
  var page = await browser.newPage(), errors = [];
  page.on('pageerror', function (error) { errors.push(error.message); });
  var origin = 'http://127.0.0.1:' + info.port;
  await require('./fixtures/source-module-runtime.cjs').install(page);
  await page.goto(origin + '/fixture/login');
  var data = await (await page.request.get(origin + '/fixture/state')).json();
  function viewer() {
    return page.evaluate(function () {
      var el = document.querySelector('.file-instance') || document.getElementById('file-viewer');
      return { hidden: el.classList.contains('hidden'), rendered: !!el.querySelector('.file-viewer-markdown h1'),
        source: !!el.querySelector('code.language-markdown'), toggle: el.classList.contains('file-instance') ? !el.querySelector('[data-action=preview]').disabled : !document.getElementById('file-viewer-render').classList.contains('hidden') };
    });
  }
  return { page: page, origin: origin, cwd: cwd, data: data, errors: errors, viewer: viewer,
    absolute: path.join(cwd, 'docs/SYNTH.ko.md'),
    say: function (id, text) { return page.request.post(origin + '/fixture/live?kind=turn&id=' + id + '&text=' + encodeURIComponent(text)); },
    closeViewer: async function () { var instance = page.locator('.file-instance'); if (await instance.count()) await instance.getByRole('button', { name: 'Close file pane', exact: true }).click(); else await page.click('#file-viewer-close'); await page.waitForFunction(function () { return !document.querySelector('.file-instance') && document.getElementById('file-viewer').classList.contains('hidden'); }); },
    clickIn: async function (scope, label, expect) {
      await scope.getByRole('button', { name: label, exact: true }).last().click();
      await page.waitForFunction(function (kind) {
        var el = document.querySelector('.file-instance') || document.getElementById('file-viewer');
        if (el.classList.contains('hidden')) return false;
        if (kind === 'markdown') return !!el.querySelector('.file-viewer-markdown h1');
        if (kind === 'source') return !!el.querySelector('code.language-markdown');
        return !!el.querySelector('.file-viewer-body .file-viewer-code-content, .file-viewer-body pre');
      }, expect, { timeout: 10000 });
    },
    panesReady: function (count, ids) {
      return page.waitForFunction(function (args) {
        var frames = Array.from(document.querySelectorAll('#split-host .split-pane-frame'));
        return frames.length === args.count && args.ids.every(function (id) {
          var frame = frames.find(function (item) { return Number(item.dataset.sessionId) === id; });
          var live = frame && frame.clayPane && frame.clayPane.identity();
          return live && live.ready && live.sessionId === id && !frame.dataset.requestedSessionId;
        });
      }, { count: count, ids: ids }, { timeout: 30000 });
    },
    pane: function (id) { return page.frameLocator('.split-pane-frame[data-session-id="' + id + '"]'); } };
}

async function exerciseLinks(f, scope, label) {
  await f.clickIn(scope, 'Open file ' + f.absolute, 'markdown');
  var state = await f.viewer();
  assert.deepEqual([state.rendered, state.source, state.toggle], [true, false, true], label + ': absolute link opens rendered first');
  if (await f.page.locator('.file-instance').count()) await f.page.locator('.file-instance [data-action=source]').click(); else await f.page.click('#file-viewer-render');
  assert.equal((await f.viewer()).source, true, label + ': source toggle');
  await f.page.evaluate(function () { var tab = document.querySelector('.file-instance .tool-pane-title') || document.querySelector('.file-viewer-tab'); tab.click(); });
  await f.page.waitForTimeout(400);
  assert.equal((await f.viewer()).source, true, label + ': explicit source choice survives tab refocus');
  if (await f.page.locator('.file-instance').count()) await f.page.locator('.file-instance [data-action=preview]').click(); else await f.page.click('#file-viewer-render');
  assert.equal((await f.viewer()).rendered, true);
  await f.closeViewer();
  await f.clickIn(scope, 'Open file docs/SYNTH.ko.md', 'markdown');
  assert.equal((await f.viewer()).rendered, true, label + ': relative link opens rendered first');
  await f.closeViewer();
  await f.clickIn(scope, 'Open file docs/SYNTH.ko.md:3', 'source');
  assert.equal((await f.viewer()).source, true, label + ': explicit line target opens source rows');
  await f.closeViewer();
  await f.clickIn(scope, 'Open file plain.js', 'code');
  state = await f.viewer();
  assert.deepEqual([state.rendered, state.source, state.toggle], [false, false, false], label + ': non-Markdown unchanged');
  await f.closeViewer();
}

var links = 'Absolute [a](ABS) relative [rel](docs/SYNTH.ko.md) lined [lined](docs/SYNTH.ko.md:3) code [plain.js](plain.js)';

test('real chat links open in a legacy split pair', { timeout: 120000 }, async function (t) {
  var f = await startFixture(t, {});
  var group = f.data.groups[0];
  await f.page.evaluate(async function (g) { (await import('/p/reuse/modules/split-view.js')).openGroup(g); }, group);
  await f.panesReady(2, [group.pair.driverId, group.pair.workerId]);
  await f.say(group.pair.driverId, links.replace('ABS', f.absolute));
  await f.say(group.pair.workerId, 'Worker [w](' + f.absolute + ')');
  await exerciseLinks(f, f.pane(group.pair.driverId), 'driver pane');
  await f.clickIn(f.pane(group.pair.workerId), 'Open file ' + f.absolute, 'markdown');
  assert.equal((await f.viewer()).rendered, true, 'worker pane link');
  assert.deepEqual(f.errors, []);
});

test('real chat links open from the ordinary single-session view', { timeout: 120000 }, async function (t) {
  var f = await startFixture(t, {});
  var id = f.data.standaloneId;
  await f.page.request.post(f.origin + '/fixture/switch?id=' + id);
  await f.page.waitForFunction(function (id) { return document.getElementById('messages').dataset.controlSessionId === String(id) || !!document.querySelector('.split-pane-frame[data-session-id="' + id + '"]'); }, id, { timeout: 30000 });
  await f.say(id, links.replace('ABS', f.absolute));
  var paned = await f.page.locator('.split-pane-frame[data-session-id="' + id + '"]').count();
  await exerciseLinks(f, paned ? f.pane(id) : f.page.locator('#messages'), paned ? 'single-session pane' : 'native shell');
  assert.deepEqual(f.errors, []);
});

test('saved-view pane links open, and stale or forged pane messages are rejected', { timeout: 120000, skip: !hasSavedViews && 'requires the saved-view (PR599) UI' }, async function (t) {
  var f = await startFixture(t, { CLAY_PANE_PRESENTATION: '1' });
  var view = f.data.restoreView;
  assert.ok(view, 'fixture created a real saved view');
  await f.say(view.pair.driverId, links.replace('ABS', f.absolute));
  await f.page.locator('[data-split-view-id="' + view.id + '"] .split-view-group-open').first().click();
  await f.panesReady(view.members.length, view.members);
  var pane = f.pane(view.pair.driverId);
  // Forged and stale messages from the live pane window must not open anything.
  async function forge(patch) {
    await f.page.evaluate(function (args) {
      var frame = document.querySelector('.split-pane-frame[data-session-id="' + args.id + '"]');
      var binding = frame.clayPane.identity().binding;
      var message = Object.assign({ type: 'clay-pane-open-file', paneBinding: binding, path: 'docs/SYNTH.ko.md', line: null, column: null,
        projectSlug: frame.dataset.projectSlug, sessionId: String(args.id) }, args.patch);
      frame.contentWindow.parent.postMessage(message, location.origin);
    }, { id: view.pair.driverId, patch: patch });
    await f.page.waitForTimeout(500);
    assert.equal((await f.viewer()).hidden, true, 'rejected: ' + JSON.stringify(Object.keys(patch)));
  }
  await forge({ paneBinding: null });
  await forge({ paneBinding: { requestId: 'stale', generation: -1 } });
  await forge({ sessionId: String(view.pair.driverId + 100) });
  await forge({ projectSlug: 'other-project' });
  await forge({ path: '' });
  await f.page.evaluate(function (id) { document.querySelector('.split-pane-frame[data-session-id="' + id + '"]').clayPane.suspend(); }, view.pair.driverId);
  await forge({});
  await f.page.reload({ waitUntil: 'domcontentloaded' });
  await f.panesReady(view.members.length, view.members);
  await exerciseLinks(f, f.pane(view.pair.driverId), 'saved view after reload');
  assert.deepEqual(f.errors, []);
});