mirror of
https://github.com/sinanyuntu/trade-message-center.git
synced 2026-09-17 13:22:11 +08:00
192 lines
6.8 KiB
JavaScript
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");
|
|
});
|