Ctrl K
ComunidadGuías…

test/home-debate-controls.test.js

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

Archivo original ↗Preguntar a KiBot
var assert = require("node:assert/strict");
var fs = require("node:fs");
var path = require("node:path");
var test = require("node:test");
var pathToFileURL = require("node:url").pathToFileURL;

function FakeElement(tag) {
  this.tagName = tag.toUpperCase();
  this.children = [];
  this.attributes = {};
  this.listeners = {};
  this.className = "";
  this.textContent = "";
  this.value = "";
  this.disabled = false;
  this.hidden = false;
  this.isConnected = true;
  this.focusCalls = [];
  var element = this;
  this.classList = {
    add: function () {
      for (var i = 0; i < arguments.length; i++) if ((" " + element.className + " ").indexOf(" " + arguments[i] + " ") === -1) element.className += (element.className ? " " : "") + arguments[i];
    },
  };
  Object.defineProperty(this, "innerHTML", {
    get: function () { return ""; },
    set: function () { element.children = []; },
  });
}
FakeElement.prototype.appendChild = function (child) { this.children.push(child); child.parentNode = this; return child; };
FakeElement.prototype.setAttribute = function (name, value) { this.attributes[name] = String(value); };
FakeElement.prototype.addEventListener = function (name, handler) { this.listeners[name] = handler; };
FakeElement.prototype.click = function () { if (this.listeners.click) this.listeners.click({ currentTarget: this }); };
FakeElement.prototype.focus = function (options) { this.focusCalls.push(options); };

function flatten(root) {
  var result = [root];
  for (var i = 0; i < root.children.length; i++) result = result.concat(flatten(root.children[i]));
  return result;
}

function findText(root, text) {
  return flatten(root).find(function (node) { return node.textContent === text; });
}

function fixtureDocument() {
  var elements = {
    "home-debate-controls-slot": new FakeElement("div"),
    "home-mate-chat-composer": new FakeElement("div"),
    "home-mate-chat-session-model": new FakeElement("div"),
  };
  var ordinaryInput = new FakeElement("textarea");
  var ordinarySend = new FakeElement("button");
  elements["home-mate-chat-composer"].appendChild(ordinaryInput);
  elements["home-mate-chat-composer"].appendChild(ordinarySend);
  return {
    elements: elements,
    ordinaryInput: ordinaryInput,
    ordinarySend: ordinarySend,
    document: {
      createElement: function (tag) { return new FakeElement(tag); },
      getElementById: function (id) { return elements[id] || null; },
    },
  };
}

function liveMessages(overrides) {
  var header = Object.assign({ role: "debate_header", phase: "live", topic: "Housing", round: 2, handRaised: false, interaction: null }, overrides || {});
  return [header, { role: "debate_turn", turnId: "turn-2", status: "active", mateName: "Panel", speakerRole: "Analyst", round: 2, text: "Opening", activity: "Speaking" }];
}

