import { assert, beforeEach, describe, it } from "vitest"; import { nextTick, ref } from "vue"; import { loadMultitab, lockPromise, makeMultitab, } from "../../functions/multitab"; function wait(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } beforeEach(() => { localStorage.clear(); }); describe("makeMultitab", () => { it("restores an existing stored value on setup", () => { const rawKey = "existing-key"; localStorage.setItem( `multitab:${rawKey}`, JSON.stringify({ value: "already-here", updated: new Date().toISOString(), }), ); const value = ref(undefined); makeMultitab({ [rawKey]: value }); assert.equal(value.value, "already-here"); }); it("hydrates from defaultValue and persists it when nothing is stored yet", () => { const rawKey = "fresh-key"; const value = ref(undefined); console.log("localstorage", localStorage); assert.doesNotThrow(() => { makeMultitab({ [rawKey]: value }, { defaultValue: "the-default" }); }); assert.equal(value.value, "the-default"); const stored = JSON.parse(localStorage.getItem(`multitab:${rawKey}`)!); assert.equal(stored.value, "the-default"); }); it("persists local ref changes to storage", async () => { const rawKey = "local-edit"; const value = ref("start"); makeMultitab({ [rawKey]: value }); value.value = "changed"; await wait(250); const stored = JSON.parse(localStorage.getItem(`multitab:${rawKey}`)!); assert.equal(stored.value, "changed"); }); it("applies an incoming cross-tab update to the local ref", async () => { const rawKey = "cross-tab-sync"; const fullKey = `multitab:${rawKey}`; const value = ref("original"); makeMultitab({ [rawKey]: value }); // future timestamp so it reads as newer than what setup recorded localStorage.setItem( fullKey, JSON.stringify({ value: "from-other-tab", updated: new Date(Date.now() + 10000).toISOString(), }), ); window.dispatchEvent(new StorageEvent("storage", { key: fullKey })); await wait(250); assert.equal(value.value, "from-other-tab"); }); it("does not rewrite storage after applying an identical incoming update", async () => { const rawKey = "no-bounce"; const fullKey = `multitab:${rawKey}`; const value = ref(undefined); makeMultitab({ [rawKey]: value }); const incomingUpdated = new Date(Date.now() + 10000).toISOString(); localStorage.setItem( fullKey, JSON.stringify({ value: "shared-value", updated: incomingUpdated }), ); window.dispatchEvent(new StorageEvent("storage", { key: fullKey })); await wait(250); assert.equal(value.value, "shared-value"); // give the local watcher's save debounce a full window to fire, if it were going to await wait(300); const stored = JSON.parse(localStorage.getItem(fullKey)!); assert.equal( stored.updated, incomingUpdated, "receiving tab must not re-broadcast the value it just applied", ); }); it("throws when the same key is registered twice", () => { const rawKey = "dup-key"; makeMultitab({ [rawKey]: ref() }); assert.throws(() => makeMultitab({ [rawKey]: ref() })); }); }); describe("loadMultitab", () => { it("treats an expired value as cleared", () => { const key = "multitab:expiry-test"; localStorage.setItem( key, JSON.stringify({ value: "stale", expires: new Date(Date.now() - 1000).toISOString(), updated: new Date().toISOString(), }), ); assert.isUndefined(loadMultitab(key)); }); }); describe("lockPromise", () => { it("resolves immediately when the ref starts true", async () => { const flag = ref(true); const lock = lockPromise(flag); assert.isTrue(await lock.promise); }); it("resolves once the ref becomes true and re-locks when it goes false again", async () => { const flag = ref(false); const lock = lockPromise(flag); let resolved = false; lock.promise!.then(() => { resolved = true; }); await nextTick(); assert.isFalse(resolved); flag.value = true; await nextTick(); assert.isTrue(await lock.promise); flag.value = false; await nextTick(); let resolvedAgain = false; lock.promise!.then(() => { resolvedAgain = true; }); await nextTick(); assert.isFalse(resolvedAgain); }); });