@pyreon/runtime-dom 0.11.4 → 0.11.6
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/README.md +16 -22
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +4 -3
- package/lib/index.js.map +1 -1
- package/package.json +12 -12
- package/src/delegate.ts +25 -25
- package/src/devtools.ts +37 -37
- package/src/hydrate.ts +30 -30
- package/src/hydration-debug.ts +2 -2
- package/src/index.ts +20 -20
- package/src/keep-alive.ts +6 -6
- package/src/mount.ts +46 -46
- package/src/nodes.ts +31 -19
- package/src/props.ts +93 -93
- package/src/template.ts +6 -6
- package/src/tests/coverage-gaps.test.ts +669 -669
- package/src/tests/coverage.test.ts +299 -299
- package/src/tests/mount.test.ts +1183 -1183
- package/src/tests/props.test.ts +219 -219
- package/src/tests/setup.ts +1 -1
- package/src/tests/show-context.test.ts +43 -43
- package/src/tests/template.test.ts +71 -71
- package/src/tests/transition.test.ts +124 -124
- package/src/transition-group.ts +22 -22
- package/src/transition.ts +18 -18
package/src/tests/setup.ts
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { createContext, Fragment, h, provide, useContext } from
|
|
2
|
-
import { signal } from
|
|
3
|
-
import { mount } from
|
|
4
|
-
import { describe, expect, it } from
|
|
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
5
|
|
|
6
|
-
const TestCtx = createContext(
|
|
6
|
+
const TestCtx = createContext('default')
|
|
7
7
|
|
|
8
|
-
describe(
|
|
9
|
-
it(
|
|
8
|
+
describe('context inheritance through reactive boundaries', () => {
|
|
9
|
+
it('child inside reactive accessor inherits parent context', async () => {
|
|
10
10
|
let childValue: string | undefined
|
|
11
11
|
|
|
12
12
|
function Child() {
|
|
13
13
|
childValue = useContext(TestCtx)
|
|
14
|
-
return h(
|
|
14
|
+
return h('span', null, childValue)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function Parent() {
|
|
18
|
-
provide(TestCtx,
|
|
18
|
+
provide(TestCtx, 'from-parent')
|
|
19
19
|
const show = signal(false)
|
|
20
20
|
setTimeout(() => show.set(true), 10)
|
|
21
21
|
return () => (show() ? h(Child, null) : null)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const container = document.createElement(
|
|
24
|
+
const container = document.createElement('div')
|
|
25
25
|
mount(h(Parent, null), container)
|
|
26
26
|
await new Promise((r) => setTimeout(r, 50))
|
|
27
|
-
expect(childValue).toBe(
|
|
27
|
+
expect(childValue).toBe('from-parent')
|
|
28
28
|
})
|
|
29
29
|
|
|
30
|
-
it(
|
|
30
|
+
it('deeply nested context survives through multiple reactive layers', async () => {
|
|
31
31
|
let innerValue: string | undefined
|
|
32
32
|
|
|
33
33
|
function Inner() {
|
|
34
34
|
innerValue = useContext(TestCtx)
|
|
35
|
-
return h(
|
|
35
|
+
return h('span', null, innerValue)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function Middle() {
|
|
@@ -42,16 +42,16 @@ describe("context inheritance through reactive boundaries", () => {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function Outer() {
|
|
45
|
-
provide(TestCtx,
|
|
45
|
+
provide(TestCtx, 'outer-value')
|
|
46
46
|
const show = signal(false)
|
|
47
47
|
setTimeout(() => show.set(true), 5)
|
|
48
48
|
return () => (show() ? h(Middle, null) : null)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const container = document.createElement(
|
|
51
|
+
const container = document.createElement('div')
|
|
52
52
|
mount(h(Outer, null), container)
|
|
53
53
|
await new Promise((r) => setTimeout(r, 100))
|
|
54
|
-
expect(innerValue).toBe(
|
|
54
|
+
expect(innerValue).toBe('outer-value')
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
it("sibling providers don't leak context to each other", async () => {
|
|
@@ -60,21 +60,21 @@ describe("context inheritance through reactive boundaries", () => {
|
|
|
60
60
|
|
|
61
61
|
function ChildA() {
|
|
62
62
|
childAValue = useContext(TestCtx)
|
|
63
|
-
return h(
|
|
63
|
+
return h('span', null, childAValue)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function ChildB() {
|
|
67
67
|
childBValue = useContext(TestCtx)
|
|
68
|
-
return h(
|
|
68
|
+
return h('span', null, childBValue)
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function ProviderA() {
|
|
72
|
-
provide(TestCtx,
|
|
72
|
+
provide(TestCtx, 'A')
|
|
73
73
|
return h(ChildA, null)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function ProviderB() {
|
|
77
|
-
provide(TestCtx,
|
|
77
|
+
provide(TestCtx, 'B')
|
|
78
78
|
return h(ChildB, null)
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -82,26 +82,26 @@ describe("context inheritance through reactive boundaries", () => {
|
|
|
82
82
|
return h(Fragment, null, h(ProviderA, null), h(ProviderB, null))
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
const container = document.createElement(
|
|
85
|
+
const container = document.createElement('div')
|
|
86
86
|
mount(h(App, null), container)
|
|
87
87
|
await new Promise((r) => setTimeout(r, 50))
|
|
88
88
|
|
|
89
|
-
expect(childAValue).toBe(
|
|
90
|
-
expect(childBValue).toBe(
|
|
89
|
+
expect(childAValue).toBe('A')
|
|
90
|
+
expect(childBValue).toBe('B')
|
|
91
91
|
})
|
|
92
92
|
|
|
93
|
-
it(
|
|
93
|
+
it('Show toggle preserves context across hide/show cycle', async () => {
|
|
94
94
|
let childValue: string | undefined
|
|
95
95
|
let mountCount = 0
|
|
96
96
|
|
|
97
97
|
function Child() {
|
|
98
98
|
mountCount++
|
|
99
99
|
childValue = useContext(TestCtx)
|
|
100
|
-
return h(
|
|
100
|
+
return h('span', null, childValue)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
function Parent() {
|
|
104
|
-
provide(TestCtx,
|
|
104
|
+
provide(TestCtx, 'persistent')
|
|
105
105
|
const show = signal(true)
|
|
106
106
|
|
|
107
107
|
// Hide then show again
|
|
@@ -111,67 +111,67 @@ describe("context inheritance through reactive boundaries", () => {
|
|
|
111
111
|
return () => (show() ? h(Child, null) : null)
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
const container = document.createElement(
|
|
114
|
+
const container = document.createElement('div')
|
|
115
115
|
mount(h(Parent, null), container)
|
|
116
116
|
await new Promise((r) => setTimeout(r, 100))
|
|
117
117
|
|
|
118
|
-
expect(childValue).toBe(
|
|
118
|
+
expect(childValue).toBe('persistent')
|
|
119
119
|
expect(mountCount).toBe(2) // mounted twice (initial + re-show)
|
|
120
120
|
})
|
|
121
121
|
|
|
122
|
-
it(
|
|
123
|
-
const ModeCtx = createContext<() => string>(() =>
|
|
122
|
+
it('reactive context getter updates JSX without re-running component', async () => {
|
|
123
|
+
const ModeCtx = createContext<() => string>(() => 'light')
|
|
124
124
|
let renderCount = 0
|
|
125
125
|
|
|
126
126
|
function Child() {
|
|
127
127
|
const getMode = useContext(ModeCtx)
|
|
128
128
|
renderCount++
|
|
129
129
|
// Reading the getter inside a reactive accessor — updates when mode changes
|
|
130
|
-
return h(
|
|
130
|
+
return h('span', null, () => getMode())
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
function Parent() {
|
|
134
|
-
const mode = signal<string>(
|
|
134
|
+
const mode = signal<string>('light')
|
|
135
135
|
provide(ModeCtx, () => mode())
|
|
136
|
-
setTimeout(() => mode.set(
|
|
136
|
+
setTimeout(() => mode.set('dark'), 10)
|
|
137
137
|
return h(Child, null)
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
const container = document.createElement(
|
|
140
|
+
const container = document.createElement('div')
|
|
141
141
|
mount(h(Parent, null), container)
|
|
142
142
|
|
|
143
|
-
expect(container.textContent).toBe(
|
|
143
|
+
expect(container.textContent).toBe('light')
|
|
144
144
|
|
|
145
145
|
await new Promise((r) => setTimeout(r, 50))
|
|
146
|
-
expect(container.textContent).toBe(
|
|
146
|
+
expect(container.textContent).toBe('dark')
|
|
147
147
|
// Component setup ran once — JSX expression re-evaluated reactively
|
|
148
148
|
expect(renderCount).toBe(1)
|
|
149
149
|
})
|
|
150
150
|
|
|
151
|
-
it(
|
|
152
|
-
const ItemCtx = createContext(
|
|
151
|
+
it('nested Show inside For with context', async () => {
|
|
152
|
+
const ItemCtx = createContext('none')
|
|
153
153
|
const collected: string[] = []
|
|
154
154
|
|
|
155
155
|
function Item() {
|
|
156
156
|
const val = useContext(ItemCtx)
|
|
157
157
|
collected.push(val)
|
|
158
|
-
return h(
|
|
158
|
+
return h('li', null, val)
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
function Parent() {
|
|
162
|
-
provide(ItemCtx,
|
|
162
|
+
provide(ItemCtx, 'parent-provided')
|
|
163
163
|
const items = signal([1, 2, 3])
|
|
164
164
|
const show = signal(false)
|
|
165
165
|
setTimeout(() => show.set(true), 10)
|
|
166
166
|
|
|
167
|
-
return () => (show() ? h(
|
|
167
|
+
return () => (show() ? h('ul', null, ...items().map((i) => h(Item, { key: i }))) : null)
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
const container = document.createElement(
|
|
170
|
+
const container = document.createElement('div')
|
|
171
171
|
mount(h(Parent, null), container)
|
|
172
172
|
await new Promise((r) => setTimeout(r, 50))
|
|
173
173
|
|
|
174
174
|
expect(collected.length).toBe(3)
|
|
175
|
-
expect(collected.every((v) => v ===
|
|
175
|
+
expect(collected.every((v) => v === 'parent-provided')).toBe(true)
|
|
176
176
|
})
|
|
177
177
|
})
|
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
import { computed, signal } from
|
|
2
|
-
import { _bindDirect, _bindText } from
|
|
1
|
+
import { computed, signal } from '@pyreon/reactivity'
|
|
2
|
+
import { _bindDirect, _bindText } from '../template'
|
|
3
3
|
|
|
4
4
|
// ─── _bindText ──────────────────────────────────────────────────────────────
|
|
5
5
|
|
|
6
|
-
describe(
|
|
7
|
-
test(
|
|
8
|
-
const s = signal(
|
|
9
|
-
const node = document.createTextNode(
|
|
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
10
|
|
|
11
11
|
const dispose = _bindText(s, node)
|
|
12
|
-
expect(node.data).toBe(
|
|
12
|
+
expect(node.data).toBe('hello')
|
|
13
13
|
|
|
14
|
-
s.set(
|
|
15
|
-
expect(node.data).toBe(
|
|
14
|
+
s.set('world')
|
|
15
|
+
expect(node.data).toBe('world')
|
|
16
16
|
|
|
17
17
|
dispose()
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
test(
|
|
20
|
+
test('fast path: computed source sets text and updates reactively', () => {
|
|
21
21
|
const s = signal(2)
|
|
22
22
|
const doubled = computed(() => s() * 2)
|
|
23
|
-
const node = document.createTextNode(
|
|
23
|
+
const node = document.createTextNode('')
|
|
24
24
|
|
|
25
25
|
const dispose = _bindText(doubled, node)
|
|
26
|
-
expect(node.data).toBe(
|
|
26
|
+
expect(node.data).toBe('4')
|
|
27
27
|
|
|
28
28
|
s.set(5)
|
|
29
|
-
expect(node.data).toBe(
|
|
29
|
+
expect(node.data).toBe('10')
|
|
30
30
|
|
|
31
31
|
dispose()
|
|
32
32
|
})
|
|
33
33
|
|
|
34
|
-
test(
|
|
35
|
-
const s = signal(
|
|
34
|
+
test('fallback: plain function source uses renderEffect', () => {
|
|
35
|
+
const s = signal('initial')
|
|
36
36
|
// Plain function — no .direct property
|
|
37
37
|
const getter = () => s()
|
|
38
|
-
const node = document.createTextNode(
|
|
38
|
+
const node = document.createTextNode('')
|
|
39
39
|
|
|
40
40
|
const dispose = _bindText(getter as unknown as Parameters<typeof _bindText>[0], node)
|
|
41
|
-
expect(node.data).toBe(
|
|
41
|
+
expect(node.data).toBe('initial')
|
|
42
42
|
|
|
43
|
-
s.set(
|
|
44
|
-
expect(node.data).toBe(
|
|
43
|
+
s.set('updated')
|
|
44
|
+
expect(node.data).toBe('updated')
|
|
45
45
|
|
|
46
46
|
dispose()
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
test(
|
|
50
|
-
const s = signal(
|
|
51
|
-
const node = document.createTextNode(
|
|
49
|
+
test('disposal stops updates for signal source', () => {
|
|
50
|
+
const s = signal('a')
|
|
51
|
+
const node = document.createTextNode('')
|
|
52
52
|
|
|
53
53
|
const dispose = _bindText(s, node)
|
|
54
|
-
expect(node.data).toBe(
|
|
54
|
+
expect(node.data).toBe('a')
|
|
55
55
|
|
|
56
56
|
dispose()
|
|
57
57
|
|
|
58
|
-
s.set(
|
|
59
|
-
expect(node.data).toBe(
|
|
58
|
+
s.set('b')
|
|
59
|
+
expect(node.data).toBe('a')
|
|
60
60
|
})
|
|
61
61
|
|
|
62
|
-
test(
|
|
62
|
+
test('disposal stops updates for computed source', () => {
|
|
63
63
|
const s = signal(1)
|
|
64
64
|
const c = computed(() => s() + 10)
|
|
65
|
-
const node = document.createTextNode(
|
|
65
|
+
const node = document.createTextNode('')
|
|
66
66
|
|
|
67
67
|
const dispose = _bindText(c, node)
|
|
68
|
-
expect(node.data).toBe(
|
|
68
|
+
expect(node.data).toBe('11')
|
|
69
69
|
|
|
70
70
|
dispose()
|
|
71
71
|
|
|
72
72
|
s.set(2)
|
|
73
|
-
expect(node.data).toBe(
|
|
73
|
+
expect(node.data).toBe('11')
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
test(
|
|
77
|
-
const s = signal(
|
|
76
|
+
test('disposal stops updates for plain function source', () => {
|
|
77
|
+
const s = signal('x')
|
|
78
78
|
const getter = () => s()
|
|
79
|
-
const node = document.createTextNode(
|
|
79
|
+
const node = document.createTextNode('')
|
|
80
80
|
|
|
81
81
|
const dispose = _bindText(getter as unknown as Parameters<typeof _bindText>[0], node)
|
|
82
|
-
expect(node.data).toBe(
|
|
82
|
+
expect(node.data).toBe('x')
|
|
83
83
|
|
|
84
84
|
dispose()
|
|
85
85
|
|
|
86
|
-
s.set(
|
|
87
|
-
expect(node.data).toBe(
|
|
86
|
+
s.set('y')
|
|
87
|
+
expect(node.data).toBe('x')
|
|
88
88
|
})
|
|
89
89
|
|
|
90
|
-
test(
|
|
91
|
-
const s = signal<string | null>(
|
|
92
|
-
const node = document.createTextNode(
|
|
90
|
+
test('null value renders as empty string', () => {
|
|
91
|
+
const s = signal<string | null>('text')
|
|
92
|
+
const node = document.createTextNode('')
|
|
93
93
|
|
|
94
94
|
const dispose = _bindText(s, node)
|
|
95
|
-
expect(node.data).toBe(
|
|
95
|
+
expect(node.data).toBe('text')
|
|
96
96
|
|
|
97
97
|
s.set(null)
|
|
98
|
-
expect(node.data).toBe(
|
|
98
|
+
expect(node.data).toBe('')
|
|
99
99
|
|
|
100
100
|
dispose()
|
|
101
101
|
})
|
|
102
102
|
|
|
103
|
-
test(
|
|
104
|
-
const s = signal<string | false>(
|
|
105
|
-
const node = document.createTextNode(
|
|
103
|
+
test('false value renders as empty string', () => {
|
|
104
|
+
const s = signal<string | false>('text')
|
|
105
|
+
const node = document.createTextNode('')
|
|
106
106
|
|
|
107
107
|
const dispose = _bindText(s, node)
|
|
108
108
|
s.set(false)
|
|
109
|
-
expect(node.data).toBe(
|
|
109
|
+
expect(node.data).toBe('')
|
|
110
110
|
|
|
111
111
|
dispose()
|
|
112
112
|
})
|
|
113
113
|
|
|
114
|
-
test(
|
|
115
|
-
const s = signal<string | undefined>(
|
|
116
|
-
const node = document.createTextNode(
|
|
114
|
+
test('undefined value renders as empty string', () => {
|
|
115
|
+
const s = signal<string | undefined>('text')
|
|
116
|
+
const node = document.createTextNode('')
|
|
117
117
|
|
|
118
118
|
const dispose = _bindText(s, node)
|
|
119
119
|
s.set(undefined)
|
|
120
|
-
expect(node.data).toBe(
|
|
120
|
+
expect(node.data).toBe('')
|
|
121
121
|
|
|
122
122
|
dispose()
|
|
123
123
|
})
|
|
@@ -125,70 +125,70 @@ describe("_bindText", () => {
|
|
|
125
125
|
|
|
126
126
|
// ─── _bindDirect ────────────────────────────────────────────────────────────
|
|
127
127
|
|
|
128
|
-
describe(
|
|
129
|
-
test(
|
|
130
|
-
const s = signal(
|
|
131
|
-
const el = document.createElement(
|
|
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
132
|
|
|
133
133
|
const dispose = _bindDirect(s, (v) => {
|
|
134
134
|
el.className = String(v)
|
|
135
135
|
})
|
|
136
136
|
|
|
137
|
-
expect(el.className).toBe(
|
|
137
|
+
expect(el.className).toBe('red')
|
|
138
138
|
|
|
139
|
-
s.set(
|
|
140
|
-
expect(el.className).toBe(
|
|
139
|
+
s.set('blue')
|
|
140
|
+
expect(el.className).toBe('blue')
|
|
141
141
|
|
|
142
142
|
dispose()
|
|
143
143
|
})
|
|
144
144
|
|
|
145
|
-
test(
|
|
145
|
+
test('fallback: plain function source uses renderEffect', () => {
|
|
146
146
|
const s = signal(10)
|
|
147
147
|
const getter = () => s()
|
|
148
|
-
const el = document.createElement(
|
|
148
|
+
const el = document.createElement('div')
|
|
149
149
|
|
|
150
150
|
const dispose = _bindDirect(getter as unknown as Parameters<typeof _bindDirect>[0], (v) => {
|
|
151
151
|
el.style.width = `${v}px`
|
|
152
152
|
})
|
|
153
153
|
|
|
154
|
-
expect(el.style.width).toBe(
|
|
154
|
+
expect(el.style.width).toBe('10px')
|
|
155
155
|
|
|
156
156
|
s.set(20)
|
|
157
|
-
expect(el.style.width).toBe(
|
|
157
|
+
expect(el.style.width).toBe('20px')
|
|
158
158
|
|
|
159
159
|
dispose()
|
|
160
160
|
})
|
|
161
161
|
|
|
162
|
-
test(
|
|
163
|
-
const s = signal(
|
|
164
|
-
const el = document.createElement(
|
|
162
|
+
test('disposal stops updates for signal source', () => {
|
|
163
|
+
const s = signal('a')
|
|
164
|
+
const el = document.createElement('div')
|
|
165
165
|
|
|
166
166
|
const dispose = _bindDirect(s, (v) => {
|
|
167
|
-
el.setAttribute(
|
|
167
|
+
el.setAttribute('data-val', String(v))
|
|
168
168
|
})
|
|
169
169
|
|
|
170
|
-
expect(el.getAttribute(
|
|
170
|
+
expect(el.getAttribute('data-val')).toBe('a')
|
|
171
171
|
|
|
172
172
|
dispose()
|
|
173
173
|
|
|
174
|
-
s.set(
|
|
175
|
-
expect(el.getAttribute(
|
|
174
|
+
s.set('b')
|
|
175
|
+
expect(el.getAttribute('data-val')).toBe('a')
|
|
176
176
|
})
|
|
177
177
|
|
|
178
|
-
test(
|
|
178
|
+
test('disposal stops updates for plain function source', () => {
|
|
179
179
|
const s = signal(1)
|
|
180
180
|
const getter = () => s()
|
|
181
|
-
const el = document.createElement(
|
|
181
|
+
const el = document.createElement('div')
|
|
182
182
|
|
|
183
183
|
const dispose = _bindDirect(getter as unknown as Parameters<typeof _bindDirect>[0], (v) => {
|
|
184
|
-
el.setAttribute(
|
|
184
|
+
el.setAttribute('data-num', String(v))
|
|
185
185
|
})
|
|
186
186
|
|
|
187
|
-
expect(el.getAttribute(
|
|
187
|
+
expect(el.getAttribute('data-num')).toBe('1')
|
|
188
188
|
|
|
189
189
|
dispose()
|
|
190
190
|
|
|
191
191
|
s.set(2)
|
|
192
|
-
expect(el.getAttribute(
|
|
192
|
+
expect(el.getAttribute('data-num')).toBe('1')
|
|
193
193
|
})
|
|
194
194
|
})
|