UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

170 lines (140 loc) 5.84 kB
import { $atom, Alepha, z } from "alepha"; import { beforeEach, describe, expect, it } from "vitest"; import { AtomCookiePersistence } from "../providers/AtomCookiePersistence.browser.ts"; const schema = z.object({ theme: z.string() }); /** * Clears every cookie the previous test may have left behind. jsdom's * `document.cookie` jar persists across tests within the same suite run. */ const clearAllCookies = (): void => { for (const part of document.cookie.split(";")) { const name = part.split("=")[0]?.trim(); if (name) { document.cookie = `${name}=; Path=/; Max-Age=0`; } } }; /** * Read a single cookie's raw (still encoded) value directly off * `document.cookie`, bypassing the adapter under test. */ const readRawCookie = (name: string): string | undefined => { const match = document.cookie .split(";") .map((part) => part.trim()) .find((part) => part.startsWith(`${name}=`)); return match ? match.slice(name.length + 1) : undefined; }; describe("AtomCookiePersistence (browser)", () => { beforeEach(() => { clearAllCookies(); }); it("hydrates from an existing document.cookie on register", () => { document.cookie = `test.cookie.browser.read=${encodeURIComponent( JSON.stringify({ theme: "dark" }), )}; Path=/`; const prefs = $atom({ name: "test.cookie.browser.read", schema, default: { theme: "light" }, persist: "cookie", }); const alepha = Alepha.create(); alepha.inject(AtomCookiePersistence); expect(alepha.store.get(prefs)).toEqual({ theme: "dark" }); }); it("reads and writes an atom that registered BEFORE the adapter existed", async () => { // The `$module({ atoms, imports, services })` shape: `atoms[]` register // before `services[]` are injected, so the adapter never sees the // atom's one-shot `state:register` event. Discovering atoms from the // state manager's registry (instead of from that event) is what makes // this work. document.cookie = `test.cookie.browser.module=${encodeURIComponent( JSON.stringify({ theme: "dark" }), )}; Path=/`; const prefs = $atom({ name: "test.cookie.browser.module", schema, default: { theme: "light" }, persist: "cookie", }); const alepha = Alepha.create(); alepha.store.register(prefs); alepha.inject(AtomCookiePersistence); await alepha.start(); expect(alepha.store.get(prefs)).toEqual({ theme: "dark" }); alepha.store.set(prefs, { theme: "solarized" }); const raw = readRawCookie("test.cookie.browser.module"); expect(raw).toBeDefined(); expect(JSON.parse(decodeURIComponent(raw!))).toEqual({ theme: "solarized", }); }); it("falls back to the default when there is no cookie", () => { const prefs = $atom({ name: "test.cookie.browser.none", schema, default: { theme: "light" }, persist: "cookie", }); const alepha = Alepha.create(); alepha.inject(AtomCookiePersistence); expect(alepha.store.get(prefs)).toEqual({ theme: "light" }); }); it("writes document.cookie back on mutation, using the bare (unprefixed) atom key", () => { const prefs = $atom({ name: "test.cookie.browser.write", schema, default: { theme: "light" }, persist: "cookie", }); const alepha = Alepha.create(); alepha.inject(AtomCookiePersistence); alepha.store.set(prefs, { theme: "dark" }); const raw = readRawCookie("test.cookie.browser.write"); expect(raw).toBeDefined(); expect(JSON.parse(decodeURIComponent(raw!))).toEqual({ theme: "dark" }); // No APP_NAME namespace: the browser cannot see APP_NAME (see this // class's JSDoc), and the server side (AtomCookiePersistence.ts) // matches by passing `prefix: false` for atom cookies specifically — // see AtomCookiePersistence.spec.ts's "cookie-name symmetry with // APP_NAME set" suite for the server-side half of this proof. expect(readRawCookie("rdm.test.cookie.browser.write")).toBeUndefined(); }); it("clears the cookie instead of writing the literal 'undefined' when the atom's value is cleared", () => { const prefs = $atom({ name: "test.cookie.browser.delete", schema, default: { theme: "light" }, persist: "cookie", }); const alepha = Alepha.create(); alepha.inject(AtomCookiePersistence); alepha.store.set(prefs, { theme: "dark" }); expect(readRawCookie("test.cookie.browser.delete")).toBeDefined(); // Bypass the Atom-typed overload deliberately: `set(key, undefined)` is // exactly what `StateManager.del()` does internally, and is the only // way to drive the atom back to "no value", which is what triggers the // `value === undefined` branch under test. alepha.store.set(prefs.key as any, undefined); // Must be gone entirely — never the literal string "undefined", which // would make the next register()'s `JSON.parse("undefined")` throw. expect(readRawCookie("test.cookie.browser.delete")).toBeUndefined(); }); it("clears a corrupted cookie instead of leaving it to throw on every future read", () => { document.cookie = "test.cookie.browser.corrupt=not-json-at-all; Path=/"; const prefs = $atom({ name: "test.cookie.browser.corrupt", schema, default: { theme: "light" }, persist: "cookie", }); const alepha = Alepha.create(); alepha.inject(AtomCookiePersistence); // Corrupted value is discarded; the default is used instead of throwing. expect(alepha.store.get(prefs)).toEqual({ theme: "light" }); // And the corrupted cookie itself is cleared, not left behind to keep // failing on every future page load. expect(readRawCookie("test.cookie.browser.corrupt")).toBeUndefined(); }); });