Files
based-dsh-diving/test/client.test.mjs
T
mward4 4ea8234e6a v0.1.1: initial release of @based-dsh-diving/dsh-plugin
DSH web-profile plugin bundle that replaces the static "Deep diving..."
turn-status label with 96 rotating loading phrases. Entirely browser-side:
it claims the [role="status"] label text node, re-asserts the claim when
React reconciles the stock string back, and rotates phrases on a 2.2s
timer so the label keeps moving even when streaming never re-renders it.
The elapsed-time clock, aria-live, and shimmer styling are untouched.

Includes a headless DOM-stub test covering initial claim, clock
preservation, React revert, timer rotation, localized-label detection
(Deep diving... / 深度求索中...), and clean disposal.

Architecture inspired by ldgeng/dsh-claudecode-tips (MIT).
2026-09-14 20:41:58 -04:00

89 lines
3.5 KiB
JavaScript

// Headless harness for based-dsh-diving/client.js — stubs just enough DOM to
// exercise claim / React-revert / rotate behaviour without a browser.
import { readFileSync } from "node:fs";
const capturedLoads = [];
globalThis.window = { __ModuleLoader__: { load: (def) => capturedLoads.push(def) } };
globalThis.Node = { TEXT_NODE: 3, ELEMENT_NODE: 1 };
let observerCallback = null;
globalThis.MutationObserver = class {
constructor(cb) { observerCallback = cb; }
observe() {}
disconnect() { observerCallback = null; }
};
class TextNode {
constructor(value) { this.nodeType = 3; this.nodeValue = value; this.parent = null; this.nextSibling = null; }
get isConnected() { return this.parent !== null; }
}
class El {
constructor() { this.nodeType = 1; this.children = []; this.parent = null; this.nextSibling = null; }
get isConnected() { return this.parent !== null || this.rooted; }
get firstChild() { return this.children[0] ?? null; }
append(child) { child.parent = this; this.children.push(child); child.nextSibling = null; this._relink(); }
contains(node) { let p = node; while (p) { if (p === this) return true; p = p.parent; } return false; }
_relink() { this.children.forEach((c, i) => { c.nextSibling = this.children[i + 1] ?? null; }); }
}
const root = new El();
root.rooted = true;
const status = new El();
status.rooted = true;
const label = new TextNode("Deep diving...");
const clock = new El();
clock.append(new TextNode("12s"));
status.append(label);
status.append(clock);
root.append(status);
let statuses = [status];
globalThis.document = {
documentElement: root,
querySelectorAll: (sel) => (sel === '[role="status"]' ? statuses : [])
};
await import(new URL("../client.js", import.meta.url));
if (capturedLoads.length !== 1) throw new Error(`expected 1 load, got ${capturedLoads.length}`);
const def = capturedLoads[0];
if (def.id !== "@based-dsh-diving/dsh-plugin") throw new Error(`bad id: ${def.id}`);
const mod = def.factory(() => { throw new Error("require not expected"); });
let cleanup = null;
mod.apply({ effect: (fn) => { cleanup = fn(); } });
const ok = (name, cond, extra = "") => console.log(`${cond ? "PASS" : "FAIL"} ${name}${extra ? " " + extra : ""}`);
ok("label claimed on apply", /\.\.\.$/.test(label.nodeValue) && !/deep diving/i.test(label.nodeValue), JSON.stringify(label.nodeValue));
ok("clock untouched", clock.children[0].nodeValue === "12s");
const first = label.nodeValue;
// React re-renders the stock label back onto the same text node.
label.nodeValue = "Deep diving...";
observerCallback?.();
await new Promise((r) => setTimeout(r, 200));
ok("React revert re-asserted by debounced scan", label.nodeValue === first, JSON.stringify(label.nodeValue));
// Rotation moves the text on its own.
await new Promise((r) => setTimeout(r, 2400));
ok("phrase rotates on interval", label.nodeValue !== first, JSON.stringify(label.nodeValue));
// Chinese locale label is recognized too (fresh status element, as React would
// mount for the next turn).
const zhStatus = new El();
zhStatus.rooted = true;
const zh = new TextNode("深度求索中...");
zh.parent = zhStatus;
zhStatus.children = [zh];
statuses = [zhStatus];
observerCallback?.();
await new Promise((r) => setTimeout(r, 200));
ok("zh label claimed", /\.\.\.$/.test(zh.nodeValue) && !/深度求索中/.test(zh.nodeValue), JSON.stringify(zh.nodeValue));
// Disposal stops rotation.
cleanup();
const frozen = zh.nodeValue;
await new Promise((r) => setTimeout(r, 2400));
ok("disposer stops rotation", zh.nodeValue === frozen);