@praxisjs/runtime 0.7.0 → 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/CHANGELOG.md +39 -0
- package/dist/children.d.ts.map +1 -1
- package/dist/children.js +2 -1
- package/dist/children.js.map +1 -1
- package/dist/component.d.ts +2 -3
- package/dist/component.d.ts.map +1 -1
- package/dist/component.js +133 -60
- package/dist/component.js.map +1 -1
- package/dist/context.d.ts +13 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +24 -2
- package/dist/context.js.map +1 -1
- package/dist/dom/constants.d.ts +1 -1
- package/dist/dom/constants.d.ts.map +1 -1
- package/dist/dom/constants.js +24 -83
- package/dist/dom/constants.js.map +1 -1
- package/dist/dom/create.d.ts +1 -1
- package/dist/dom/create.d.ts.map +1 -1
- package/dist/dom/create.js +3 -3
- package/dist/dom/create.js.map +1 -1
- package/dist/dom/events.d.ts +29 -1
- package/dist/dom/events.d.ts.map +1 -1
- package/dist/dom/events.js +167 -17
- package/dist/dom/events.js.map +1 -1
- package/dist/dom/props.d.ts +4 -0
- package/dist/dom/props.d.ts.map +1 -1
- package/dist/dom/props.js +57 -8
- package/dist/dom/props.js.map +1 -1
- package/dist/dom/range.d.ts +4 -0
- package/dist/dom/range.d.ts.map +1 -0
- package/dist/dom/range.js +43 -0
- package/dist/dom/range.js.map +1 -0
- package/dist/dom/remove.d.ts.map +1 -1
- package/dist/dom/remove.js +2 -8
- package/dist/dom/remove.js.map +1 -1
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +7 -5
- package/dist/element.js.map +1 -1
- package/dist/hmr.d.ts +35 -0
- package/dist/hmr.d.ts.map +1 -0
- package/dist/hmr.js +85 -0
- package/dist/hmr.js.map +1 -0
- package/dist/hydration-context.d.ts +9 -1
- package/dist/hydration-context.d.ts.map +1 -1
- package/dist/hydration-context.js +34 -13
- package/dist/hydration-context.js.map +1 -1
- package/dist/hydration.d.ts.map +1 -1
- package/dist/hydration.js +4 -11
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +16 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -7
- package/dist/index.js.map +1 -1
- package/dist/list.d.ts +72 -0
- package/dist/list.d.ts.map +1 -0
- package/dist/list.js +786 -0
- package/dist/list.js.map +1 -0
- package/dist/portal.d.ts.map +1 -1
- package/dist/portal.js +3 -0
- package/dist/portal.js.map +1 -1
- package/dist/reactive.d.ts +2 -2
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +109 -34
- package/dist/reactive.js.map +1 -1
- package/dist/scope.d.ts +14 -4
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +55 -15
- package/dist/scope.js.map +1 -1
- package/dist/template.d.ts +48 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +270 -0
- package/dist/template.js.map +1 -0
- package/package.json +15 -6
- package/src/children.ts +2 -1
- package/src/component.ts +135 -63
- package/src/context.ts +35 -4
- package/src/dom/constants.ts +25 -82
- package/src/dom/create.ts +3 -3
- package/src/dom/events.ts +188 -17
- package/src/dom/props.ts +66 -9
- package/src/dom/range.ts +45 -0
- package/src/dom/remove.ts +3 -7
- package/src/element.ts +8 -6
- package/src/hmr.ts +110 -0
- package/src/hydration-context.ts +34 -13
- package/src/hydration.ts +4 -9
- package/src/index.ts +54 -7
- package/src/list.ts +886 -0
- package/src/portal.ts +3 -0
- package/src/reactive.ts +147 -40
- package/src/scope.ts +63 -16
- package/src/template.ts +271 -0
- package/dist/__tests__/children.test.d.ts +0 -2
- package/dist/__tests__/children.test.d.ts.map +0 -1
- package/dist/__tests__/children.test.js +0 -241
- package/dist/__tests__/children.test.js.map +0 -1
- package/dist/__tests__/component.test.d.ts +0 -2
- package/dist/__tests__/component.test.d.ts.map +0 -1
- package/dist/__tests__/component.test.js +0 -331
- package/dist/__tests__/component.test.js.map +0 -1
- package/dist/__tests__/context.test.d.ts +0 -2
- package/dist/__tests__/context.test.d.ts.map +0 -1
- package/dist/__tests__/context.test.js +0 -111
- package/dist/__tests__/context.test.js.map +0 -1
- package/dist/__tests__/dom.test.d.ts +0 -2
- package/dist/__tests__/dom.test.d.ts.map +0 -1
- package/dist/__tests__/dom.test.js +0 -385
- package/dist/__tests__/dom.test.js.map +0 -1
- package/dist/__tests__/hydration.test.d.ts +0 -2
- package/dist/__tests__/hydration.test.d.ts.map +0 -1
- package/dist/__tests__/hydration.test.js +0 -299
- package/dist/__tests__/hydration.test.js.map +0 -1
- package/dist/__tests__/portal.test.d.ts +0 -2
- package/dist/__tests__/portal.test.d.ts.map +0 -1
- package/dist/__tests__/portal.test.js +0 -157
- package/dist/__tests__/portal.test.js.map +0 -1
- package/dist/__tests__/remove.test.d.ts +0 -2
- package/dist/__tests__/remove.test.d.ts.map +0 -1
- package/dist/__tests__/remove.test.js +0 -45
- package/dist/__tests__/remove.test.js.map +0 -1
- package/dist/__tests__/render.test.d.ts +0 -2
- package/dist/__tests__/render.test.d.ts.map +0 -1
- package/dist/__tests__/render.test.js +0 -97
- package/dist/__tests__/render.test.js.map +0 -1
- package/dist/__tests__/scope.test.d.ts +0 -2
- package/dist/__tests__/scope.test.d.ts.map +0 -1
- package/dist/__tests__/scope.test.js +0 -110
- package/dist/__tests__/scope.test.js.map +0 -1
- package/src/__tests__/children.test.ts +0 -276
- package/src/__tests__/component.test.ts +0 -363
- package/src/__tests__/context.test.ts +0 -128
- package/src/__tests__/dom.test.ts +0 -434
- package/src/__tests__/hydration.test.ts +0 -360
- package/src/__tests__/portal.test.ts +0 -177
- package/src/__tests__/remove.test.ts +0 -51
- package/src/__tests__/render.test.ts +0 -119
- package/src/__tests__/scope.test.ts +0 -120
- package/tsconfig.json +0 -8
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
3
|
-
|
|
4
|
-
import { StatefulComponent } from "@praxisjs/core";
|
|
5
|
-
import { getComponentProps } from "@praxisjs/core/internal";
|
|
6
|
-
|
|
7
|
-
import { mountComponent } from "../component";
|
|
8
|
-
import { Scope } from "../scope";
|
|
9
|
-
|
|
10
|
-
class SimpleComp extends StatefulComponent {
|
|
11
|
-
static __isComponent = true as const;
|
|
12
|
-
static __isStateless = false;
|
|
13
|
-
render() {
|
|
14
|
-
return document.createTextNode("hello");
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
class NullComp extends StatefulComponent {
|
|
19
|
-
static __isComponent = true as const;
|
|
20
|
-
static __isStateless = false;
|
|
21
|
-
render() { return null; }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
class MultiComp extends StatefulComponent {
|
|
25
|
-
static __isComponent = true as const;
|
|
26
|
-
static __isStateless = false;
|
|
27
|
-
render() {
|
|
28
|
-
return [
|
|
29
|
-
document.createTextNode("a"),
|
|
30
|
-
document.createTextNode("b"),
|
|
31
|
-
];
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
class ErrorComp extends StatefulComponent {
|
|
36
|
-
static __isComponent = true as const;
|
|
37
|
-
static __isStateless = false;
|
|
38
|
-
onError(_err: Error): undefined {}
|
|
39
|
-
render(): never {
|
|
40
|
-
throw new Error("render error");
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
class NonErrorThrowComp extends StatefulComponent {
|
|
45
|
-
static __isComponent = true as const;
|
|
46
|
-
static __isStateless = false;
|
|
47
|
-
onError(_err: Error): undefined {}
|
|
48
|
-
render(): never {
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
50
|
-
throw "string render error";
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
class LifecycleComp extends StatefulComponent {
|
|
55
|
-
static __isComponent = true as const;
|
|
56
|
-
static __isStateless = false;
|
|
57
|
-
onBeforeMount() {}
|
|
58
|
-
onMount() {}
|
|
59
|
-
onUnmount() {}
|
|
60
|
-
render() { return null; }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
describe("mountComponent", () => {
|
|
64
|
-
it("returns an array of nodes", () => {
|
|
65
|
-
const scope = new Scope();
|
|
66
|
-
const nodes = mountComponent(SimpleComp, {}, scope);
|
|
67
|
-
expect(Array.isArray(nodes)).toBe(true);
|
|
68
|
-
expect(nodes.length).toBeGreaterThan(0);
|
|
69
|
-
scope.dispose();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("wraps output with start/end comment anchors", () => {
|
|
73
|
-
const scope = new Scope();
|
|
74
|
-
const nodes = mountComponent(SimpleComp, {}, scope);
|
|
75
|
-
expect(nodes[0].nodeType).toBe(Node.COMMENT_NODE);
|
|
76
|
-
expect(nodes[nodes.length - 1].nodeType).toBe(Node.COMMENT_NODE);
|
|
77
|
-
scope.dispose();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("mounts the rendered content between comments", () => {
|
|
81
|
-
const scope = new Scope();
|
|
82
|
-
const container = document.createElement("div");
|
|
83
|
-
const nodes = mountComponent(SimpleComp, {}, scope);
|
|
84
|
-
nodes.forEach((n) => container.appendChild(n));
|
|
85
|
-
expect(container.textContent).toContain("hello");
|
|
86
|
-
scope.dispose();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("calls onBeforeMount before render", () => {
|
|
90
|
-
const scope = new Scope();
|
|
91
|
-
const order: string[] = [];
|
|
92
|
-
class OrderComp extends StatefulComponent {
|
|
93
|
-
static __isComponent = true as const;
|
|
94
|
-
static __isStateless = false;
|
|
95
|
-
onBeforeMount() { order.push("before"); }
|
|
96
|
-
render() {
|
|
97
|
-
order.push("render");
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
mountComponent(OrderComp, {}, scope);
|
|
102
|
-
expect(order).toEqual(["before", "render"]);
|
|
103
|
-
scope.dispose();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("calls onMount asynchronously after mount", async () => {
|
|
107
|
-
const scope = new Scope();
|
|
108
|
-
const onMount = vi.spyOn(LifecycleComp.prototype, "onMount");
|
|
109
|
-
mountComponent(LifecycleComp, {}, scope);
|
|
110
|
-
expect(onMount).not.toHaveBeenCalled(); // not called synchronously
|
|
111
|
-
await Promise.resolve(); // flush microtask
|
|
112
|
-
expect(onMount).toHaveBeenCalled();
|
|
113
|
-
onMount.mockRestore();
|
|
114
|
-
scope.dispose();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("calls onUnmount when scope is disposed", async () => {
|
|
118
|
-
const scope = new Scope();
|
|
119
|
-
const onUnmount = vi.spyOn(LifecycleComp.prototype, "onUnmount");
|
|
120
|
-
mountComponent(LifecycleComp, {}, scope);
|
|
121
|
-
await Promise.resolve();
|
|
122
|
-
scope.dispose();
|
|
123
|
-
expect(onUnmount).toHaveBeenCalled();
|
|
124
|
-
onUnmount.mockRestore();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("calls onError when render throws", () => {
|
|
128
|
-
const scope = new Scope();
|
|
129
|
-
const onError = vi.spyOn(ErrorComp.prototype, "onError");
|
|
130
|
-
mountComponent(ErrorComp, {}, scope);
|
|
131
|
-
expect(onError).toHaveBeenCalledWith(expect.any(Error));
|
|
132
|
-
onError.mockRestore();
|
|
133
|
-
scope.dispose();
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("wraps non-Error throws in Error before passing to onError", () => {
|
|
137
|
-
const scope = new Scope();
|
|
138
|
-
const onError = vi.spyOn(NonErrorThrowComp.prototype, "onError");
|
|
139
|
-
mountComponent(NonErrorThrowComp, {}, scope);
|
|
140
|
-
expect(onError).toHaveBeenCalledWith(expect.any(Error));
|
|
141
|
-
expect((onError.mock.calls[0][0] as Error).message).toBe("string render error");
|
|
142
|
-
onError.mockRestore();
|
|
143
|
-
scope.dispose();
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("passes props to the component instance", () => {
|
|
147
|
-
let receivedProp: unknown;
|
|
148
|
-
class PropsComp extends StatefulComponent {
|
|
149
|
-
static __isComponent = true as const;
|
|
150
|
-
static __isStateless = false;
|
|
151
|
-
render() {
|
|
152
|
-
receivedProp = (getComponentProps(this) as Record<string, unknown>).msg;
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const scope = new Scope();
|
|
157
|
-
mountComponent(PropsComp, { msg: "hello" }, scope);
|
|
158
|
-
expect(receivedProp).toBe("hello");
|
|
159
|
-
scope.dispose();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("mounts array of children", () => {
|
|
163
|
-
const scope = new Scope();
|
|
164
|
-
const container = document.createElement("div");
|
|
165
|
-
const nodes = mountComponent(MultiComp, {}, scope);
|
|
166
|
-
nodes.forEach((n) => container.appendChild(n));
|
|
167
|
-
expect(container.textContent).toBe("ab");
|
|
168
|
-
scope.dispose();
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("onError handler that itself throws — outer error propagates", () => {
|
|
172
|
-
class ThrowingErrorComp extends StatefulComponent {
|
|
173
|
-
static __isComponent = true as const;
|
|
174
|
-
static __isStateless = false;
|
|
175
|
-
onError(_err: Error): never {
|
|
176
|
-
throw new Error("onError also throws");
|
|
177
|
-
}
|
|
178
|
-
render(): never {
|
|
179
|
-
throw new Error("render error");
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
const scope = new Scope();
|
|
183
|
-
expect(() => mountComponent(ThrowingErrorComp, {}, scope)).toThrow("onError also throws");
|
|
184
|
-
scope.dispose();
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it("onError returning a Node mounts it as fallback UI", () => {
|
|
188
|
-
class FallbackComp extends StatefulComponent {
|
|
189
|
-
static __isComponent = true as const;
|
|
190
|
-
static __isStateless = false;
|
|
191
|
-
onError(_err: Error) {
|
|
192
|
-
const p = document.createElement("p");
|
|
193
|
-
p.textContent = "error boundary";
|
|
194
|
-
return p;
|
|
195
|
-
}
|
|
196
|
-
render(): never {
|
|
197
|
-
throw new Error("render error");
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
const scope = new Scope();
|
|
201
|
-
const container = document.createElement("div");
|
|
202
|
-
const nodes = mountComponent(FallbackComp, {}, scope);
|
|
203
|
-
nodes.forEach((n) => container.appendChild(n));
|
|
204
|
-
expect(container.querySelector("p")?.textContent).toBe("error boundary");
|
|
205
|
-
scope.dispose();
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("onError returning null renders nothing as fallback", () => {
|
|
209
|
-
class NullFallbackComp extends StatefulComponent {
|
|
210
|
-
static __isComponent = true as const;
|
|
211
|
-
static __isStateless = false;
|
|
212
|
-
onError(_err: Error) {
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
render(): never {
|
|
216
|
-
throw new Error("render error");
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
const scope = new Scope();
|
|
220
|
-
const container = document.createElement("div");
|
|
221
|
-
const nodes = mountComponent(NullFallbackComp, {}, scope);
|
|
222
|
-
nodes.forEach((n) => container.appendChild(n));
|
|
223
|
-
expect(container.textContent).toBe("");
|
|
224
|
-
scope.dispose();
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("skips onMount and instance ref when disposed before the mount microtask", async () => {
|
|
228
|
-
class MountAfterDisposeComp extends StatefulComponent {
|
|
229
|
-
static __isComponent = true as const;
|
|
230
|
-
static __isStateless = false;
|
|
231
|
-
onMount() {}
|
|
232
|
-
render() { return null; }
|
|
233
|
-
}
|
|
234
|
-
const scope = new Scope();
|
|
235
|
-
const onMount = vi.spyOn(MountAfterDisposeComp.prototype, "onMount");
|
|
236
|
-
const refs: Array<object | null> = [];
|
|
237
|
-
mountComponent(MountAfterDisposeComp, { ref: (inst: object | null) => { refs.push(inst); } }, scope);
|
|
238
|
-
scope.dispose(); // dispose before microtask flushes
|
|
239
|
-
await Promise.resolve(); // flush microtask
|
|
240
|
-
expect(onMount).not.toHaveBeenCalled();
|
|
241
|
-
expect(refs).toEqual([null]);
|
|
242
|
-
onMount.mockRestore();
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it("ref callback receives the instance after onMount", async () => {
|
|
246
|
-
const scope = new Scope();
|
|
247
|
-
const refs: Array<object | null> = [];
|
|
248
|
-
const nodes = mountComponent(LifecycleComp, { ref: (inst: object | null) => { refs.push(inst); } }, scope);
|
|
249
|
-
expect(refs).toHaveLength(0); // not called synchronously
|
|
250
|
-
await Promise.resolve();
|
|
251
|
-
expect(refs).toHaveLength(1);
|
|
252
|
-
expect(refs[0]).toBeInstanceOf(LifecycleComp);
|
|
253
|
-
nodes.length; // suppress unused warning
|
|
254
|
-
scope.dispose();
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it("ref callback is called with null on unmount", async () => {
|
|
258
|
-
const scope = new Scope();
|
|
259
|
-
const refs: Array<object | null> = [];
|
|
260
|
-
mountComponent(LifecycleComp, { ref: (inst: object | null) => { refs.push(inst); } }, scope);
|
|
261
|
-
await Promise.resolve();
|
|
262
|
-
scope.dispose();
|
|
263
|
-
expect(refs).toEqual([expect.any(LifecycleComp), null]);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it("ref is not forwarded to component props", () => {
|
|
267
|
-
let seenRef: unknown = "not-checked";
|
|
268
|
-
class RefCheckComp extends StatefulComponent {
|
|
269
|
-
static __isComponent = true as const;
|
|
270
|
-
static __isStateless = false;
|
|
271
|
-
render() {
|
|
272
|
-
seenRef = (getComponentProps(this) as Record<string, unknown>).ref;
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
const scope = new Scope();
|
|
277
|
-
const refFn = () => {};
|
|
278
|
-
mountComponent(RefCheckComp, { ref: refFn }, scope);
|
|
279
|
-
expect(seenRef).toBeUndefined();
|
|
280
|
-
scope.dispose();
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it("component with no render() method — error reaches onError handler", () => {
|
|
284
|
-
let caughtError: Error | undefined;
|
|
285
|
-
class NoRenderComp extends StatefulComponent {
|
|
286
|
-
static __isComponent = true as const;
|
|
287
|
-
static __isStateless = false;
|
|
288
|
-
onError(err: Error): undefined { caughtError = err; }
|
|
289
|
-
render(): Node[] { throw new TypeError("render is not implemented"); }
|
|
290
|
-
}
|
|
291
|
-
const scope = new Scope();
|
|
292
|
-
// mountComponent catches the TypeError and routes it through onError
|
|
293
|
-
expect(() => mountComponent(NoRenderComp as never, {}, scope)).not.toThrow();
|
|
294
|
-
expect(caughtError).toBeInstanceOf(Error);
|
|
295
|
-
scope.dispose();
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
describe("mountComponent onMount scheduling", () => {
|
|
300
|
-
afterEach(() => {
|
|
301
|
-
vi.unstubAllGlobals();
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
function stubMicrotasks(): Array<() => void> {
|
|
305
|
-
const jobs: Array<() => void> = [];
|
|
306
|
-
vi.stubGlobal("queueMicrotask", (fn: () => void) => { jobs.push(fn); });
|
|
307
|
-
return jobs;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function lifecycleComp(onMount: () => void) {
|
|
311
|
-
return class extends StatefulComponent {
|
|
312
|
-
static __isComponent = true as const;
|
|
313
|
-
static __isStateless = false;
|
|
314
|
-
onMount() { onMount(); }
|
|
315
|
-
render() { return null; }
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
it("drains every component mounted in the same pass with a single microtask, in mount order", () => {
|
|
320
|
-
const jobs = stubMicrotasks();
|
|
321
|
-
const order: string[] = [];
|
|
322
|
-
const scope = new Scope();
|
|
323
|
-
mountComponent(lifecycleComp(() => order.push("a")), {}, scope);
|
|
324
|
-
mountComponent(lifecycleComp(() => order.push("b")), {}, scope);
|
|
325
|
-
expect(jobs).toHaveLength(1);
|
|
326
|
-
jobs.shift()?.();
|
|
327
|
-
expect(order).toEqual(["a", "b"]);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it("schedules a new microtask for components mounted after a drain", () => {
|
|
331
|
-
const jobs = stubMicrotasks();
|
|
332
|
-
const onMount = vi.fn();
|
|
333
|
-
const scope = new Scope();
|
|
334
|
-
mountComponent(lifecycleComp(onMount), {}, scope);
|
|
335
|
-
jobs.shift()?.();
|
|
336
|
-
mountComponent(lifecycleComp(onMount), {}, scope);
|
|
337
|
-
expect(jobs).toHaveLength(1);
|
|
338
|
-
jobs.shift()?.();
|
|
339
|
-
expect(onMount).toHaveBeenCalledTimes(2);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it("a throwing onMount does not stop the others and is rethrown on its own microtask", () => {
|
|
343
|
-
const jobs = stubMicrotasks();
|
|
344
|
-
const second = vi.fn();
|
|
345
|
-
const scope = new Scope();
|
|
346
|
-
mountComponent(lifecycleComp(() => { throw new Error("mount boom"); }), {}, scope);
|
|
347
|
-
mountComponent(lifecycleComp(second), {}, scope);
|
|
348
|
-
jobs.shift()?.();
|
|
349
|
-
expect(second).toHaveBeenCalledOnce();
|
|
350
|
-
expect(jobs).toHaveLength(1);
|
|
351
|
-
expect(() => { jobs.shift()?.(); }).toThrow("mount boom");
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it("skips onMount for a component disposed before the drain", () => {
|
|
355
|
-
const jobs = stubMicrotasks();
|
|
356
|
-
const onMount = vi.fn();
|
|
357
|
-
const scope = new Scope();
|
|
358
|
-
mountComponent(lifecycleComp(onMount), {}, scope);
|
|
359
|
-
scope.dispose();
|
|
360
|
-
jobs.shift()?.();
|
|
361
|
-
expect(onMount).not.toHaveBeenCalled();
|
|
362
|
-
});
|
|
363
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { runInScope, getCurrentScope } from "../context";
|
|
4
|
-
import { Scope } from "../scope";
|
|
5
|
-
|
|
6
|
-
describe("runInScope / getCurrentScope", () => {
|
|
7
|
-
it("getCurrentScope throws when called outside of a render context", () => {
|
|
8
|
-
expect(() => getCurrentScope()).toThrow("[PraxisJS]");
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("runInScope makes the scope available via getCurrentScope", () => {
|
|
12
|
-
const scope = new Scope();
|
|
13
|
-
let captured: Scope | null = null;
|
|
14
|
-
runInScope(scope, () => {
|
|
15
|
-
captured = getCurrentScope();
|
|
16
|
-
});
|
|
17
|
-
expect(captured).toBe(scope);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("restores previous scope after runInScope exits", () => {
|
|
21
|
-
const outer = new Scope();
|
|
22
|
-
const inner = new Scope();
|
|
23
|
-
let innerCaptured: Scope | null = null;
|
|
24
|
-
let outerAfter: Scope | null = null;
|
|
25
|
-
|
|
26
|
-
runInScope(outer, () => {
|
|
27
|
-
runInScope(inner, () => {
|
|
28
|
-
innerCaptured = getCurrentScope();
|
|
29
|
-
});
|
|
30
|
-
outerAfter = getCurrentScope();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
expect(innerCaptured).toBe(inner);
|
|
34
|
-
expect(outerAfter).toBe(outer);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("restores null scope even when the callback throws", () => {
|
|
38
|
-
const scope = new Scope();
|
|
39
|
-
expect(() =>
|
|
40
|
-
runInScope(scope, () => {
|
|
41
|
-
throw new Error("boom");
|
|
42
|
-
}),
|
|
43
|
-
).toThrow("boom");
|
|
44
|
-
|
|
45
|
-
expect(() => getCurrentScope()).toThrow("[PraxisJS]");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("returns the callback's return value", () => {
|
|
49
|
-
const scope = new Scope();
|
|
50
|
-
const result = runInScope(scope, () => 42);
|
|
51
|
-
expect(result).toBe(42);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("multiple sequential runInScope() calls at same nesting level do not leak state", () => {
|
|
55
|
-
const a = new Scope();
|
|
56
|
-
const b = new Scope();
|
|
57
|
-
let capturedA: Scope | null = null;
|
|
58
|
-
let capturedB: Scope | null = null;
|
|
59
|
-
runInScope(a, () => { capturedA = getCurrentScope(); });
|
|
60
|
-
runInScope(b, () => { capturedB = getCurrentScope(); });
|
|
61
|
-
expect(capturedA).toBe(a);
|
|
62
|
-
expect(capturedB).toBe(b);
|
|
63
|
-
// After both calls, we should be back outside any scope
|
|
64
|
-
expect(() => getCurrentScope()).toThrow("[PraxisJS]");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("very deeply nested runInScope() (5+ levels) restores all scopes correctly", () => {
|
|
68
|
-
const scopes = Array.from({ length: 6 }, () => new Scope());
|
|
69
|
-
const captured: (Scope | null)[] = [];
|
|
70
|
-
|
|
71
|
-
runInScope(scopes[0], () => {
|
|
72
|
-
captured.push(getCurrentScope());
|
|
73
|
-
runInScope(scopes[1], () => {
|
|
74
|
-
captured.push(getCurrentScope());
|
|
75
|
-
runInScope(scopes[2], () => {
|
|
76
|
-
captured.push(getCurrentScope());
|
|
77
|
-
runInScope(scopes[3], () => {
|
|
78
|
-
captured.push(getCurrentScope());
|
|
79
|
-
runInScope(scopes[4], () => {
|
|
80
|
-
captured.push(getCurrentScope());
|
|
81
|
-
runInScope(scopes[5], () => {
|
|
82
|
-
captured.push(getCurrentScope());
|
|
83
|
-
});
|
|
84
|
-
captured.push(getCurrentScope()); // back to 4
|
|
85
|
-
});
|
|
86
|
-
captured.push(getCurrentScope()); // back to 3
|
|
87
|
-
});
|
|
88
|
-
captured.push(getCurrentScope()); // back to 2
|
|
89
|
-
});
|
|
90
|
-
captured.push(getCurrentScope()); // back to 1
|
|
91
|
-
});
|
|
92
|
-
captured.push(getCurrentScope()); // back to 0
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
expect(captured).toEqual([
|
|
96
|
-
scopes[0],
|
|
97
|
-
scopes[1],
|
|
98
|
-
scopes[2],
|
|
99
|
-
scopes[3],
|
|
100
|
-
scopes[4],
|
|
101
|
-
scopes[5],
|
|
102
|
-
scopes[4],
|
|
103
|
-
scopes[3],
|
|
104
|
-
scopes[2],
|
|
105
|
-
scopes[1],
|
|
106
|
-
scopes[0],
|
|
107
|
-
]);
|
|
108
|
-
expect(() => getCurrentScope()).toThrow("[PraxisJS]");
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("exception at different nesting depths always restores outer scope", () => {
|
|
112
|
-
const outer = new Scope();
|
|
113
|
-
const middle = new Scope();
|
|
114
|
-
|
|
115
|
-
// Exception in middle scope restores outer
|
|
116
|
-
expect(() => {
|
|
117
|
-
runInScope(outer, () => {
|
|
118
|
-
runInScope(middle, () => {
|
|
119
|
-
throw new Error("middle throws");
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
}).toThrow("middle throws");
|
|
123
|
-
|
|
124
|
-
// outer is still active since the outer runInScope completed normally
|
|
125
|
-
// (exception propagated through it too), so after all we should be back to null
|
|
126
|
-
expect(() => getCurrentScope()).toThrow("[PraxisJS]");
|
|
127
|
-
});
|
|
128
|
-
});
|