
























@@ -1,13 +1,74 @@
1+/* @vitest-environment jsdom */
2+13import { readFileSync } from "node:fs";
2-import { describe, expect, it } from "vitest";
4+import path from "node:path";
5+import { beforeEach, describe, expect, it, vi } from "vitest";
6+7+const disableAutoStartKey = Symbol.for("openclaw.diffs.disableAutoStart");
8+(globalThis as typeof globalThis & Record<symbol, unknown>)[disableAutoStartKey] = true;
39410const VIEWER_CLIENT_SRC = readFileSync(
5-new URL("./viewer-client.ts", import.meta.url),
11+path.join(process.cwd(), "extensions/diffs/src/viewer-client.ts"),
612"utf8",
713);
814915const XSS_PATTERNS = ["onerror", "<script", "onclick", "javascript:", "onload"];
101617+const {
18+ fileDiffHydrateMock,
19+ fileDiffRerenderMock,
20+ fileDiffSetOptionsMock,
21+ preloadHighlighterMock,
22+} = vi.hoisted(() => ({
23+fileDiffHydrateMock: vi.fn(),
24+fileDiffRerenderMock: vi.fn(),
25+fileDiffSetOptionsMock: vi.fn(),
26+preloadHighlighterMock: vi.fn(async () => undefined),
27+}));
28+29+vi.mock("@pierre/diffs", () => ({
30+FileDiff: class {
31+hydrate(params: unknown) {
32+return fileDiffHydrateMock(params);
33+}
34+rerender() {
35+return fileDiffRerenderMock();
36+}
37+setOptions(params: unknown) {
38+return fileDiffSetOptionsMock(params);
39+}
40+},
41+preloadHighlighter: preloadHighlighterMock,
42+}));
43+44+const viewerPayload = JSON.stringify({
45+prerenderedHTML: "<div>diff</div>",
46+options: {
47+theme: { light: "pierre-light", dark: "pierre-dark" },
48+diffStyle: "unified",
49+diffIndicators: "bars",
50+disableLineNumbers: false,
51+expandUnchanged: false,
52+themeType: "dark",
53+backgroundEnabled: true,
54+overflow: "wrap",
55+unsafeCSS: "",
56+},
57+langs: ["text"],
58+oldFile: { fileName: "a.ts", lang: "text", content: "old" },
59+newFile: { fileName: "a.ts", lang: "text", content: "new" },
60+});
61+62+function renderCard(): void {
63+document.body.insertAdjacentHTML(
64+"beforeend",
65+`<section class="oc-diff-card">
66+ <div data-openclaw-diff-host></div>
67+ <script type="application/json" data-openclaw-diff-payload>${viewerPayload}</script>
68+ </section>`,
69+);
70+}
71+1172describe("createToolbarButton icon safety", () => {
1273it("toolbarIconSvg map exists and has exactly 8 icon names", () => {
1374const requiredNames = [
@@ -58,3 +119,57 @@ describe("createToolbarButton icon safety", () => {
58119}
59120});
60121});
122+123+describe("hydrateViewer", () => {
124+beforeEach(() => {
125+document.body.innerHTML = "";
126+delete document.documentElement.dataset.openclawDiffsError;
127+delete document.documentElement.dataset.openclawDiffsReady;
128+vi.clearAllMocks();
129+});
130+131+it("continues hydrating later cards when one card throws", async () => {
132+const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
133+renderCard();
134+renderCard();
135+fileDiffHydrateMock.mockImplementationOnce(() => {
136+throw new Error("broken card");
137+});
138+const { controllers, hydrateViewer } = await import("./viewer-client.js");
139+controllers.splice(0);
140+141+await hydrateViewer();
142+143+expect(fileDiffHydrateMock).toHaveBeenCalledTimes(2);
144+expect(controllers).toHaveLength(1);
145+expect(warn).toHaveBeenCalledWith(
146+"Skipping diff card that failed to hydrate",
147+expect.any(Error),
148+);
149+expect(document.documentElement.dataset.openclawDiffsError).toBeUndefined();
150+warn.mockRestore();
151+});
152+153+it("does not retain controllers when initial state application throws", async () => {
154+const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
155+renderCard();
156+renderCard();
157+fileDiffSetOptionsMock.mockImplementationOnce(() => {
158+throw new Error("broken options");
159+});
160+const { controllers, hydrateViewer } = await import("./viewer-client.js");
161+controllers.splice(0);
162+163+await hydrateViewer();
164+165+expect(fileDiffHydrateMock).toHaveBeenCalledTimes(2);
166+expect(fileDiffSetOptionsMock).toHaveBeenCalledTimes(2);
167+expect(controllers).toHaveLength(1);
168+expect(warn).toHaveBeenCalledWith(
169+"Skipping diff card that failed to hydrate",
170+expect.any(Error),
171+);
172+expect(document.documentElement.dataset.openclawDiffsError).toBeUndefined();
173+warn.mockRestore();
174+});
175+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。