@sigil-dev/grimoire 0.7.6 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +35 -34
- package/package.json +8 -6
- package/preload.js +3 -2
- package/server.ts +13 -13
- package/src/client/head.ts +29 -29
- package/src/client/router.ts +120 -53
- package/src/dev/compile-module.ts +173 -0
- package/src/dev/effect-registry.ts +23 -0
- package/src/dev/graph.ts +114 -0
- package/src/dev/hmr-client.ts +158 -0
- package/src/dev/hmr-server.ts +187 -0
- package/src/dev/loader.ts +47 -0
- package/src/dev/paths.ts +14 -0
- package/src/dev/runtime-bundle.ts +49 -0
- package/src/dev/watcher.ts +44 -0
- package/src/integrations/vite.ts +73 -72
- package/src/rendering/hydrate.ts +102 -64
- package/src/rendering/index.ts +296 -199
- package/src/rendering/ssrPlugin.ts +67 -53
- package/src/routing/manifest-gen.ts +42 -39
- package/src/routing/router.ts +109 -106
- package/src/routing/scanner.ts +141 -135
- package/src/routing/transform-routes.ts +101 -101
- package/src/server/build.ts +239 -147
- package/src/server/coordinator.ts +306 -306
- package/src/server/index.ts +260 -50
- package/src/server/worker.ts +59 -59
- package/src/typegen/index.ts +356 -353
- package/src/types.ts +270 -269
- package/test/context.test.ts +52 -52
- package/test/hydration.test.ts +119 -119
- package/test/middleware.test.ts +223 -223
- package/test/rendering.test.ts +579 -425
- package/test/routing.test.ts +81 -83
- package/test/scanning.test.ts +200 -181
- package/test/scope.test.ts +24 -8
- package/test/server.test.ts +249 -229
- package/test/streaming.test.ts +125 -106
- package/test/transform-routes.test.ts +84 -84
- package/test/typegen.test.ts +35 -25
- package/tsconfig.json +1 -0
package/test/hydration.test.ts
CHANGED
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
import { Window } from "happy-dom";
|
|
2
|
-
|
|
3
|
-
// set up DOM globals before tests
|
|
4
|
-
const window = new Window();
|
|
5
|
-
const document = window.document;
|
|
6
|
-
|
|
7
|
-
// @ts-expect-error
|
|
8
|
-
globalThis.document = document;
|
|
9
|
-
// @ts-expect-error
|
|
10
|
-
globalThis.window = window;
|
|
11
|
-
// @ts-expect-error
|
|
12
|
-
globalThis.HTMLElement = window.HTMLElement;
|
|
13
|
-
// @ts-expect-error
|
|
14
|
-
globalThis.Event = window.Event;
|
|
15
|
-
// @ts-expect-error
|
|
16
|
-
globalThis.MouseEvent = window.MouseEvent;
|
|
17
|
-
// @ts-expect-error
|
|
18
|
-
globalThis.SyntaxError = SyntaxError;
|
|
19
|
-
|
|
20
|
-
import { beforeEach, describe, expect, test } from "bun:test";
|
|
21
|
-
|
|
22
|
-
// mock routes - simulates what #grimoire-routes generates
|
|
23
|
-
const mockRoutes: Record<string, (props: any) => any> = {
|
|
24
|
-
"/spells/:id": (props: any) => {
|
|
25
|
-
return { params: props.params };
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// simulate hydrate logic (same as hydrate.ts but without the import)
|
|
30
|
-
function runHydrate(routes: Record<string, (props: any) => any>) {
|
|
31
|
-
const stateEl = document.getElementById("__grimoire_state__");
|
|
32
|
-
if (stateEl) {
|
|
33
|
-
const state = JSON.parse(stateEl.textContent!);
|
|
34
|
-
const Page = routes[state.pattern];
|
|
35
|
-
if (Page) {
|
|
36
|
-
Page({ ...state.data, params: state.params });
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// simulate initRouter (same logic as router.ts)
|
|
42
|
-
let clickHandlerAttached = false;
|
|
43
|
-
function initRouter(routes: Record<string, (props: any) => any>) {
|
|
44
|
-
document.addEventListener("click", () => {});
|
|
45
|
-
clickHandlerAttached = true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
describe("hydration", () => {
|
|
49
|
-
beforeEach(() => {
|
|
50
|
-
clickHandlerAttached = false;
|
|
51
|
-
document.body.innerHTML = `
|
|
52
|
-
<div id="app">
|
|
53
|
-
<div id="grimoire-page">
|
|
54
|
-
<div class="spell-detail">
|
|
55
|
-
<h1>Fireball</h1>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
<script id="__grimoire_state__" type="application/json">
|
|
60
|
-
${JSON.stringify({ params: { id: "fireball" }, data: {}, pattern: "/spells/:id" })}
|
|
61
|
-
</script>
|
|
62
|
-
`;
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// 1. hydrate claims existing DOM, doesn't create new nodes
|
|
66
|
-
test("hydrate claims existing h1 instead of creating new one", () => {
|
|
67
|
-
const before = document.getElementById("app")!.innerHTML;
|
|
68
|
-
runHydrate(mockRoutes);
|
|
69
|
-
// DOM unchanged - hydrate only reads state, doesn't write
|
|
70
|
-
expect(document.getElementById("app")!.innerHTML).toBe(before);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// 2. router attaches after hydration
|
|
74
|
-
test("initRouter attaches click handler", () => {
|
|
75
|
-
initRouter(mockRoutes);
|
|
76
|
-
expect(clickHandlerAttached).toBe(true);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// 3. state script is readable
|
|
80
|
-
test("grimoire state is parseable", () => {
|
|
81
|
-
const el = document.getElementById("__grimoire_state__");
|
|
82
|
-
const state = JSON.parse(el!.textContent!);
|
|
83
|
-
expect(state.pattern).toBe("/spells/:id");
|
|
84
|
-
expect(state.params).toEqual({ id: "fireball" });
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// 4. hydrate doesn't touch DOM structure
|
|
88
|
-
test("hydrate preserves DOM structure", () => {
|
|
89
|
-
const before = document.body.innerHTML;
|
|
90
|
-
runHydrate(mockRoutes);
|
|
91
|
-
expect(document.body.innerHTML).toBe(before);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// 5. hydrate with missing pattern is safe
|
|
95
|
-
test("hydrate with unknown pattern does not throw", () => {
|
|
96
|
-
const el = document.getElementById("__grimoire_state__");
|
|
97
|
-
const state = JSON.parse(el!.textContent!);
|
|
98
|
-
state.pattern = "/nonexistent";
|
|
99
|
-
el!.textContent = JSON.stringify(state);
|
|
100
|
-
|
|
101
|
-
expect(() => runHydrate(mockRoutes)).not.toThrow();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// 6. hydrate passes data and params correctly
|
|
105
|
-
test("hydrate passes data and params to page component", () => {
|
|
106
|
-
let receivedProps: any = null;
|
|
107
|
-
const testRoutes: Record<string, (props: any) => any> = {
|
|
108
|
-
"/spells/:id": (props: any) => {
|
|
109
|
-
receivedProps = props;
|
|
110
|
-
return null;
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
runHydrate(testRoutes);
|
|
115
|
-
expect(receivedProps).toEqual({
|
|
116
|
-
params: { id: "fireball" },
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
});
|
|
1
|
+
import { Window } from "happy-dom";
|
|
2
|
+
|
|
3
|
+
// set up DOM globals before tests
|
|
4
|
+
const window = new Window();
|
|
5
|
+
const document = window.document;
|
|
6
|
+
|
|
7
|
+
// @ts-expect-error
|
|
8
|
+
globalThis.document = document;
|
|
9
|
+
// @ts-expect-error
|
|
10
|
+
globalThis.window = window;
|
|
11
|
+
// @ts-expect-error
|
|
12
|
+
globalThis.HTMLElement = window.HTMLElement;
|
|
13
|
+
// @ts-expect-error
|
|
14
|
+
globalThis.Event = window.Event;
|
|
15
|
+
// @ts-expect-error
|
|
16
|
+
globalThis.MouseEvent = window.MouseEvent;
|
|
17
|
+
// @ts-expect-error
|
|
18
|
+
globalThis.SyntaxError = SyntaxError;
|
|
19
|
+
|
|
20
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
21
|
+
|
|
22
|
+
// mock routes - simulates what #grimoire-routes generates
|
|
23
|
+
const mockRoutes: Record<string, (props: any) => any> = {
|
|
24
|
+
"/spells/:id": (props: any) => {
|
|
25
|
+
return { params: props.params };
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// simulate hydrate logic (same as hydrate.ts but without the import)
|
|
30
|
+
function runHydrate(routes: Record<string, (props: any) => any>) {
|
|
31
|
+
const stateEl = document.getElementById("__grimoire_state__");
|
|
32
|
+
if (stateEl) {
|
|
33
|
+
const state = JSON.parse(stateEl.textContent!);
|
|
34
|
+
const Page = routes[state.pattern];
|
|
35
|
+
if (Page) {
|
|
36
|
+
Page({ ...state.data, params: state.params });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// simulate initRouter (same logic as router.ts)
|
|
42
|
+
let clickHandlerAttached = false;
|
|
43
|
+
function initRouter(routes: Record<string, (props: any) => any>) {
|
|
44
|
+
document.addEventListener("click", () => {});
|
|
45
|
+
clickHandlerAttached = true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("hydration", () => {
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
clickHandlerAttached = false;
|
|
51
|
+
document.body.innerHTML = `
|
|
52
|
+
<div id="app">
|
|
53
|
+
<div id="grimoire-page">
|
|
54
|
+
<div class="spell-detail">
|
|
55
|
+
<h1>Fireball</h1>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<script id="__grimoire_state__" type="application/json">
|
|
60
|
+
${JSON.stringify({ params: { id: "fireball" }, data: {}, pattern: "/spells/:id" })}
|
|
61
|
+
</script>
|
|
62
|
+
`;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// 1. hydrate claims existing DOM, doesn't create new nodes
|
|
66
|
+
test("hydrate claims existing h1 instead of creating new one", () => {
|
|
67
|
+
const before = document.getElementById("app")!.innerHTML;
|
|
68
|
+
runHydrate(mockRoutes);
|
|
69
|
+
// DOM unchanged - hydrate only reads state, doesn't write
|
|
70
|
+
expect(document.getElementById("app")!.innerHTML).toBe(before);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 2. router attaches after hydration
|
|
74
|
+
test("initRouter attaches click handler", () => {
|
|
75
|
+
initRouter(mockRoutes);
|
|
76
|
+
expect(clickHandlerAttached).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// 3. state script is readable
|
|
80
|
+
test("grimoire state is parseable", () => {
|
|
81
|
+
const el = document.getElementById("__grimoire_state__");
|
|
82
|
+
const state = JSON.parse(el!.textContent!);
|
|
83
|
+
expect(state.pattern).toBe("/spells/:id");
|
|
84
|
+
expect(state.params).toEqual({ id: "fireball" });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// 4. hydrate doesn't touch DOM structure
|
|
88
|
+
test("hydrate preserves DOM structure", () => {
|
|
89
|
+
const before = document.body.innerHTML;
|
|
90
|
+
runHydrate(mockRoutes);
|
|
91
|
+
expect(document.body.innerHTML).toBe(before);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 5. hydrate with missing pattern is safe
|
|
95
|
+
test("hydrate with unknown pattern does not throw", () => {
|
|
96
|
+
const el = document.getElementById("__grimoire_state__");
|
|
97
|
+
const state = JSON.parse(el!.textContent!);
|
|
98
|
+
state.pattern = "/nonexistent";
|
|
99
|
+
el!.textContent = JSON.stringify(state);
|
|
100
|
+
|
|
101
|
+
expect(() => runHydrate(mockRoutes)).not.toThrow();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// 6. hydrate passes data and params correctly
|
|
105
|
+
test("hydrate passes data and params to page component", () => {
|
|
106
|
+
let receivedProps: any = null;
|
|
107
|
+
const testRoutes: Record<string, (props: any) => any> = {
|
|
108
|
+
"/spells/:id": (props: any) => {
|
|
109
|
+
receivedProps = props;
|
|
110
|
+
return null;
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
runHydrate(testRoutes);
|
|
115
|
+
expect(receivedProps).toEqual({
|
|
116
|
+
params: { id: "fireball" },
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|