@pyreon/runtime-dom 0.11.2 → 0.11.4

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.
@@ -0,0 +1,177 @@
1
+ import { createContext, Fragment, h, provide, useContext } from "@pyreon/core"
2
+ import { signal } from "@pyreon/reactivity"
3
+ import { mount } from "@pyreon/runtime-dom"
4
+ import { describe, expect, it } from "vitest"
5
+
6
+ const TestCtx = createContext("default")
7
+
8
+ describe("context inheritance through reactive boundaries", () => {
9
+ it("child inside reactive accessor inherits parent context", async () => {
10
+ let childValue: string | undefined
11
+
12
+ function Child() {
13
+ childValue = useContext(TestCtx)
14
+ return h("span", null, childValue)
15
+ }
16
+
17
+ function Parent() {
18
+ provide(TestCtx, "from-parent")
19
+ const show = signal(false)
20
+ setTimeout(() => show.set(true), 10)
21
+ return () => (show() ? h(Child, null) : null)
22
+ }
23
+
24
+ const container = document.createElement("div")
25
+ mount(h(Parent, null), container)
26
+ await new Promise((r) => setTimeout(r, 50))
27
+ expect(childValue).toBe("from-parent")
28
+ })
29
+
30
+ it("deeply nested context survives through multiple reactive layers", async () => {
31
+ let innerValue: string | undefined
32
+
33
+ function Inner() {
34
+ innerValue = useContext(TestCtx)
35
+ return h("span", null, innerValue)
36
+ }
37
+
38
+ function Middle() {
39
+ const show = signal(false)
40
+ setTimeout(() => show.set(true), 10)
41
+ return () => (show() ? h(Inner, null) : null)
42
+ }
43
+
44
+ function Outer() {
45
+ provide(TestCtx, "outer-value")
46
+ const show = signal(false)
47
+ setTimeout(() => show.set(true), 5)
48
+ return () => (show() ? h(Middle, null) : null)
49
+ }
50
+
51
+ const container = document.createElement("div")
52
+ mount(h(Outer, null), container)
53
+ await new Promise((r) => setTimeout(r, 100))
54
+ expect(innerValue).toBe("outer-value")
55
+ })
56
+
57
+ it("sibling providers don't leak context to each other", async () => {
58
+ let childAValue: string | undefined
59
+ let childBValue: string | undefined
60
+
61
+ function ChildA() {
62
+ childAValue = useContext(TestCtx)
63
+ return h("span", null, childAValue)
64
+ }
65
+
66
+ function ChildB() {
67
+ childBValue = useContext(TestCtx)
68
+ return h("span", null, childBValue)
69
+ }
70
+
71
+ function ProviderA() {
72
+ provide(TestCtx, "A")
73
+ return h(ChildA, null)
74
+ }
75
+
76
+ function ProviderB() {
77
+ provide(TestCtx, "B")
78
+ return h(ChildB, null)
79
+ }
80
+
81
+ function App() {
82
+ return h(Fragment, null, h(ProviderA, null), h(ProviderB, null))
83
+ }
84
+
85
+ const container = document.createElement("div")
86
+ mount(h(App, null), container)
87
+ await new Promise((r) => setTimeout(r, 50))
88
+
89
+ expect(childAValue).toBe("A")
90
+ expect(childBValue).toBe("B")
91
+ })
92
+
93
+ it("Show toggle preserves context across hide/show cycle", async () => {
94
+ let childValue: string | undefined
95
+ let mountCount = 0
96
+
97
+ function Child() {
98
+ mountCount++
99
+ childValue = useContext(TestCtx)
100
+ return h("span", null, childValue)
101
+ }
102
+
103
+ function Parent() {
104
+ provide(TestCtx, "persistent")
105
+ const show = signal(true)
106
+
107
+ // Hide then show again
108
+ setTimeout(() => show.set(false), 10)
109
+ setTimeout(() => show.set(true), 30)
110
+
111
+ return () => (show() ? h(Child, null) : null)
112
+ }
113
+
114
+ const container = document.createElement("div")
115
+ mount(h(Parent, null), container)
116
+ await new Promise((r) => setTimeout(r, 100))
117
+
118
+ expect(childValue).toBe("persistent")
119
+ expect(mountCount).toBe(2) // mounted twice (initial + re-show)
120
+ })
121
+
122
+ it("reactive context getter updates JSX without re-running component", async () => {
123
+ const ModeCtx = createContext<() => string>(() => "light")
124
+ let renderCount = 0
125
+
126
+ function Child() {
127
+ const getMode = useContext(ModeCtx)
128
+ renderCount++
129
+ // Reading the getter inside a reactive accessor — updates when mode changes
130
+ return h("span", null, () => getMode())
131
+ }
132
+
133
+ function Parent() {
134
+ const mode = signal<string>("light")
135
+ provide(ModeCtx, () => mode())
136
+ setTimeout(() => mode.set("dark"), 10)
137
+ return h(Child, null)
138
+ }
139
+
140
+ const container = document.createElement("div")
141
+ mount(h(Parent, null), container)
142
+
143
+ expect(container.textContent).toBe("light")
144
+
145
+ await new Promise((r) => setTimeout(r, 50))
146
+ expect(container.textContent).toBe("dark")
147
+ // Component setup ran once — JSX expression re-evaluated reactively
148
+ expect(renderCount).toBe(1)
149
+ })
150
+
151
+ it("nested Show inside For with context", async () => {
152
+ const ItemCtx = createContext("none")
153
+ const collected: string[] = []
154
+
155
+ function Item() {
156
+ const val = useContext(ItemCtx)
157
+ collected.push(val)
158
+ return h("li", null, val)
159
+ }
160
+
161
+ function Parent() {
162
+ provide(ItemCtx, "parent-provided")
163
+ const items = signal([1, 2, 3])
164
+ const show = signal(false)
165
+ setTimeout(() => show.set(true), 10)
166
+
167
+ return () => (show() ? h("ul", null, ...items().map((i) => h(Item, { key: i }))) : null)
168
+ }
169
+
170
+ const container = document.createElement("div")
171
+ mount(h(Parent, null), container)
172
+ await new Promise((r) => setTimeout(r, 50))
173
+
174
+ expect(collected.length).toBe(3)
175
+ expect(collected.every((v) => v === "parent-provided")).toBe(true)
176
+ })
177
+ })