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
120 lines
3.8 KiB
TypeScript
120 lines
3.8 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
createStoredStringSyncHandlers,
|
|
readOptionalStoredStringValue,
|
|
readStoredStringValue,
|
|
resolveStoredStringUpdate,
|
|
} from "./useStoredString.ts";
|
|
|
|
type TestMode = "edit" | "preview";
|
|
|
|
const isTestMode = (value: string | null): value is TestMode =>
|
|
value === "edit" || value === "preview";
|
|
|
|
function installLocalStorage() {
|
|
const previousLocalStorage = Object.getOwnPropertyDescriptor(globalThis, "localStorage");
|
|
const backing = new Map<string, string>();
|
|
const storage: Storage = {
|
|
get length() {
|
|
return backing.size;
|
|
},
|
|
clear() {
|
|
backing.clear();
|
|
},
|
|
getItem(key: string) {
|
|
return backing.get(key) ?? null;
|
|
},
|
|
key(index: number) {
|
|
return Array.from(backing.keys())[index] ?? null;
|
|
},
|
|
removeItem(key: string) {
|
|
backing.delete(key);
|
|
},
|
|
setItem(key: string, value: string) {
|
|
backing.set(key, value);
|
|
},
|
|
};
|
|
|
|
Object.defineProperty(globalThis, "localStorage", {
|
|
value: storage,
|
|
configurable: true,
|
|
});
|
|
|
|
return {
|
|
storage,
|
|
restore() {
|
|
if (previousLocalStorage) {
|
|
Object.defineProperty(globalThis, "localStorage", previousLocalStorage);
|
|
return;
|
|
}
|
|
Reflect.deleteProperty(globalThis, "localStorage");
|
|
},
|
|
};
|
|
}
|
|
|
|
test("stored string sync handlers refresh from same-window and browser storage events", (t) => {
|
|
const env = installLocalStorage();
|
|
t.after(() => env.restore());
|
|
|
|
const storageKey = "netcatty:test-mode";
|
|
const syncedValues: TestMode[] = [];
|
|
const handlers = createStoredStringSyncHandlers<TestMode>({
|
|
storageKey,
|
|
fallback: "edit",
|
|
isAllowedValue: isTestMode,
|
|
onValue: (value) => syncedValues.push(value),
|
|
});
|
|
|
|
env.storage.setItem(storageKey, "preview");
|
|
handlers.handleAdapterChange({ detail: { key: storageKey } } as CustomEvent<{ key: string }>);
|
|
assert.deepEqual(syncedValues, ["preview"]);
|
|
|
|
env.storage.setItem(storageKey, "invalid");
|
|
handlers.handleBrowserStorage({ type: "storage", key: storageKey } as StorageEvent);
|
|
assert.deepEqual(syncedValues, ["preview", "edit"]);
|
|
|
|
env.storage.setItem(storageKey, "preview");
|
|
handlers.handleAdapterChange({ detail: { key: "other-key" } } as CustomEvent<{ key: string }>);
|
|
assert.deepEqual(syncedValues, ["preview", "edit"]);
|
|
});
|
|
|
|
test("stored string helpers read fallback and resolve updater-style toggles", (t) => {
|
|
const env = installLocalStorage();
|
|
t.after(() => env.restore());
|
|
|
|
const storageKey = "netcatty:test-mode";
|
|
assert.equal(readStoredStringValue(storageKey, "edit", isTestMode), "edit");
|
|
assert.equal(readOptionalStoredStringValue(storageKey, isTestMode), null);
|
|
|
|
env.storage.setItem(storageKey, "preview");
|
|
assert.equal(readStoredStringValue(storageKey, "edit", isTestMode), "preview");
|
|
assert.equal(readOptionalStoredStringValue(storageKey, isTestMode), "preview");
|
|
|
|
env.storage.setItem(storageKey, "invalid");
|
|
assert.equal(readStoredStringValue(storageKey, "edit", isTestMode), "edit");
|
|
assert.equal(readOptionalStoredStringValue(storageKey, isTestMode), null);
|
|
|
|
assert.equal(resolveStoredStringUpdate<TestMode>("edit", "preview"), "preview");
|
|
assert.equal(
|
|
resolveStoredStringUpdate<TestMode>("preview", (currentValue) => (
|
|
currentValue === "edit" ? "preview" : "edit"
|
|
)),
|
|
"edit",
|
|
);
|
|
});
|
|
|
|
test("stored string helpers work with default validator when isAllowedValue is omitted", (t) => {
|
|
const env = installLocalStorage();
|
|
t.after(() => env.restore());
|
|
|
|
const storageKey = "netcatty:test-font";
|
|
assert.equal(readStoredStringValue(storageKey, "default-font"), "default-font");
|
|
assert.equal(readOptionalStoredStringValue(storageKey), null);
|
|
|
|
env.storage.setItem(storageKey, "Inter");
|
|
assert.equal(readStoredStringValue(storageKey, "default-font"), "Inter");
|
|
assert.equal(readOptionalStoredStringValue(storageKey), "Inter");
|
|
});
|