var test = require("node:test"); var assert = require("node:assert/strict"); var fs = require("node:fs"); var path = require("node:path"); var pathToFileURL = require("node:url").pathToFileURL; function FakeElement(tag) { this.tagName = tag.toUpperCase(); this.children = []; this.attributes = {}; this.className = ""; this.dataset = {}; this.textContent = ""; this.src = ""; 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]; } }, }; } 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.querySelector = function (selector) { var className = selector.charAt(0) === "." ? selector.slice(1) : null; var nodes = flatten(this); for (var i = 1; i < nodes.length; i++) { if (className && (" " + nodes[i].className + " ").indexOf(" " + className + " ") !== -1) return nodes[i]; } return null; }; function flatten(root) { var result = [root]; for (var i = 0; i < root.children.length; i++) result = result.concat(flatten(root.children[i])); return result; } test("ordinary Home bubbles use exact restored Mate and current user identity safely", async function () { var originalDocument = global.document; var originalWindow = global.window; var originalMarked = global.marked; var originalMermaid = global.mermaid; var originalPurify = global.DOMPurify; var originalLocalStorage = global.localStorage; global.document = { body: new FakeElement("body"), createElement: function (tag) { return new FakeElement(tag); }, getElementById: function () { return null; }, querySelector: function () { return null; }, querySelectorAll: function () { return []; }, addEventListener: function () {}, removeEventListener: function () {}, }; global.document.body.dataset = {}; global.window = { addEventListener: function () {}, removeEventListener: function () {}, matchMedia: function () { return { matches: false, addEventListener: function () {} }; } }; global.marked = { use: function () {}, parse: function (value) { return value; } }; global.mermaid = { initialize: function () {} }; global.DOMPurify = { sanitize: function (value) { return value; } }; global.localStorage = { getItem: function () { return null; }, setItem: function () {}, removeItem: function () {} }; try { var root = path.join(__dirname, ".."); var storeModule = await import(pathToFileURL(path.join(root, "lib/public/modules/store.js")).href); storeModule.createStore({ homeChatMateId: "restored-mate", myUserId: "user-a", cachedAllUsers: [{ id: "user-a", profile: { avatarCustom: "data:user-a" } }], cachedMatesList: [{ id: "focused-elsewhere", profile: { displayName: "Other", avatarCustom: "data:other" } }], }); var identity = await import(pathToFileURL(path.join(root, "lib/public/modules/home-chat-identity.js")).href); var restoredMate = { id: "restored-mate", profile: { displayName: "", avatarCustom: "data:restored" } }; var mate = identity.createHomeOrdinaryBubble({ role: "assistant", text: "Hello" }, restoredMate, restoredMate.profile.displayName, "09:41"); var mateAvatar = mate.querySelector(".dm-bubble-avatar"); var mateName = mate.querySelector(".dm-bubble-name"); assert.equal(mateAvatar.src, "data:restored"); assert.equal(mateAvatar.attributes.alt, undefined); assert.equal(mateAvatar.alt, ""); assert.equal(mateName.textContent, ""); assert.equal(mateName.children.length, 0); assert.equal(mate.attributes.role, "article"); assert.equal(mate.attributes["aria-label"], "Message from "); assert.match(mate.className, /home-chat-ordinary-mate/); var user = identity.createHomeOrdinaryBubble({ role: "user", text: "My answer" }, restoredMate, restoredMate.profile.displayName, "09:42"); assert.equal(user.querySelector(".dm-bubble-avatar"), null); assert.equal(user.querySelector(".dm-bubble-name"), null); assert.equal(user.querySelector(".dm-bubble-header"), null); assert.equal(user.attributes["aria-label"], "Message from You"); assert.equal(user.querySelector(".bubble").children[0].textContent, "My answer"); assert.equal(user.children.length, 1); assert.equal(user.children[0].children[0], user.querySelector(".bubble")); } finally { global.document = originalDocument; global.window = originalWindow; global.marked = originalMarked; global.mermaid = originalMermaid; global.DOMPurify = originalPurify; global.localStorage = originalLocalStorage; } }); test("ordinary pre-delta streaming keeps the Mate identity beside its activity", async function () { var originalDocument = global.document; global.document = { createElement: function (tag) { return new FakeElement(tag); } }; try { var root = path.join(__dirname, ".."); var identity = await import(pathToFileURL(path.join(root, "lib/public/modules/home-chat-identity.js")).href); var row = identity.createHomeOrdinaryTyping({ profile: { avatarCustom: "data:clay" } }, "Clay"); assert.equal(row.querySelector(".dm-bubble-avatar").src, "data:clay"); assert.equal(row.querySelector(".dm-bubble-name").textContent, "Clay"); assert.equal(row.querySelector(".home-chat-typing").attributes["aria-label"], "Clay is responding"); assert.equal(row.querySelector(".home-chat-typing").children.length, 3); assert.match(row.className, /home-chat-ordinary-typing/); } finally { global.document = originalDocument; } }); test("ordinary identity styles expose shared headers without affecting Debate-owned cards", function () { var root = path.join(__dirname, ".."); var css = fs.readFileSync(path.join(root, "lib/public/css/home-hub.css"), "utf8"); var chat = fs.readFileSync(path.join(root, "lib/public/modules/home-mate-chat.js"), "utf8"); var identity = fs.readFileSync(path.join(root, "lib/public/modules/home-chat-identity.js"), "utf8"); assert.match(identity, /mateAvatarUrl\(mate, 34\)/); assert.doesNotMatch(identity, /userAvatarUrl|currentUser/); assert.match(identity, /var row = isUser \? createUserBubble\(\{[\s\S]*text: message\.text \|\| "",[\s\S]*\}\) : createAssistantBubble/); assert.match(identity, /row\.setAttribute\("role", "article"\)/); assert.match(css, /\.home-chat-ordinary-message > \.dm-bubble-avatar \{[\s\S]*display: block;[\s\S]*width: 34px;[\s\S]*height: 34px;/); assert.match(css, /\.home-chat-ordinary-message \.dm-bubble-header \{[\s\S]*display: flex;/); assert.match(css, /\.home-chat-ordinary-message \{[\s\S]*grid-template-columns: 34px minmax\(0, 1fr\)/); assert.match(css, /\.home-chat-ordinary-user \{[\s\S]*display: flex;[\s\S]*justify-content: flex-end;/); assert.match(css, /\.home-chat-ordinary-user > \.dm-bubble-content \{[\s\S]*align-items: flex-end;/); assert.doesNotMatch(css, /home-chat-ordinary-user[^}]*grid-template|home-chat-ordinary-user > \.dm-bubble-avatar|home-chat-ordinary-user \.dm-bubble-header/); assert.doesNotMatch(css, /\.home-mate-chat-transcript\.home-chat-bubble-layout \.dm-bubble-avatar,\s*\.home-mate-chat-transcript\.home-chat-bubble-layout \.dm-bubble-header\s*\{\s*display:\s*none/); assert.match(chat, /if \(message\.role === "proposal" \|\| message\.role === "question"\) return createHomeDebateTranscriptCard/); assert.match(chat, /if \(\["debate_header", "debate_turn", "debate_user", "debate_tool_decision"\][\s\S]*return createHomeDebateLiveCard/); assert.match(chat, /createHomeOrdinaryBubble\(message, mate, mateName, timeText\)/); });