1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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([]);
});
});
|