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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160import { describe, it, expect, beforeEach } from "vitest";
describe("useServerInsertedHTML", () => {
beforeEach(async () => {
// Clear any stale callbacks between tests
const { clearServerInsertedHTML } = await import(
"../packages/vinext/src/shims/navigation.js"
);
clearServerInsertedHTML();
});
it("is exported from next/navigation shim", async () => {
const mod = await import("../packages/vinext/src/shims/navigation.js");
expect(typeof mod.useServerInsertedHTML).toBe("function");
});
it("flushServerInsertedHTML is exported", async () => {
const mod = await import("../packages/vinext/src/shims/navigation.js");
expect(typeof mod.flushServerInsertedHTML).toBe("function");
});
it("clearServerInsertedHTML is exported", async () => {
const mod = await import("../packages/vinext/src/shims/navigation.js");
expect(typeof mod.clearServerInsertedHTML).toBe("function");
});
it("collects callbacks registered during SSR (no document)", async () => {
const { useServerInsertedHTML, flushServerInsertedHTML } = await import(
"../packages/vinext/src/shims/navigation.js"
);
// In test environment, document is undefined (SSR-like)
const results: string[] = [];
useServerInsertedHTML(() => {
results.push("called");
return "<style>.a { color: red; }</style>";
});
const flushed = flushServerInsertedHTML();
expect(flushed).toHaveLength(1);
expect(flushed[0]).toBe("<style>.a { color: red; }</style>");
expect(results).toEqual(["called"]);
});
it("collects multiple callbacks", async () => {
const { useServerInsertedHTML, flushServerInsertedHTML } = await import(
"../packages/vinext/src/shims/navigation.js"
);
useServerInsertedHTML(() => "<style>.a {}</style>");
useServerInsertedHTML(() => "<style>.b {}</style>");
useServerInsertedHTML(() => "<style>.c {}</style>");
const flushed = flushServerInsertedHTML();
expect(flushed).toHaveLength(3);
expect(flushed[0]).toBe("<style>.a {}</style>");
expect(flushed[1]).toBe("<style>.b {}</style>");
expect(flushed[2]).toBe("<style>.c {}</style>");
});
it("flush clears the callback list", async () => {
const { useServerInsertedHTML, flushServerInsertedHTML } = await import(
"../packages/vinext/src/shims/navigation.js"
);
useServerInsertedHTML(() => "first");
const first = flushServerInsertedHTML();
expect(first).toHaveLength(1);
// Second flush should be empty
const second = flushServerInsertedHTML();
expect(second).toHaveLength(0);
});
it("clearServerInsertedHTML discards callbacks without calling them", async () => {
const { useServerInsertedHTML, clearServerInsertedHTML, flushServerInsertedHTML } = await import(
"../packages/vinext/src/shims/navigation.js"
);
let called = false;
useServerInsertedHTML(() => {
called = true;
return "should not appear";
});
clearServerInsertedHTML();
const flushed = flushServerInsertedHTML();
expect(flushed).toHaveLength(0);
expect(called).toBe(false);
});
it("ignores null/undefined returns from callbacks", async () => {
const { useServerInsertedHTML, flushServerInsertedHTML } = await import(
"../packages/vinext/src/shims/navigation.js"
);
useServerInsertedHTML(() => null);
useServerInsertedHTML(() => undefined);
useServerInsertedHTML(() => "<style>.valid {}</style>");
const flushed = flushServerInsertedHTML();
expect(flushed).toHaveLength(1);
expect(flushed[0]).toBe("<style>.valid {}</style>");
});
it("handles callback errors gracefully", async () => {
const { useServerInsertedHTML, flushServerInsertedHTML } = await import(
"../packages/vinext/src/shims/navigation.js"
);
useServerInsertedHTML(() => {
throw new Error("CSS extraction failed");
});
useServerInsertedHTML(() => "<style>.ok {}</style>");
// Should not throw, should skip the erroring callback
const flushed = flushServerInsertedHTML();
expect(flushed).toHaveLength(1);
expect(flushed[0]).toBe("<style>.ok {}</style>");
});
it("callbacks can return React-like elements", async () => {
const { useServerInsertedHTML, flushServerInsertedHTML } = await import(
"../packages/vinext/src/shims/navigation.js"
);
// Simulate a React element (like styled-components would return)
const fakeElement = { $$typeof: Symbol.for("react.element"), type: "style", props: { children: ".x{}" } };
useServerInsertedHTML(() => fakeElement);
const flushed = flushServerInsertedHTML();
expect(flushed).toHaveLength(1);
expect(flushed[0]).toBe(fakeElement);
});
it("supports the styled-components SSR pattern", async () => {
const { useServerInsertedHTML, flushServerInsertedHTML } = await import(
"../packages/vinext/src/shims/navigation.js"
);
// Simulate styled-components ServerStyleSheet pattern
const collectedStyles: string[] = [];
let extractCount = 0;
// This simulates what a StyleRegistry component does
useServerInsertedHTML(() => {
extractCount++;
collectedStyles.push(`<style data-styled="sc-${extractCount}">.sc-${extractCount} { color: red; }</style>`);
return collectedStyles[collectedStyles.length - 1];
});
const flushed = flushServerInsertedHTML();
expect(flushed).toHaveLength(1);
expect(flushed[0]).toContain("data-styled");
expect(extractCount).toBe(1);
});
});