Files
Netcatty/components/ScriptsSidePanel.stacked-lifecycle.test.tsx
T
f7483f0593 fix(scripts): add persistable stacked chip view to script library panel (#3567)
* fix(#3565): automated AI fix

* fix: address Codex review on PR #3567

* fix: address Codex review on PR #3567

* fix: address Codex review on PR #3567

* fix: address Codex review on PR #3567

* fix(scripts): observe stacked sentinel again after returning to library

* fix(scripts): reuse shared persisted string hook for cross-window view changes

---------

Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: 陈大猫 <16399091+binaricat@users.noreply.github.com>
2026-10-08 15:20:22 +08:00

74 lines
4.1 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import React from "react";
import { createDomRenderer, dispatchDomEvent, installDomEnvironment, runWithAct } from "./test-support/renderReactDom.tsx";
import type { Snippet } from "../types";
test("stacked library continues loading after running a script and returning", async (t) => {
const env = installDomEnvironment();
const previous = Object.getOwnPropertyDescriptor(globalThis, "IntersectionObserver");
const previousStorage = Object.getOwnPropertyDescriptor(globalThis, "localStorage");
Object.defineProperty(globalThis, "localStorage", { configurable: true, value: env.window.localStorage });
const observers: Array<{ target: Element | null; intersect: () => void }> = [];
class Observer {
target: Element | null = null;
constructor(private callback: IntersectionObserverCallback) { observers.push(this); }
observe(target: Element) { this.target = target; }
disconnect() { this.target = null; }
intersect = () => {
if (this.target?.isConnected) {
this.callback([{ target: this.target, isIntersecting: true } as IntersectionObserverEntry], this as unknown as IntersectionObserver);
}
};
}
Object.defineProperty(globalThis, "IntersectionObserver", { configurable: true, value: Observer });
const { ScriptsSidePanel } = await import("./ScriptsSidePanel.tsx");
const { I18nProvider } = await import("../application/i18n/I18nProvider.tsx");
const { SCRIPTS_VIEW_MODE_STORAGE_KEY } = await import("../application/state/useScriptsViewMode.ts");
env.window.localStorage.setItem(SCRIPTS_VIEW_MODE_STORAGE_KEY, "stacked");
const renderer = await createDomRenderer(env.document);
t.after(async () => {
await renderer.unmount();
if (previous) Object.defineProperty(globalThis, "IntersectionObserver", previous);
else Reflect.deleteProperty(globalThis, "IntersectionObserver");
if (previousStorage) Object.defineProperty(globalThis, "localStorage", previousStorage);
else Reflect.deleteProperty(globalThis, "localStorage");
env.cleanup();
});
const snippets: Snippet[] = Array.from({ length: 300 }, (_, i) => ({
id: `script-${i}`, label: `Script ${String(i).padStart(3, "0")}`, command: "echo ok", kind: "script", package: "",
}));
const executed: string[] = [];
await renderer.render(
<I18nProvider locale="en"><ScriptsSidePanel snippets={snippets} packages={[]}
onSnippetClick={() => assert.fail("scripts use the existing script execution path")}
onRunScript={(snippet) => executed.push(snippet.id)} /></I18nProvider>,
);
const library = () => env.document.querySelector('[data-scripts-view="stacked"]');
const chipCount = () => library()?.querySelectorAll("button").length;
const button = (label: string) => {
const result = Array.from(env.document.querySelectorAll("button")).find((element) => element.textContent === label);
assert.ok(result, `missing button ${label}`);
return result;
};
assert.equal(chipCount(), 120);
const firstSentinel = observers.at(-1)?.target;
assert.ok(firstSentinel?.isConnected);
await dispatchDomEvent(button("Script 000"), new env.window.MouseEvent("click", { bubbles: true }));
assert.deepEqual(executed, ["script-0"]);
assert.equal(library(), null, "execution opens Running");
assert.equal(firstSentinel.isConnected, false);
await dispatchDomEvent(button("Library"), new env.window.MouseEvent("click", { bubbles: true }));
assert.equal(chipCount(), 120);
assert.ok(observers.at(-1)?.target?.isConnected, "the new sentinel must be observed");
assert.notEqual(observers.at(-1)?.target, firstSentinel);
await runWithAct(() => { observers.at(-1)?.intersect(); });
assert.equal(chipCount(), 240);
await dispatchDomEvent(button("Running"), new env.window.MouseEvent("click", { bubbles: true }));
await dispatchDomEvent(button("Library"), new env.window.MouseEvent("click", { bubbles: true }));
assert.equal(chipCount(), 240, "returning preserves the loaded window");
await runWithAct(() => { observers.at(-1)?.intersect(); });
assert.equal(chipCount(), 300);
assert.ok(button("Script 299"));
});