test("live Home debate replaces the ordinary composer and routes exact default controls", async function () {
  var originalDocument = global.document;
  var originalFrame = global.requestAnimationFrame;
  var fixture = fixtureDocument();
  var sent = [];
  global.document = fixture.document;
  global.requestAnimationFrame = function (callback) { callback(); return 1; };
  try {
    var storeModule = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/store.js")).href);
    var wsModule = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/ws-ref.js")).href);
    var controls = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-controls.js")).href);
    storeModule.createStore({ homeChatMateId: "builtin:clay", homeChatSessionId: "cli-live" });
    wsModule.setWs({ readyState: 1, send: function (data) { sent.push(JSON.parse(data)); } });
    assert.equal(controls.renderHomeDebateControls([], "request-live"), false);
    assert.equal(fixture.elements["home-mate-chat-composer"].hidden, false);
    assert.equal(fixture.elements["home-mate-chat-session-model"].hidden, false);

    assert.equal(controls.renderHomeDebateControls(liveMessages(), "request-live"), true);
    var slot = fixture.elements["home-debate-controls-slot"];
    assert.equal(fixture.elements["home-mate-chat-composer"].hidden, true);
    assert.equal(fixture.ordinaryInput.parentNode.hidden, true);
    assert.equal(fixture.ordinarySend.parentNode.hidden, true);
    assert.equal(fixture.elements["home-mate-chat-session-model"].hidden, true);
    assert.equal(slot.hidden, false);
    assert.equal(slot.children[0].attributes.role, "toolbar");
    assert.equal(slot.children[0].attributes["aria-label"], "Live debate controls");
    assert.equal(findText(slot, "Panel is speaking").attributes["aria-live"], "polite");
    assert.equal(flatten(slot).filter(function (node) { return node.tagName === "I"; }).length, 3);

    var raise = findText(slot, "Raise hand");
    var stop = findText(slot, "Stop debate");
    raise.click();
    stop.click();
    assert.deepEqual(sent, [
      { type: "home_debate_control", action: "hand_raise", mateId: "builtin:clay", sessionId: "cli-live", requestId: "request-live", text: "", response: null },
      { type: "home_debate_control", action: "stop", mateId: "builtin:clay", sessionId: "cli-live", requestId: "request-live", text: "", response: null },
    ]);
    controls.renderHomeDebateControls(liveMessages({ stopping: true }), "request-live");
    assert.equal(findText(slot, "Stopping after Panel finishes").attributes["aria-live"], "polite");
    var cancel = findText(slot, "Cancel stop");
    assert.match(cancel.className, /home-debate-control-secondary/);
    cancel.click();
    assert.equal(sent[2].action, "cancel_stop");
    assert.equal(findText(slot, "Cancelling…").disabled, true);
    controls.renderHomeDebateControls(liveMessages({ stopping: false }), "request-live");
    assert.ok(findText(slot, "Stop debate"));
    controls.renderHomeDebateControls(liveMessages({ handRaised: true }), "request-live");
    assert.equal(findText(slot, "Hand raised").disabled, true);
    assert.equal(findText(slot, "Hand raised").attributes["aria-pressed"], "true");
  } finally {
    global.document = originalDocument;
    global.requestAnimationFrame = originalFrame;
  }
});

test("user-floor and conclude controls preserve IME, caret, focus, and occupy one stable slot", async function () {
  var originalDocument = global.document;
  var originalFrame = global.requestAnimationFrame;
  var fixture = fixtureDocument();
  var sent = [];
  global.document = fixture.document;
  global.requestAnimationFrame = function (callback) { callback(); return 1; };
  try {
    var storeModule = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/store.js")).href);
    var wsModule = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/ws-ref.js")).href);
    var controls = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-controls.js")).href);
    storeModule.store.set({ homeChatMateId: "builtin:clay", homeChatSessionId: "local:31" });
    wsModule.setWs({ readyState: 1, send: function (data) { sent.push(JSON.parse(data)); } });
    var floorState = liveMessages({ interaction: "user_floor" });
    controls.renderHomeDebateControls(floorState, "floor-request");
    var slot = fixture.elements["home-debate-controls-slot"];
    var input = flatten(slot).find(function (node) { return node.tagName === "TEXTAREA"; });
    assert.deepEqual(input.focusCalls, [{ preventScroll: true }]);
    input.value = "한글 의견";
    input.selectionStart = 3;
    input.selectionEnd = 3;
    input.listeners.input();
    input.listeners.compositionstart();
    var prevented = 0;
    input.listeners.keydown({ key: "Enter", shiftKey: false, isComposing: false, keyCode: 229, preventDefault: function () { prevented++; } });
    assert.equal(sent.length, 0);
    assert.equal(prevented, 0);
    controls.renderHomeDebateControls(floorState, "floor-request");
    var stableInput = flatten(slot).find(function (node) { return node.tagName === "TEXTAREA"; });
    assert.equal(stableInput, input);
    assert.equal(stableInput.value, "한글 의견");
    assert.equal(stableInput.selectionStart, 3);
    assert.equal(stableInput.focusCalls.length, 1);
    input.listeners.compositionend();
    input.listeners.keydown({ key: "Enter", shiftKey: false, isComposing: false, keyCode: 13, preventDefault: function () { prevented++; } });
    assert.equal(prevented, 1);
    assert.equal(sent[0].action, "user_floor");
    assert.equal(sent[0].text, "한글 의견");
    assert.equal(findText(slot, "Sending…").disabled, true);

    controls.renderHomeDebateControls(liveMessages(), "conclude-request");
    controls.renderHomeDebateControls(liveMessages({ interaction: "conclude" }), "conclude-request");
    var concludeInput = flatten(slot).find(function (node) { return node.tagName === "TEXTAREA"; });
    concludeInput.value = "Address implementation risk";
    findText(slot, "Continue").click();
    assert.equal(sent[1].action, "conclude");
    assert.equal(sent[1].response, "continue");
    assert.equal(sent[1].text, "Address implementation risk");

    controls.renderHomeDebateControls(liveMessages(), "end-request");
    controls.renderHomeDebateControls(liveMessages({ interaction: "conclude" }), "end-request");
    findText(slot, "End debate").click();
    assert.equal(sent[2].action, "conclude");
    assert.equal(sent[2].response, "end");
  } finally {
    global.document = originalDocument;
    global.requestAnimationFrame = originalFrame;
  }
});

