test/ask-user-rendering.test.js
Rama main · réplica de lectura del repositorio original.
var test = require("node:test");
var assert = require("node:assert/strict");
var path = require("node:path");
var pathToFileURL = require("node:url").pathToFileURL;
function FakeElement(tag) {
this.tagName = String(tag).toUpperCase();
this.children = [];
this.parentNode = null;
this.dataset = {};
this.className = "";
this.listeners = {};
this.textContent = "";
this.value = "";
this.disabled = false;
var self = this;
this.classList = {
add: function () { for (var i = 0; i < arguments.length; i++) if (!self.classList.contains(arguments[i])) self.className += (self.className ? " " : "") + arguments[i]; },
remove: function (name) { self.className = self.className.split(" ").filter(function (item) { return item && item !== name; }).join(" "); },
contains: function (name) { return (" " + self.className + " ").indexOf(" " + name + " ") !== -1; },
};
}
FakeElement.prototype.appendChild = function (child) { child.parentNode = this; this.children.push(child); return child; };
FakeElement.prototype.insertBefore = function (child, before) { var i = this.children.indexOf(before); if (i < 0) return this.appendChild(child); child.parentNode = this; this.children.splice(i, 0, child); return child; };
FakeElement.prototype.addEventListener = function (name, fn) { this.listeners[name] = fn; };
FakeElement.prototype.click = function () { if (this.listeners.click) this.listeners.click({ currentTarget: this }); };
FakeElement.prototype.querySelectorAll = function (selector) {
var result = [];
function matches(node) {
if (selector === ".ask-user-container") return node.classList.contains("ask-user-container");
if (selector === ".ask-user-question") return node.classList.contains("ask-user-question");
if (selector === ".ask-user-option") return node.classList.contains("ask-user-option");
if (selector === ".ask-user-submit") return node.classList.contains("ask-user-submit");
if (selector === ".ask-user-answer-summary") return node.classList.contains("ask-user-answer-summary");
if (selector === ".ask-user-other input") return node.tagName === "INPUT" && node.parentNode && node.parentNode.classList.contains("ask-user-other");
if (selector === ".option-label") return node.classList.contains("option-label");
return false;
}
function visit(node) { if (matches(node)) result.push(node); for (var i = 0; i < node.children.length; i++) visit(node.children[i]); }
visit(this);
return result;
};
FakeElement.prototype.querySelector = function (selector) {
if (selector === ".option-label" && this._innerHTML && !this.querySelectorAll(selector).length) {
var label = new FakeElement("div"); label.className = "option-label"; this.appendChild(label);
}
return this.querySelectorAll(selector)[0] || null;
};
Object.defineProperty(FakeElement.prototype, "innerHTML", { get: function () { return this._innerHTML || ""; }, set: function (value) { this._innerHTML = String(value); } });
test("AskUserQuestion rendering is idempotent per session request and submits once", async function () {
var originalDocument = global.document;
var originalWindow = global.window;
var originalLocalStorage = global.localStorage;
var originalMarked = global.marked;
var originalMermaid = global.mermaid;
var originalPurifier = global.DOMPurify;
var messages = new FakeElement("div");
var secondMessages = new FakeElement("div");
var input = new FakeElement("textarea");
var sent = [];
var context;
global.document = {
addEventListener: function () {},
body: new FakeElement("body"),
createElement: function (tag) { return new FakeElement(tag); },
querySelectorAll: function (selector) { return messages.querySelectorAll(selector).concat(secondMessages.querySelectorAll(selector)); },
getElementById: function () { return null; },
};
global.window = { addEventListener: function () {}, removeEventListener: function () {} };
global.localStorage = { getItem: function () { return null; }, setItem: function () {}, removeItem: function () {} };
global.marked = { use: function () {}, parse: function (value) { return value; } };
global.mermaid = { initialize: function () {} };
global.DOMPurify = { sanitize: function (value) { return value; } };
try {
var storeModule = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/store.js")).href);
storeModule.createStore({ activeSessionId: 101, currentVendor: "claude" });
var tools = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/tools.js")).href);
context = {
finalizeAssistantBlock: function () {}, stopUrgentBlink: function () {}, setActivity: function () {},
addToMessages: function (el) { context.messagesEl.appendChild(el); }, scrollToBottom: function () {},
isMateDm: function () { return false; }, inputEl: input, ws: { send: function (value) { sent.push(JSON.parse(value)); } }, connected: true,
messagesEl: messages,
};
tools.initTools(context);
tools.resetToolState();
var question = { questions: [{ question: "Which outcome?", options: [{ label: "Ship" }, { label: "Wait" }] }] };
tools.renderAskUserQuestion("ask-101", question);
var first = messages.children[0];
var freeform = first.querySelector(".ask-user-other input");
freeform.value = "Keep the current plan";
freeform.listeners.input();
var reordered = { questions: [{ options: [{ label: "Ship" }, { label: "Wait" }], question: "Which outcome?" }] };
tools.renderAskUserQuestion("ask-101", reordered);
assert.strictEqual(messages.children[0], first);
assert.equal(messages.querySelectorAll(".ask-user-container").length, 1);
assert.equal(freeform.value, "Keep the current plan");
first.querySelector(".ask-user-submit").click();
first.querySelector(".ask-user-submit").click();
assert.deepEqual(sent, [{ type: "ask_user_response", toolId: "ask-101", answers: { 0: "Keep the current plan" } }]);
storeModule.store.set({ activeSessionId: 202 });
context.messagesEl = secondMessages;
tools.renderAskUserQuestion("ask-101", question);
var next = secondMessages.children[0];
assert.notStrictEqual(next, first);
assert.equal(messages.querySelectorAll(".ask-user-container").length, 1);
assert.equal(secondMessages.querySelectorAll(".ask-user-container").length, 1);
tools.markAskUserAnswered("ask-101", { 0: "Wait" });
tools.renderAskUserQuestion("ask-101", question);
assert.equal(next.classList.contains("answered"), true);
assert.equal(secondMessages.querySelectorAll(".ask-user-container").length, 1);
storeModule.store.set({ activeSessionId: 303, askUserRequests: {} });
context.messagesEl = new FakeElement("div");
tools.renderAskUserQuestion("ask-101", question);
tools.renderAskUserQuestion("ask-102", question);
assert.equal(context.messagesEl.querySelectorAll(".ask-user-container").length, 2);
tools.renderAskUserQuestion('mcp-consent', { source: 'mcp-import', questions: [{ question: 'Import disabled copy?', allowOther: false, options: [{ label: 'Keep unchanged' }, { label: 'Import disabled copy' }] }] });
var consent = context.messagesEl.querySelectorAll('.ask-user-container').find(function (card) { return card.dataset.toolId === 'mcp-consent'; });
assert.equal(consent.classList.contains('mcp-import-consent'), true);
assert.equal(consent.querySelector('.ask-user-other input'), null);
var beforeConsent = sent.length;
consent.querySelector('.ask-user-submit').click(); assert.equal(sent.length, beforeConsent);
consent.querySelectorAll('.ask-user-option')[1].click(); consent.querySelector('.ask-user-submit').click();
assert.equal(sent[sent.length - 1].toolId, 'mcp-consent'); assert.equal(sent[sent.length - 1].answers[0], 'Import disabled copy');
} finally {
global.document = originalDocument;
global.window = originalWindow;
global.localStorage = originalLocalStorage;
global.marked = originalMarked;
global.mermaid = originalMermaid;
global.DOMPurify = originalPurifier;
}
});