Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
104 lines
4.0 KiB
TypeScript
104 lines
4.0 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import React from "react";
|
|
import { act, create, type ReactTestRenderer } from "react-test-renderer";
|
|
|
|
import { useStoredBoolean } from "./useStoredBoolean.ts";
|
|
|
|
const STORAGE_KEY = "netcatty:test:stored-boolean";
|
|
|
|
test("functional updates stay pure when another consumer shares the storage key", async () => {
|
|
const values = new Map<string, string>([[STORAGE_KEY, "false"]]);
|
|
const fakeStorage = {
|
|
getItem: (key: string) => values.get(key) ?? null,
|
|
setItem: (key: string, value: string) => values.set(key, value),
|
|
removeItem: (key: string) => values.delete(key),
|
|
};
|
|
const fakeWindow = new EventTarget();
|
|
const globals = globalThis as typeof globalThis & {
|
|
IS_REACT_ACT_ENVIRONMENT?: boolean;
|
|
localStorage?: typeof fakeStorage;
|
|
window?: typeof fakeWindow;
|
|
};
|
|
const previousActEnvironment = globals.IS_REACT_ACT_ENVIRONMENT;
|
|
const previousLocalStorage = Object.getOwnPropertyDescriptor(globalThis, "localStorage");
|
|
const previousWindow = Object.getOwnPropertyDescriptor(globalThis, "window");
|
|
const previousDispatchEvent = Object.getOwnPropertyDescriptor(globalThis, "dispatchEvent");
|
|
const previousCustomEvent = Object.getOwnPropertyDescriptor(globalThis, "CustomEvent");
|
|
let renderer: ReactTestRenderer | null = null;
|
|
let toggle: (() => void) | null = null;
|
|
let parentValue = false;
|
|
let childValue = false;
|
|
const errors: string[] = [];
|
|
const originalConsoleError = console.error;
|
|
|
|
Object.defineProperty(globalThis, "localStorage", { value: fakeStorage, configurable: true });
|
|
Object.defineProperty(globalThis, "window", { value: fakeWindow, configurable: true });
|
|
Object.defineProperty(globalThis, "dispatchEvent", {
|
|
value: fakeWindow.dispatchEvent.bind(fakeWindow),
|
|
configurable: true,
|
|
});
|
|
Object.defineProperty(globalThis, "CustomEvent", {
|
|
value: class TestCustomEvent<T> extends Event implements CustomEvent<T> {
|
|
readonly detail: T;
|
|
|
|
constructor(type: string, init?: CustomEventInit<T>) {
|
|
super(type);
|
|
this.detail = init?.detail as T;
|
|
}
|
|
|
|
initCustomEvent(): void {
|
|
// Deprecated browser API required by the CustomEvent interface.
|
|
}
|
|
},
|
|
configurable: true,
|
|
});
|
|
globals.IS_REACT_ACT_ENVIRONMENT = true;
|
|
console.error = (...args: unknown[]) => {
|
|
errors.push(args.map(String).join(" "));
|
|
};
|
|
|
|
const Parent = () => {
|
|
[parentValue] = useStoredBoolean(STORAGE_KEY);
|
|
return React.createElement(Child);
|
|
};
|
|
const Child = () => {
|
|
const [value, setValue] = useStoredBoolean(STORAGE_KEY);
|
|
childValue = value;
|
|
toggle = () => setValue((previous) => !previous);
|
|
return null;
|
|
};
|
|
|
|
try {
|
|
await act(async () => {
|
|
renderer = create(React.createElement(React.StrictMode, null, React.createElement(Parent)));
|
|
});
|
|
await act(async () => {
|
|
toggle?.();
|
|
});
|
|
|
|
assert.equal(parentValue, true);
|
|
assert.equal(childValue, true);
|
|
assert.equal(values.get(STORAGE_KEY), "true");
|
|
assert.equal(
|
|
errors.some((message) => message.includes("Cannot update a component")
|
|
&& message.includes("while rendering a different component")),
|
|
false,
|
|
);
|
|
} finally {
|
|
console.error = originalConsoleError;
|
|
await act(async () => {
|
|
renderer?.unmount();
|
|
});
|
|
globals.IS_REACT_ACT_ENVIRONMENT = previousActEnvironment;
|
|
if (previousLocalStorage) Object.defineProperty(globalThis, "localStorage", previousLocalStorage);
|
|
else delete globals.localStorage;
|
|
if (previousWindow) Object.defineProperty(globalThis, "window", previousWindow);
|
|
else delete globals.window;
|
|
if (previousDispatchEvent) Object.defineProperty(globalThis, "dispatchEvent", previousDispatchEvent);
|
|
else delete (globalThis as typeof globalThis & { dispatchEvent?: unknown }).dispatchEvent;
|
|
if (previousCustomEvent) Object.defineProperty(globalThis, "CustomEvent", previousCustomEvent);
|
|
else delete (globalThis as typeof globalThis & { CustomEvent?: unknown }).CustomEvent;
|
|
}
|
|
});
|