test("terminal Home debate remains special and offers resume plus fresh debate actions", async function () {
  var originalDocument = global.document;
  var fixture = fixtureDocument();
  var starts = 0;
  var sent = [];
  global.document = fixture.document;
  try {
    var storeModule = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/store.js")).href);
    var wsModule = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/ws-ref.js")).href);
    var controls = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-controls.js")).href);
    storeModule.store.set({ homeChatMateId: "builtin:clay", homeChatSessionId: "cli-ended" });
    wsModule.setWs({ readyState: 1, send: function (data) { sent.push(JSON.parse(data)); } });
    controls.renderHomeDebateControls([{ role: "debate_header", phase: "interrupted", reason: "interrupted" }], "terminal", function () { starts++; });
    var slot = fixture.elements["home-debate-controls-slot"];
    assert.equal(fixture.elements["home-mate-chat-composer"].hidden, true);
    assert.equal(fixture.elements["home-mate-chat-session-model"].hidden, true);
    assert.equal(flatten(slot).some(function (node) { return node.tagName === "TEXTAREA"; }), false);
    assert.equal(findText(slot, "Debate interrupted when Clay restarted").attributes.role, "status");
    findText(slot, "Resume debate").click();
    assert.deepEqual(sent[0], { type: "home_debate_control", action: "resume", mateId: "builtin:clay", sessionId: "cli-ended", requestId: "terminal", text: "", response: null });
    assert.equal(findText(slot, "Resuming…").disabled, true);
    controls.renderHomeDebateControls([{ role: "debate_header", phase: "ended", reason: "natural" }], "terminal-new", function () { starts++; });
    findText(slot, "New debate").click();
    assert.equal(starts, 1);
    controls.renderHomeDebateControls([], "normal");
    assert.equal(fixture.elements["home-mate-chat-composer"].hidden, false);
    assert.equal(fixture.elements["home-mate-chat-session-model"].hidden, false);
    assert.equal(slot.hidden, true);
  } finally {
    global.document = originalDocument;
  }
});

test("project chat restores debate live controls without a Home composer", function () {
  var root = path.join(__dirname, "..");
  var index = fs.readFileSync(path.join(root, "lib/public/index.html"), "utf8");
  var workspace = fs.readFileSync(path.join(root, "lib/public/modules/project-debate-workspace.js"), "utf8");
  var live = fs.readFileSync(path.join(root, "lib/public/modules/home-debate-live.js"), "utf8");
  var css = fs.readFileSync(path.join(root, "lib/public/css/home-debate-live.css"), "utf8");
  assert.doesNotMatch(index, /id="home-mate-chat-composer"|id="home-debate-controls-slot"/);
  assert.match(workspace, /home-debate-live\.js/);
  assert.match(workspace, /home-debate-planning\.js/);
  assert.match(workspace, /restore/);
  assert.doesNotMatch(live, /home-debate-live-controls|home-debate-live-interaction|Raise hand|End debate/);
  assert.match(css, /\.home-debate-control-surface[\s\S]*var\(--bg\)[\s\S]*var\(--border-subtle\)/);
  assert.match(css, /@media \(max-width: 600px\)[\s\S]*var\(--safe-bottom/);
  assert.match(css, /home-debate-control-terminal \.home-debate-control-actions[\s\S]*margin-left:\s*auto/);
  assert.match(css, /prefers-reduced-motion: reduce[\s\S]*home-debate-control-dots/);
  assert.doesNotMatch(css, /#[0-9a-f]{3,8}|serif|linear-gradient/i);
});