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");
|
||
|
|
});
|