summaryrefslogtreecommitdiffhomepage
path: root/src/adapters/history/index.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/adapters/history/index.test.ts')
-rw-r--r--src/adapters/history/index.test.ts98
1 files changed, 98 insertions, 0 deletions
diff --git a/src/adapters/history/index.test.ts b/src/adapters/history/index.test.ts
new file mode 100644
index 0000000..faab9d4
--- /dev/null
+++ b/src/adapters/history/index.test.ts
@@ -0,0 +1,98 @@
+import { describe, expect, it } from "vitest";
+import { createHistoryAdapter, type HistoryWindow } from "./index";
+
+/**
+ * A minimal in-memory `HistoryWindow` fake for deterministic tests. `pathname`
+ * is a closure variable reflected through the `location` getter; `back(url)`
+ * simulates an external navigation (back/forward): it sets the path + fires the
+ * popstate listeners.
+ */
+function fakeWindow(initial = "/"): HistoryWindow & { back(url: string): void } {
+ let pathname = initial;
+ const popstateListeners = new Set<() => void>();
+ return {
+ get location() {
+ return {
+ get pathname() {
+ return pathname;
+ },
+ };
+ },
+ history: {
+ pushState(_data, _unused, url) {
+ if (typeof url === "string") pathname = url;
+ },
+ replaceState(_data, _unused, url) {
+ if (typeof url === "string") pathname = url;
+ },
+ },
+ addEventListener(type, listener) {
+ if (type === "popstate") popstateListeners.add(listener);
+ },
+ removeEventListener(type, listener) {
+ if (type === "popstate") popstateListeners.delete(listener);
+ },
+ back(url: string) {
+ pathname = url;
+ for (const l of popstateListeners) l();
+ },
+ };
+}
+
+describe("createHistoryAdapter", () => {
+ it("reads the current path", () => {
+ const w = fakeWindow("/my-ws");
+ const h = createHistoryAdapter({ window: w });
+ expect(h.path).toBe("/my-ws");
+ });
+
+ it("navigate updates the path + notifies subscribers", () => {
+ const w = fakeWindow("/");
+ const h = createHistoryAdapter({ window: w });
+ const seen: string[] = [];
+ h.subscribe((p) => seen.push(p));
+ h.navigate("/default");
+ expect(h.path).toBe("/default");
+ expect(seen).toEqual(["/default"]);
+ });
+
+ it("replace updates the path WITHOUT notifying", () => {
+ const w = fakeWindow("/");
+ const h = createHistoryAdapter({ window: w });
+ const seen: string[] = [];
+ h.subscribe((p) => seen.push(p));
+ h.replace("/default");
+ expect(h.path).toBe("/default");
+ expect(seen).toEqual([]);
+ });
+
+ it("fires subscribers on popstate (back/forward)", () => {
+ const w = fakeWindow("/");
+ const h = createHistoryAdapter({ window: w });
+ const seen: string[] = [];
+ h.subscribe((p) => seen.push(p));
+ w.back("/my-ws");
+ expect(seen).toEqual(["/my-ws"]);
+ });
+
+ it("unsubscribe stops notifications (navigate + popstate)", () => {
+ const w = fakeWindow("/");
+ const h = createHistoryAdapter({ window: w });
+ const seen: string[] = [];
+ const unsub = h.subscribe((p) => seen.push(p));
+ unsub();
+ h.navigate("/default");
+ w.back("/other");
+ expect(seen).toEqual([]);
+ });
+
+ it("degrades to a no-op adapter when there is no location (SSR)", () => {
+ const w = { location: undefined } as unknown as HistoryWindow;
+ const h = createHistoryAdapter({ window: w });
+ expect(h.path).toBe("/");
+ const seen: string[] = [];
+ h.subscribe((p) => seen.push(p));
+ h.navigate("/default");
+ expect(seen).toEqual([]);
+ });
+});