Files
trade-message-center/apps/chrome-extension/test/onetalk-rendered-card-observer.test.js
T

192 lines
6.8 KiB
JavaScript

// 验证 DOM card observer 只扫描渲染卡片并在页面关闭时释放监听。
import assert from "node:assert/strict";
import test from "node:test";
import { installOneTalkRenderedCardObserver } from "../src/onetalk/main-page/card-observer/entry.ts";
const cardFor = () => {
const wrapper = {
getAttribute: () =>
JSON.stringify({ messageId: "message-1", conversationCode: "conversation-1" }),
};
return {
textNodes: [{ nodeValue: "Order" }, { nodeValue: "Widget" }],
getAttribute: () =>
JSON.stringify({
messageId: "message-1",
conversationCode: "conversation-1",
cardType: 54,
messageType: 1,
}),
closest: () => wrapper,
matches: (selector) =>
selector === ".session-rich-content.card" || selector === "[data-expinfo]",
querySelector: (selector) => {
if (selector === "[data-expinfo]") return card;
return null;
},
querySelectorAll: (selector) =>
selector === "img[src]"
? [
{
getAttribute: (name) =>
name === "src" ? "https://img.alicdn.com/card.jpg" : null,
},
]
: [],
};
};
const card = cardFor();
const fixtureNode = ({ text = null, imageUrl = null, classNames = [] } = {}) => {
const value = {
childNodes: [],
textContent: text,
imageUrl,
classNames: new Set(classNames),
parentElement: null,
getAttribute(name) {
return name === "src" ? this.imageUrl : null;
},
querySelectorAll: (selector) => {
if (selector.startsWith(".")) return [];
assert.equal(selector, "img[src]");
return [];
},
};
return value;
};
const validCardForMutation = () => {
const root = fixtureNode();
const labelNode = fixtureNode({ text: "订单状态", classNames: ["TextView"] });
const statusNode = fixtureNode({ text: "Pending", classNames: ["TextView"] });
const row = fixtureNode();
row.childNodes.push(labelNode, statusNode);
labelNode.parentElement = row;
statusNode.parentElement = row;
root.childNodes.push(row);
row.parentElement = root;
const wrapper = {
getAttribute: () =>
JSON.stringify({ messageId: "message-1", conversationCode: "conversation-1" }),
};
const card = {
closest: () => wrapper,
matches: (selector) =>
selector === ".session-rich-content.card" || selector === "[data-expinfo]",
getAttribute: (name) =>
name === "data-expinfo"
? JSON.stringify({ messageId: "message-1", conversationCode: "conversation-1" })
: null,
querySelector: (selector) => {
if (selector === "[data-expinfo]") return card;
if (selector === ".tpl-wrapper") return root;
return null;
},
};
root.parentElement = card;
statusNode.parentElement = card;
return { card, statusNode };
};
test("rejects unrecognized cards while observing mounts without timers or wrapper scans", () => {
const observers = [];
const listeners = new Map();
const documentElement = {};
const page = {
location: { href: "https://onetalk.alibaba.com/message/default.htm" },
document: {
documentElement,
querySelectorAll: (selector) => {
if (selector === ".contact-item-container.selected[data-cid]")
return [{ getAttribute: () => "conversation-1" }];
if (selector === ".session-rich-content.card") return [card];
assert.fail(`unexpected selector: ${selector}`);
},
createTreeWalker: (target) => {
let index = 0;
return { nextNode: () => target.textNodes[index++] ?? null };
},
},
MutationObserver: class {
constructor(callback) {
this.callback = callback;
this.options = null;
observers.push(this);
}
observe(target, options) {
assert.equal(target, documentElement);
this.options = options;
}
disconnect() {
this.disconnected = true;
}
},
addEventListener: (type, listener) => listeners.set(type, listener),
};
const observed = [];
installOneTalkRenderedCardObserver(page, (observations) => observed.push(...observations));
assert.equal(observed.length, 0);
assert.deepEqual(observers[0].options, {
childList: true,
subtree: true,
characterData: true,
attributes: true,
attributeFilter: ["data-expinfo", "src", "style"],
});
observers[0].callback([{ target: card, addedNodes: [] }]);
assert.equal(
observed.length,
0,
"an unrecognized structure never publishes a fallback snapshot",
);
listeners.get("pagehide")();
assert.equal(observers[0].disconnected, true);
});
test("re-reads a recognized DOM field when its text changes in place", () => {
const observers = [];
const listeners = new Map();
const documentElement = {};
const fixture = validCardForMutation();
const page = {
location: { href: "https://onetalk.alibaba.com/message/default.htm" },
getComputedStyle: () => ({ display: "block", visibility: "visible" }),
document: {
documentElement,
querySelectorAll: (selector) => {
if (selector === ".contact-item-container.selected[data-cid]")
return [{ getAttribute: () => "conversation-1" }];
if (selector === ".session-rich-content.card") return [fixture.card];
assert.fail(`unexpected selector: ${selector}`);
},
},
MutationObserver: class {
constructor(callback) {
this.callback = callback;
this.options = null;
observers.push(this);
}
observe(target, options) {
assert.equal(target, documentElement);
this.options = options;
}
disconnect() {}
},
addEventListener: (type, listener) => listeners.set(type, listener),
};
const observed = [];
installOneTalkRenderedCardObserver(page, (observations) => observed.push(...observations));
assert.equal(observed.length, 1);
fixture.statusNode.textContent = "Updated status";
observers[0].callback([{ target: fixture.statusNode, addedNodes: [] }]);
assert.equal(observed.length, 2);
assert.equal(observed[1].content.status, "Updated status");
});