@praxisjs/runtime 0.7.1 → 0.8.1
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 +28 -0
- package/dist/dom/events.d.ts.map +1 -1
- package/dist/dom/events.js +163 -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 +61 -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 +184 -16
- package/src/dom/props.ts +70 -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 -347
- 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 -429
- 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 -381
- package/src/__tests__/context.test.ts +0 -128
- package/src/__tests__/dom.test.ts +0 -483
- 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,97 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { describe, it, expect } from "vitest";
|
|
3
|
-
import { signal } from "@praxisjs/core/internal";
|
|
4
|
-
import { render, mountElement } from "../index";
|
|
5
|
-
import { Scope } from "../scope";
|
|
6
|
-
// ── mountElement ─────────────────────────────────────────────────────────────
|
|
7
|
-
describe("mountElement", () => {
|
|
8
|
-
it("creates an element with the given tag", () => {
|
|
9
|
-
const scope = new Scope();
|
|
10
|
-
const el = mountElement("section", {}, scope);
|
|
11
|
-
expect(el.tagName.toLowerCase()).toBe("section");
|
|
12
|
-
});
|
|
13
|
-
it("applies static props", () => {
|
|
14
|
-
const scope = new Scope();
|
|
15
|
-
const el = mountElement("p", { id: "para", class: "text" }, scope);
|
|
16
|
-
expect(el.id).toBe("para");
|
|
17
|
-
expect(el.getAttribute("class")).toBe("text");
|
|
18
|
-
});
|
|
19
|
-
it("mounts string children", () => {
|
|
20
|
-
const scope = new Scope();
|
|
21
|
-
const el = mountElement("p", { children: "hello" }, scope);
|
|
22
|
-
expect(el.textContent).toBe("hello");
|
|
23
|
-
});
|
|
24
|
-
it("mounts reactive prop — updates when signal changes", () => {
|
|
25
|
-
const scope = new Scope();
|
|
26
|
-
const cls = signal("a");
|
|
27
|
-
const el = mountElement("div", { class: () => cls() }, scope);
|
|
28
|
-
expect(el.getAttribute("class")).toBe("a");
|
|
29
|
-
cls.set("b");
|
|
30
|
-
expect(el.getAttribute("class")).toBe("b");
|
|
31
|
-
scope.dispose();
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
// ── render ───────────────────────────────────────────────────────────────────
|
|
35
|
-
describe("render", () => {
|
|
36
|
-
it("mounts content into container", () => {
|
|
37
|
-
const container = document.createElement("div");
|
|
38
|
-
document.body.appendChild(container);
|
|
39
|
-
const scope = new Scope();
|
|
40
|
-
render(() => {
|
|
41
|
-
const el = mountElement("h1", { children: "Hello" }, scope);
|
|
42
|
-
return el;
|
|
43
|
-
}, container);
|
|
44
|
-
expect(container.textContent).toBe("Hello");
|
|
45
|
-
document.body.removeChild(container);
|
|
46
|
-
});
|
|
47
|
-
it("clears container on unmount", () => {
|
|
48
|
-
const container = document.createElement("div");
|
|
49
|
-
document.body.appendChild(container);
|
|
50
|
-
const unmount = render(() => {
|
|
51
|
-
const scope = new Scope();
|
|
52
|
-
return mountElement("p", { children: "bye" }, scope);
|
|
53
|
-
}, container);
|
|
54
|
-
expect(container.textContent).toBe("bye");
|
|
55
|
-
unmount();
|
|
56
|
-
expect(container.innerHTML).toBe("");
|
|
57
|
-
document.body.removeChild(container);
|
|
58
|
-
});
|
|
59
|
-
it("render() factory function throws — container remains cleared, error propagates", () => {
|
|
60
|
-
const container = document.createElement("div");
|
|
61
|
-
container.innerHTML = "<p>old content</p>";
|
|
62
|
-
document.body.appendChild(container);
|
|
63
|
-
expect(() => {
|
|
64
|
-
render(() => {
|
|
65
|
-
throw new Error("factory error");
|
|
66
|
-
}, container);
|
|
67
|
-
}).toThrow("factory error");
|
|
68
|
-
// container was cleared before factory ran
|
|
69
|
-
expect(container.innerHTML).toBe("");
|
|
70
|
-
document.body.removeChild(container);
|
|
71
|
-
});
|
|
72
|
-
it("unmount() called twice — does not crash", () => {
|
|
73
|
-
const container = document.createElement("div");
|
|
74
|
-
document.body.appendChild(container);
|
|
75
|
-
const unmount = render(() => {
|
|
76
|
-
const scope = new Scope();
|
|
77
|
-
return mountElement("p", { children: "hi" }, scope);
|
|
78
|
-
}, container);
|
|
79
|
-
unmount();
|
|
80
|
-
expect(() => unmount()).not.toThrow();
|
|
81
|
-
document.body.removeChild(container);
|
|
82
|
-
});
|
|
83
|
-
it("reactive content re-renders on signal change", () => {
|
|
84
|
-
const container = document.createElement("div");
|
|
85
|
-
document.body.appendChild(container);
|
|
86
|
-
const text = signal("initial");
|
|
87
|
-
render(() => {
|
|
88
|
-
const scope = new Scope();
|
|
89
|
-
return mountElement("span", { children: () => text() }, scope);
|
|
90
|
-
}, container);
|
|
91
|
-
expect(container.textContent).toBe("initial");
|
|
92
|
-
text.set("updated");
|
|
93
|
-
expect(container.textContent).toBe("updated");
|
|
94
|
-
document.body.removeChild(container);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
//# sourceMappingURL=render.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"render.test.js","sourceRoot":"","sources":["../../src/__tests__/render.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scope.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/scope.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { Scope } from "../scope";
|
|
3
|
-
describe("Scope", () => {
|
|
4
|
-
it("add() registers a cleanup fn that runs on dispose()", () => {
|
|
5
|
-
const scope = new Scope();
|
|
6
|
-
const cleanup = vi.fn();
|
|
7
|
-
scope.add(cleanup);
|
|
8
|
-
scope.dispose();
|
|
9
|
-
expect(cleanup).toHaveBeenCalledOnce();
|
|
10
|
-
});
|
|
11
|
-
it("dispose() runs all cleanups in order", () => {
|
|
12
|
-
const scope = new Scope();
|
|
13
|
-
const order = [];
|
|
14
|
-
scope.add(() => order.push(1));
|
|
15
|
-
scope.add(() => order.push(2));
|
|
16
|
-
scope.add(() => order.push(3));
|
|
17
|
-
scope.dispose();
|
|
18
|
-
expect(order).toEqual([1, 2, 3]);
|
|
19
|
-
});
|
|
20
|
-
it("dispose() clears cleanups — second dispose() is a no-op", () => {
|
|
21
|
-
const scope = new Scope();
|
|
22
|
-
const fn = vi.fn();
|
|
23
|
-
scope.add(fn);
|
|
24
|
-
scope.dispose();
|
|
25
|
-
scope.dispose();
|
|
26
|
-
expect(fn).toHaveBeenCalledOnce();
|
|
27
|
-
});
|
|
28
|
-
it("effect() registers a reactive effect and its cleanup", () => {
|
|
29
|
-
const scope = new Scope();
|
|
30
|
-
const ran = [];
|
|
31
|
-
scope.effect(() => {
|
|
32
|
-
ran.push(1);
|
|
33
|
-
});
|
|
34
|
-
expect(ran).toHaveLength(1); // runs immediately
|
|
35
|
-
scope.dispose();
|
|
36
|
-
});
|
|
37
|
-
it("fork() creates a child scope that disposes when parent disposes", () => {
|
|
38
|
-
const parent = new Scope();
|
|
39
|
-
const child = parent.fork();
|
|
40
|
-
const childCleanup = vi.fn();
|
|
41
|
-
child.add(childCleanup);
|
|
42
|
-
parent.dispose();
|
|
43
|
-
expect(childCleanup).toHaveBeenCalledOnce();
|
|
44
|
-
});
|
|
45
|
-
it("cleanup that throws does not prevent other cleanups from running", () => {
|
|
46
|
-
const scope = new Scope();
|
|
47
|
-
const ran = [];
|
|
48
|
-
scope.add(() => { ran.push(1); });
|
|
49
|
-
scope.add(() => { throw new Error("boom"); });
|
|
50
|
-
scope.add(() => { ran.push(3); });
|
|
51
|
-
expect(() => { scope.dispose(); }).toThrow();
|
|
52
|
-
expect(ran).toEqual([1, 3]);
|
|
53
|
-
});
|
|
54
|
-
it("grandchild scopes: parent.fork().fork() — disposing parent disposes all descendants", () => {
|
|
55
|
-
const parent = new Scope();
|
|
56
|
-
const child = parent.fork();
|
|
57
|
-
const grandchild = child.fork();
|
|
58
|
-
const grandchildFn = vi.fn();
|
|
59
|
-
const childFn = vi.fn();
|
|
60
|
-
grandchild.add(grandchildFn);
|
|
61
|
-
child.add(childFn);
|
|
62
|
-
parent.dispose();
|
|
63
|
-
expect(childFn).toHaveBeenCalledOnce();
|
|
64
|
-
expect(grandchildFn).toHaveBeenCalledOnce();
|
|
65
|
-
});
|
|
66
|
-
it("dispose() called during a cleanup does not crash", () => {
|
|
67
|
-
const scope = new Scope();
|
|
68
|
-
scope.add(() => {
|
|
69
|
-
// calling dispose() again during a cleanup — should be safe (no-op, cleanups already cleared)
|
|
70
|
-
scope.dispose();
|
|
71
|
-
});
|
|
72
|
-
expect(() => { scope.dispose(); }).not.toThrow();
|
|
73
|
-
});
|
|
74
|
-
it("cleanup added inside a cleanup is NOT called in the same dispose cycle", () => {
|
|
75
|
-
const scope = new Scope();
|
|
76
|
-
const inner = vi.fn();
|
|
77
|
-
scope.add(() => {
|
|
78
|
-
scope.add(inner);
|
|
79
|
-
});
|
|
80
|
-
scope.dispose();
|
|
81
|
-
expect(inner).not.toHaveBeenCalled();
|
|
82
|
-
});
|
|
83
|
-
it("multiple throwing cleanups — AggregateError is thrown with all errors", () => {
|
|
84
|
-
const scope = new Scope();
|
|
85
|
-
scope.add(() => { throw new Error("error 1"); });
|
|
86
|
-
scope.add(() => { throw new Error("error 2"); });
|
|
87
|
-
expect(() => { scope.dispose(); }).toThrow(AggregateError);
|
|
88
|
-
try {
|
|
89
|
-
const s2 = new Scope();
|
|
90
|
-
s2.add(() => { throw new Error("e1"); });
|
|
91
|
-
s2.add(() => { throw new Error("e2"); });
|
|
92
|
-
s2.dispose();
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
expect(err).toBeInstanceOf(AggregateError);
|
|
96
|
-
expect(err.errors).toHaveLength(2);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
it("fork() child can be disposed independently before parent", () => {
|
|
100
|
-
const parent = new Scope();
|
|
101
|
-
const child = parent.fork();
|
|
102
|
-
const childFn = vi.fn();
|
|
103
|
-
child.add(childFn);
|
|
104
|
-
child.dispose();
|
|
105
|
-
expect(childFn).toHaveBeenCalledOnce();
|
|
106
|
-
// parent dispose should not throw even though child is already disposed
|
|
107
|
-
expect(() => { parent.dispose(); }).not.toThrow();
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
//# sourceMappingURL=scope.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scope.test.js","sourceRoot":"","sources":["../../src/__tests__/scope.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { describe, it, expect } from "vitest";
|
|
3
|
-
|
|
4
|
-
import { signal } from "@praxisjs/core/internal";
|
|
5
|
-
|
|
6
|
-
import { mountChildren } from "../children";
|
|
7
|
-
import { getCurrentScope } from "../context";
|
|
8
|
-
import { mountReactive } from "../reactive";
|
|
9
|
-
import { Scope } from "../scope";
|
|
10
|
-
|
|
11
|
-
function container() {
|
|
12
|
-
return document.createElement("div");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe("mountChildren", () => {
|
|
16
|
-
it("does nothing for null, undefined, or false", () => {
|
|
17
|
-
const el = container();
|
|
18
|
-
const scope = new Scope();
|
|
19
|
-
mountChildren(el, null, scope);
|
|
20
|
-
mountChildren(el, undefined, scope);
|
|
21
|
-
mountChildren(el, false, scope);
|
|
22
|
-
expect(el.childNodes.length).toBe(0);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("appends a text node for a string", () => {
|
|
26
|
-
const el = container();
|
|
27
|
-
const scope = new Scope();
|
|
28
|
-
mountChildren(el, "hello", scope);
|
|
29
|
-
expect(el.textContent).toBe("hello");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("appends a text node for a number", () => {
|
|
33
|
-
const el = container();
|
|
34
|
-
const scope = new Scope();
|
|
35
|
-
mountChildren(el, 42, scope);
|
|
36
|
-
expect(el.textContent).toBe("42");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("appends a DOM node directly", () => {
|
|
40
|
-
const el = container();
|
|
41
|
-
const scope = new Scope();
|
|
42
|
-
const span = document.createElement("span");
|
|
43
|
-
mountChildren(el, span, scope);
|
|
44
|
-
expect(el.firstChild).toBe(span);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("recursively mounts arrays", () => {
|
|
48
|
-
const el = container();
|
|
49
|
-
const scope = new Scope();
|
|
50
|
-
mountChildren(el, ["a", "b", "c"], scope);
|
|
51
|
-
expect(el.textContent).toBe("abc");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("mounts a reactive function — updates when signal changes", () => {
|
|
55
|
-
const el = container();
|
|
56
|
-
const scope = new Scope();
|
|
57
|
-
const s = signal("first");
|
|
58
|
-
mountChildren(el, () => s(), scope);
|
|
59
|
-
expect(el.textContent).toBe("first");
|
|
60
|
-
s.set("second");
|
|
61
|
-
expect(el.textContent).toBe("second");
|
|
62
|
-
scope.dispose();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("reactive children clean up old nodes on update", () => {
|
|
66
|
-
const el = container();
|
|
67
|
-
const scope = new Scope();
|
|
68
|
-
const s = signal<string | null>("visible");
|
|
69
|
-
mountChildren(el, () => s(), scope);
|
|
70
|
-
expect(el.textContent).toBe("visible");
|
|
71
|
-
s.set(null);
|
|
72
|
-
expect(el.textContent).toBe("");
|
|
73
|
-
scope.dispose();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("reactive function returning an array of nodes renders all of them", () => {
|
|
77
|
-
const el = container();
|
|
78
|
-
const scope = new Scope();
|
|
79
|
-
mountChildren(el, () => ["x", "y", "z"], scope);
|
|
80
|
-
expect(el.textContent).toBe("xyz");
|
|
81
|
-
scope.dispose();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("reactive function returning a Node renders it", () => {
|
|
85
|
-
const el = container();
|
|
86
|
-
const scope = new Scope();
|
|
87
|
-
const node = document.createElement("em");
|
|
88
|
-
node.textContent = "em";
|
|
89
|
-
mountChildren(el, () => node, scope);
|
|
90
|
-
expect(el.textContent).toBe("em");
|
|
91
|
-
scope.dispose();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("reactive function returning a number renders it", () => {
|
|
95
|
-
const el = container();
|
|
96
|
-
const scope = new Scope();
|
|
97
|
-
mountChildren(el, () => 42, scope);
|
|
98
|
-
expect(el.textContent).toBe("42");
|
|
99
|
-
scope.dispose();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("reactive function returning an unrecognized type renders nothing", () => {
|
|
103
|
-
const el = container();
|
|
104
|
-
const scope = new Scope();
|
|
105
|
-
mountChildren(el, () => ({ unknown: "object" }) as unknown, scope);
|
|
106
|
-
expect(el.textContent).toBe(""); // normalizeToNodes returns [] for unknown types
|
|
107
|
-
scope.dispose();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("deeply nested arrays [[[[\"text\"]]]] — all values flattened", () => {
|
|
111
|
-
const el = container();
|
|
112
|
-
const scope = new Scope();
|
|
113
|
-
mountChildren(el, [[[["deep"]]]] as unknown, scope);
|
|
114
|
-
expect(el.textContent).toBe("deep");
|
|
115
|
-
scope.dispose();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("mixed content [\"text\", 42, null, false, domNode, () => \"reactive\"] — each handled correctly", () => {
|
|
119
|
-
const el = container();
|
|
120
|
-
const scope = new Scope();
|
|
121
|
-
const span = document.createElement("span");
|
|
122
|
-
span.textContent = "node";
|
|
123
|
-
mountChildren(el, ["text", 42, null, false, span, () => "reactive"], scope);
|
|
124
|
-
// "text", "42", span ("node"), "reactive" — null and false are skipped
|
|
125
|
-
expect(el.textContent).toBe("text42nodereactive");
|
|
126
|
-
scope.dispose();
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("reactive function that changes return type (string → array → null) — old nodes cleaned up", () => {
|
|
130
|
-
const el = container();
|
|
131
|
-
const scope = new Scope();
|
|
132
|
-
const s = signal<unknown>("hello");
|
|
133
|
-
mountChildren(el, () => s(), scope);
|
|
134
|
-
expect(el.textContent).toBe("hello");
|
|
135
|
-
s.set(["a", "b"]);
|
|
136
|
-
expect(el.textContent).toBe("ab");
|
|
137
|
-
s.set(null);
|
|
138
|
-
expect(el.textContent).toBe("");
|
|
139
|
-
scope.dispose();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it("disposes only the active reactive child scope on parent dispose", () => {
|
|
143
|
-
const el = container();
|
|
144
|
-
const scope = new Scope();
|
|
145
|
-
const s = signal("first");
|
|
146
|
-
const cleanups: string[] = [];
|
|
147
|
-
|
|
148
|
-
mountChildren(el, () => {
|
|
149
|
-
const value = s();
|
|
150
|
-
getCurrentScope().add(() => cleanups.push(value));
|
|
151
|
-
return value;
|
|
152
|
-
}, scope);
|
|
153
|
-
|
|
154
|
-
s.set("second");
|
|
155
|
-
expect(cleanups).toEqual(["first"]);
|
|
156
|
-
|
|
157
|
-
scope.dispose();
|
|
158
|
-
expect(cleanups).toEqual(["first", "second"]);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("non-array object passed directly — falls through and mounts nothing", () => {
|
|
162
|
-
const el = container();
|
|
163
|
-
const scope = new Scope();
|
|
164
|
-
mountChildren(el, { some: "object" } as unknown, scope);
|
|
165
|
-
expect(el.childNodes.length).toBe(0);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("same Node instance passed twice — second append moves the node (not duplicated)", () => {
|
|
169
|
-
const el = container();
|
|
170
|
-
const scope = new Scope();
|
|
171
|
-
const node = document.createElement("span");
|
|
172
|
-
node.textContent = "x";
|
|
173
|
-
mountChildren(el, [node, node], scope);
|
|
174
|
-
// DOM spec: inserting an already-inserted node moves it, so there's only one instance
|
|
175
|
-
expect(el.querySelectorAll("span").length).toBe(1);
|
|
176
|
-
scope.dispose();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("reactive function returning null after returning content — content is removed", () => {
|
|
180
|
-
const el = container();
|
|
181
|
-
const scope = new Scope();
|
|
182
|
-
const s = signal<string | null>("visible");
|
|
183
|
-
mountChildren(el, () => s(), scope);
|
|
184
|
-
expect(el.textContent).toBe("visible");
|
|
185
|
-
s.set(null);
|
|
186
|
-
expect(el.textContent).toBe("");
|
|
187
|
-
scope.dispose();
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// ── mountReactive (direct) ────────────────────────────────────────────────────
|
|
192
|
-
|
|
193
|
-
describe("mountReactive", () => {
|
|
194
|
-
it("falls back to parent as anchor when the end comment is detached (parentNode ?? parent branch)", () => {
|
|
195
|
-
const parent = document.createElement("div");
|
|
196
|
-
document.body.appendChild(parent);
|
|
197
|
-
const scope = new Scope();
|
|
198
|
-
const s = signal(0);
|
|
199
|
-
|
|
200
|
-
// fn reads s (subscribes) but always returns null → never creates DOM nodes
|
|
201
|
-
mountReactive(parent, () => { s(); return null; }, scope);
|
|
202
|
-
|
|
203
|
-
// Remove the end-anchor comment so end.parentNode becomes null
|
|
204
|
-
const endComment = Array.from(parent.childNodes).find(
|
|
205
|
-
(n): n is Comment => n.nodeType === Node.COMMENT_NODE,
|
|
206
|
-
);
|
|
207
|
-
expect(endComment).toBeDefined();
|
|
208
|
-
parent.removeChild(endComment!);
|
|
209
|
-
|
|
210
|
-
// Trigger re-run: s was 0, now 1 → Object.is differs → effect fires
|
|
211
|
-
// end.parentNode === null → anchor = null ?? parent = parent (the ?? branch)
|
|
212
|
-
// currentNodes = [], newNodes = [] → no remove/insert, no crash
|
|
213
|
-
expect(() => s.set(1)).not.toThrow();
|
|
214
|
-
|
|
215
|
-
scope.dispose();
|
|
216
|
-
document.body.removeChild(parent);
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
describe("fine-grained rows", () => {
|
|
221
|
-
function setup() {
|
|
222
|
-
const ids = signal([1, 2]);
|
|
223
|
-
const titles = signal<Record<number, string>>({ 1: "a", 2: "b" });
|
|
224
|
-
let listRuns = 0;
|
|
225
|
-
const ul = container();
|
|
226
|
-
const scope = new Scope();
|
|
227
|
-
mountChildren(ul, () => {
|
|
228
|
-
listRuns++;
|
|
229
|
-
return ids().map((id) => {
|
|
230
|
-
const li = document.createElement("li");
|
|
231
|
-
mountChildren(li, () => titles()[id], scope);
|
|
232
|
-
return li;
|
|
233
|
-
});
|
|
234
|
-
}, scope);
|
|
235
|
-
return { ids, titles, ul, scope, runs: () => listRuns };
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
it("changing row content updates text in place without re-running the list function", () => {
|
|
239
|
-
const { titles, ul, scope, runs } = setup();
|
|
240
|
-
const rows = [...ul.querySelectorAll("li")];
|
|
241
|
-
titles.set({ 1: "A", 2: "b" });
|
|
242
|
-
expect(runs()).toBe(1);
|
|
243
|
-
expect([...ul.querySelectorAll("li")]).toEqual(rows);
|
|
244
|
-
expect(rows.map((r) => r.textContent)).toEqual(["A", "b"]);
|
|
245
|
-
scope.dispose();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it("changing the structure rebuilds the list", () => {
|
|
249
|
-
const { ids, titles, ul, scope, runs } = setup();
|
|
250
|
-
titles.set({ 1: "a", 2: "b", 3: "c" });
|
|
251
|
-
ids.set([1, 2, 3]);
|
|
252
|
-
expect(runs()).toBe(2);
|
|
253
|
-
expect(ul.querySelectorAll("li")).toHaveLength(3);
|
|
254
|
-
scope.dispose();
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
describe("mountReactive multi-node replacement", () => {
|
|
259
|
-
it("removes nodes that were inserted between the tracked ones after mount", () => {
|
|
260
|
-
const toggle = signal(0);
|
|
261
|
-
const parent = container();
|
|
262
|
-
const scope = new Scope();
|
|
263
|
-
let a!: Node;
|
|
264
|
-
mountChildren(parent, () => {
|
|
265
|
-
void toggle();
|
|
266
|
-
a = document.createElement("a");
|
|
267
|
-
return [a, document.createElement("b")];
|
|
268
|
-
}, scope);
|
|
269
|
-
const stray = document.createElement("stray");
|
|
270
|
-
parent.insertBefore(stray, a.nextSibling);
|
|
271
|
-
toggle.set(1);
|
|
272
|
-
expect(parent.contains(stray)).toBe(false);
|
|
273
|
-
expect([...parent.children].map((c) => c.localName)).toEqual(["a", "b"]);
|
|
274
|
-
scope.dispose();
|
|
275
|
-
});
|
|
276
|
-
});
|