mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
test(ui): fix Node 25+ localStorage crashes in jsdom suites; document PR-open CI drops (#110812)
* test(ui): shim dead Node 25+ WebStorage localStorage in jsdom test setup Node 25+ ships a default-on global localStorage that is dead without --localstorage-file (undefined on 26.5; throwing or inert elsewhere). It shadows jsdom's Storage during vitest global population, so storage-touching UI tests crash on newer local Node while Linux CI (Node 24) passes — six files failed locally on Node 26.5. The shared setup now capability-probes (round-trip, throw-safe), prefers jsdom's own window Storage when only the global alias is dead, and otherwise installs one in-memory Storage on both window and globalThis. Also documents the PR-open CI drop pattern in AGENTS.md: fresh PRs race GitHub's merge-ref computation and the open-event CI run can drop or die as startup_failure/BuildFailed (not rerunnable); verify attach and close/reopen to re-fire. * fix(test): drop unnecessary String() conversions in localStorage shim
This commit is contained in:
committed by
GitHub
parent
f705f69fc2
commit
a54a292d62
@@ -232,6 +232,7 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- No surprise GH writes: chat must mention every posted/updated public comment with URL.
|
||||
- GH comments with backticks, `$`, or shell snippets: use heredoc/body file, not inline double-quoted `--body`.
|
||||
- PR create: real body required. Use the current template: `What Problem This Solves`, `Why This Change Was Made`, `User Impact`, and `Evidence`; include visible refs, behavior, and validation.
|
||||
- PR create races GitHub's merge-ref computation: the pull_request-open CI run can drop entirely or die as `startup_failure`/`BuildFailed` (`(Unknown event)`, not rerunnable). After opening, verify the CI workflow attached to the head SHA; if missing, close/reopen the PR to re-fire the event.
|
||||
- PR create/refresh: keep PR branches takeover-ready. Use a branch maintainers can push to, or for fork PRs ensure `maintainer_can_modify` / GitHub's `Allow edits by maintainers` is enabled unless explicitly told otherwise or GitHub's Actions/secrets warning makes that unsafe.
|
||||
- GitHub issue/PR create: read `$agent-transcript`; ask about sanitized transcript logs when available.
|
||||
- Contributor PRs: parsed context requires authored `What Problem This Solves` and `Evidence` sections. Do not require field-level proof forms; reviewers inspect code, tests, and CI for correctness.
|
||||
|
||||
@@ -77,3 +77,70 @@ if (typeof HTMLDialogElement !== "undefined" && !("close" in HTMLDialogElement.p
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Node 25+ enables WebStorage by default with a global localStorage getter
|
||||
// that is dead without --localstorage-file (undefined on 26.5, reported to
|
||||
// throw or return an inert proxy on other 25/26 releases). During jsdom
|
||||
// global population it shadows the DOM Storage (globalThis is the window),
|
||||
// so storage-touching tests crash on newer local Node while Linux CI
|
||||
// (Node 24, no default WebStorage) passes. Capability-probe instead of
|
||||
// trusting any one shape, then install an in-memory Storage.
|
||||
function globalLocalStorageIsUsable(): boolean {
|
||||
try {
|
||||
const existing = globalThis.localStorage;
|
||||
if (!existing) {
|
||||
return false;
|
||||
}
|
||||
existing.setItem("__openclaw_probe__", "1");
|
||||
const roundTrips = existing.getItem("__openclaw_probe__") === "1";
|
||||
existing.removeItem("__openclaw_probe__");
|
||||
return roundTrips;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function usableWindowLocalStorage(): Storage | null {
|
||||
try {
|
||||
const candidate = window.localStorage;
|
||||
if (!candidate) {
|
||||
return null;
|
||||
}
|
||||
candidate.setItem("__openclaw_probe__", "1");
|
||||
const roundTrips = candidate.getItem("__openclaw_probe__") === "1";
|
||||
candidate.removeItem("__openclaw_probe__");
|
||||
return roundTrips ? candidate : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined" && !globalLocalStorageIsUsable()) {
|
||||
const backing = new Map<string, string>();
|
||||
// Prefer jsdom's own Storage when only the global alias is dead so
|
||||
// `localStorage` and `window.localStorage` stay the same object.
|
||||
const storage: Storage = usableWindowLocalStorage() ?? {
|
||||
get length() {
|
||||
return backing.size;
|
||||
},
|
||||
clear: () => backing.clear(),
|
||||
getItem: (key: string) => backing.get(key) ?? null,
|
||||
key: (index: number) => [...backing.keys()][index] ?? null,
|
||||
removeItem: (key: string) => {
|
||||
backing.delete(key);
|
||||
},
|
||||
setItem: (key: string, value: string) => {
|
||||
backing.set(key, value);
|
||||
},
|
||||
};
|
||||
const install = (target: object) =>
|
||||
Object.defineProperty(target, "localStorage", {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
get: () => storage,
|
||||
});
|
||||
install(globalThis);
|
||||
if ((window as unknown) !== globalThis) {
|
||||
install(window);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user