@pyreon/runtime-dom 0.7.12 → 0.7.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/runtime-dom",
3
- "version": "0.7.12",
3
+ "version": "0.7.14",
4
4
  "description": "DOM renderer for Pyreon",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,8 +39,8 @@
39
39
  "prepublishOnly": "bun run build"
40
40
  },
41
41
  "dependencies": {
42
- "@pyreon/core": "^0.7.12",
43
- "@pyreon/reactivity": "^0.7.12"
42
+ "@pyreon/core": "^0.7.14",
43
+ "@pyreon/reactivity": "^0.7.14"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@happy-dom/global-registrator": "^20.8.3",
@@ -3175,3 +3175,38 @@ describe("TransitionGroup — cleanup", () => {
3175
3175
  expect(el.innerHTML).toBe("")
3176
3176
  })
3177
3177
  })
3178
+
3179
+ // ─── Error paths (no ErrorBoundary) ──────────────────────────────────────────
3180
+
3181
+ describe("mount — error paths", () => {
3182
+ test("component that throws during setup fires console.error", () => {
3183
+ const el = container()
3184
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {})
3185
+
3186
+ const Broken: ComponentFn = () => {
3187
+ throw new Error("setup boom")
3188
+ }
3189
+ mount(h(Broken, null), el)
3190
+
3191
+ expect(spy).toHaveBeenCalledWith(
3192
+ expect.stringContaining("threw during setup"),
3193
+ expect.any(Error),
3194
+ )
3195
+ spy.mockRestore()
3196
+ })
3197
+
3198
+ test("component that throws during render fires console.error", () => {
3199
+ const el = container()
3200
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {})
3201
+
3202
+ const BrokenChild: ComponentFn = () => {
3203
+ throw new Error("render boom")
3204
+ }
3205
+ // Parent returns a child that throws when mounted (render phase)
3206
+ const Parent: ComponentFn = () => h(BrokenChild, null)
3207
+ mount(h(Parent, null), el)
3208
+
3209
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining("threw during"), expect.any(Error))
3210
+ spy.mockRestore()
3211
+ })
3212
+ })
@@ -0,0 +1,194 @@
1
+ import { computed, signal } from "@pyreon/reactivity"
2
+ import { _bindDirect, _bindText } from "../template"
3
+
4
+ // ─── _bindText ──────────────────────────────────────────────────────────────
5
+
6
+ describe("_bindText", () => {
7
+ test("fast path: signal source sets text and updates reactively", () => {
8
+ const s = signal("hello")
9
+ const node = document.createTextNode("")
10
+
11
+ const dispose = _bindText(s, node)
12
+ expect(node.data).toBe("hello")
13
+
14
+ s.set("world")
15
+ expect(node.data).toBe("world")
16
+
17
+ dispose()
18
+ })
19
+
20
+ test("fast path: computed source sets text and updates reactively", () => {
21
+ const s = signal(2)
22
+ const doubled = computed(() => s() * 2)
23
+ const node = document.createTextNode("")
24
+
25
+ const dispose = _bindText(doubled, node)
26
+ expect(node.data).toBe("4")
27
+
28
+ s.set(5)
29
+ expect(node.data).toBe("10")
30
+
31
+ dispose()
32
+ })
33
+
34
+ test("fallback: plain function source uses renderEffect", () => {
35
+ const s = signal("initial")
36
+ // Plain function — no .direct property
37
+ const getter = () => s()
38
+ const node = document.createTextNode("")
39
+
40
+ const dispose = _bindText(getter as unknown as Parameters<typeof _bindText>[0], node)
41
+ expect(node.data).toBe("initial")
42
+
43
+ s.set("updated")
44
+ expect(node.data).toBe("updated")
45
+
46
+ dispose()
47
+ })
48
+
49
+ test("disposal stops updates for signal source", () => {
50
+ const s = signal("a")
51
+ const node = document.createTextNode("")
52
+
53
+ const dispose = _bindText(s, node)
54
+ expect(node.data).toBe("a")
55
+
56
+ dispose()
57
+
58
+ s.set("b")
59
+ expect(node.data).toBe("a")
60
+ })
61
+
62
+ test("disposal stops updates for computed source", () => {
63
+ const s = signal(1)
64
+ const c = computed(() => s() + 10)
65
+ const node = document.createTextNode("")
66
+
67
+ const dispose = _bindText(c, node)
68
+ expect(node.data).toBe("11")
69
+
70
+ dispose()
71
+
72
+ s.set(2)
73
+ expect(node.data).toBe("11")
74
+ })
75
+
76
+ test("disposal stops updates for plain function source", () => {
77
+ const s = signal("x")
78
+ const getter = () => s()
79
+ const node = document.createTextNode("")
80
+
81
+ const dispose = _bindText(getter as unknown as Parameters<typeof _bindText>[0], node)
82
+ expect(node.data).toBe("x")
83
+
84
+ dispose()
85
+
86
+ s.set("y")
87
+ expect(node.data).toBe("x")
88
+ })
89
+
90
+ test("null value renders as empty string", () => {
91
+ const s = signal<string | null>("text")
92
+ const node = document.createTextNode("")
93
+
94
+ const dispose = _bindText(s, node)
95
+ expect(node.data).toBe("text")
96
+
97
+ s.set(null)
98
+ expect(node.data).toBe("")
99
+
100
+ dispose()
101
+ })
102
+
103
+ test("false value renders as empty string", () => {
104
+ const s = signal<string | false>("text")
105
+ const node = document.createTextNode("")
106
+
107
+ const dispose = _bindText(s, node)
108
+ s.set(false)
109
+ expect(node.data).toBe("")
110
+
111
+ dispose()
112
+ })
113
+
114
+ test("undefined value renders as empty string", () => {
115
+ const s = signal<string | undefined>("text")
116
+ const node = document.createTextNode("")
117
+
118
+ const dispose = _bindText(s, node)
119
+ s.set(undefined)
120
+ expect(node.data).toBe("")
121
+
122
+ dispose()
123
+ })
124
+ })
125
+
126
+ // ─── _bindDirect ────────────────────────────────────────────────────────────
127
+
128
+ describe("_bindDirect", () => {
129
+ test("fast path: signal source calls updater immediately and on change", () => {
130
+ const s = signal("red")
131
+ const el = document.createElement("div")
132
+
133
+ const dispose = _bindDirect(s, (v) => {
134
+ el.className = String(v)
135
+ })
136
+
137
+ expect(el.className).toBe("red")
138
+
139
+ s.set("blue")
140
+ expect(el.className).toBe("blue")
141
+
142
+ dispose()
143
+ })
144
+
145
+ test("fallback: plain function source uses renderEffect", () => {
146
+ const s = signal(10)
147
+ const getter = () => s()
148
+ const el = document.createElement("div")
149
+
150
+ const dispose = _bindDirect(getter as unknown as Parameters<typeof _bindDirect>[0], (v) => {
151
+ el.style.width = `${v}px`
152
+ })
153
+
154
+ expect(el.style.width).toBe("10px")
155
+
156
+ s.set(20)
157
+ expect(el.style.width).toBe("20px")
158
+
159
+ dispose()
160
+ })
161
+
162
+ test("disposal stops updates for signal source", () => {
163
+ const s = signal("a")
164
+ const el = document.createElement("div")
165
+
166
+ const dispose = _bindDirect(s, (v) => {
167
+ el.setAttribute("data-val", String(v))
168
+ })
169
+
170
+ expect(el.getAttribute("data-val")).toBe("a")
171
+
172
+ dispose()
173
+
174
+ s.set("b")
175
+ expect(el.getAttribute("data-val")).toBe("a")
176
+ })
177
+
178
+ test("disposal stops updates for plain function source", () => {
179
+ const s = signal(1)
180
+ const getter = () => s()
181
+ const el = document.createElement("div")
182
+
183
+ const dispose = _bindDirect(getter as unknown as Parameters<typeof _bindDirect>[0], (v) => {
184
+ el.setAttribute("data-num", String(v))
185
+ })
186
+
187
+ expect(el.getAttribute("data-num")).toBe("1")
188
+
189
+ dispose()
190
+
191
+ s.set(2)
192
+ expect(el.getAttribute("data-num")).toBe("1")
193
+ })
194
+ })