@pyreon/runtime-dom 0.24.5 → 0.24.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.
Files changed (53) hide show
  1. package/package.json +5 -9
  2. package/src/delegate.ts +0 -98
  3. package/src/devtools.ts +0 -339
  4. package/src/env.d.ts +0 -6
  5. package/src/hydrate.ts +0 -450
  6. package/src/hydration-debug.ts +0 -129
  7. package/src/index.ts +0 -83
  8. package/src/keep-alive-entry.ts +0 -3
  9. package/src/keep-alive.ts +0 -83
  10. package/src/manifest.ts +0 -236
  11. package/src/mount.ts +0 -597
  12. package/src/nodes.ts +0 -896
  13. package/src/props.ts +0 -474
  14. package/src/template.ts +0 -523
  15. package/src/tests/callback-ref-unmount.browser.test.ts +0 -62
  16. package/src/tests/callback-ref-unmount.test.ts +0 -52
  17. package/src/tests/compiler-integration.test.tsx +0 -508
  18. package/src/tests/coverage-gaps.test.ts +0 -3183
  19. package/src/tests/coverage.test.ts +0 -1140
  20. package/src/tests/ctx-stack-growth-repro.test.tsx +0 -158
  21. package/src/tests/dev-gate-pattern.test.ts +0 -46
  22. package/src/tests/dev-gate-treeshake.test.ts +0 -256
  23. package/src/tests/error-boundary-stack-leak-repro.test.tsx +0 -133
  24. package/src/tests/fanout-repro.test.tsx +0 -219
  25. package/src/tests/hydration-integration.test.tsx +0 -540
  26. package/src/tests/keyed-array-in-for-batched-toggle.browser.test.ts +0 -140
  27. package/src/tests/lifecycle-integration.test.tsx +0 -342
  28. package/src/tests/lis-prepend.browser.test.ts +0 -99
  29. package/src/tests/manifest-snapshot.test.ts +0 -85
  30. package/src/tests/mount.test.ts +0 -3529
  31. package/src/tests/native-markers.test.ts +0 -19
  32. package/src/tests/props.test.ts +0 -581
  33. package/src/tests/reactive-props.test.ts +0 -270
  34. package/src/tests/real-world-integration.test.tsx +0 -714
  35. package/src/tests/rs-collapse-dyn-h.browser.test.ts +0 -303
  36. package/src/tests/rs-collapse-dyn.browser.test.ts +0 -316
  37. package/src/tests/rs-collapse-h.browser.test.ts +0 -152
  38. package/src/tests/rs-collapse-h.test.ts +0 -237
  39. package/src/tests/rs-collapse.browser.test.ts +0 -128
  40. package/src/tests/runtime-dom.browser.test.ts +0 -409
  41. package/src/tests/setup.ts +0 -3
  42. package/src/tests/show-context.test.ts +0 -270
  43. package/src/tests/show-of-for-batched-toggle.browser.test.ts +0 -122
  44. package/src/tests/ssr-xss-round-trip.browser.test.ts +0 -93
  45. package/src/tests/style-key-removal.browser.test.ts +0 -54
  46. package/src/tests/style-key-removal.test.ts +0 -88
  47. package/src/tests/template.test.ts +0 -383
  48. package/src/tests/transition-timeout-leak.test.ts +0 -126
  49. package/src/tests/transition.test.ts +0 -568
  50. package/src/tests/verified-correct-probes.test.ts +0 -56
  51. package/src/transition-entry.ts +0 -7
  52. package/src/transition-group.ts +0 -350
  53. package/src/transition.ts +0 -245
@@ -1,152 +0,0 @@
1
- import { signal } from '@pyreon/reactivity'
2
- import { flush } from '@pyreon/test-utils/browser'
3
- import { afterEach, describe, expect, it } from 'vitest'
4
- import { _rsCollapseH, mount } from '../index'
5
-
6
- // PR 2 of the partial-collapse build (open-work #1), in REAL Chromium.
7
- // `_rsCollapseH` = `_rsCollapse` (one _tpl cloneNode, dual-emit reactive
8
- // class, no remount) PLUS it re-attaches the residual `on*` handlers the
9
- // compiler's `detectPartialCollapsibleShape` (PR 1) peeled off. The ONLY
10
- // delta vs `_rsCollapse` is the handler re-attach, routed through the
11
- // canonical `_bindEvent` → `applyEventProp` path — so a partially-
12
- // collapsed `<Button onClick=…>` behaves byte-identically to the
13
- // 5-layer mount it replaced. These specs prove exactly that delta in a
14
- // real browser (real click/pointer events, real computed style).
15
- //
16
- // Bisect-verify (PR body): neutralize the handler loop in
17
- // `_rsCollapseH` (`for (const key in handlers)` → no-op) → the 3
18
- // handler specs fail (`expected 0 to be 1` — handler never fired) while
19
- // every class/mode/no-remount assertion still passes; restore → all
20
- // pass. That asymmetry proves the handler re-attach is the load-bearing
21
- // addition, not passing for the wrong reason.
22
-
23
- describe('_rsCollapseH (real browser) — PR 2 partial-collapse runtime', () => {
24
- const cleanup: Array<() => void> = []
25
- afterEach(() => {
26
- for (const u of cleanup.splice(0)) u()
27
- })
28
-
29
- function injectCss(css: string): void {
30
- const el = document.createElement('style')
31
- el.textContent = css
32
- document.head.appendChild(el)
33
- cleanup.push(() => el.remove())
34
- }
35
-
36
- function mountInto(node: ReturnType<typeof _rsCollapseH>): HTMLElement {
37
- const root = document.createElement('div')
38
- document.body.appendChild(root)
39
- const dispose = mount(node as unknown as Parameters<typeof mount>[0], root)
40
- cleanup.push(() => {
41
- dispose()
42
- root.remove()
43
- })
44
- return root
45
- }
46
-
47
- it('applies the light class AND fires the peeled onClick on a real click', async () => {
48
- injectCss('.rsh-l{color:rgb(1,2,3)}.rsh-d{color:rgb(9,8,7)}')
49
- const isDark = signal(false)
50
- let clicks = 0
51
- const root = mountInto(
52
- _rsCollapseH('<button>Save</button>', 'rsh-l', 'rsh-d', () => isDark(), {
53
- onClick: () => {
54
- clicks++
55
- },
56
- }),
57
- )
58
- await flush()
59
- const btn = root.querySelector('button') as HTMLButtonElement
60
- expect(btn).not.toBeNull()
61
- expect(btn.className).toBe('rsh-l')
62
- expect(btn.textContent).toBe('Save')
63
- expect(getComputedStyle(btn).color).toBe('rgb(1, 2, 3)')
64
-
65
- btn.click()
66
- expect(clicks).toBe(1)
67
- btn.click()
68
- expect(clicks).toBe(2)
69
- })
70
-
71
- it('mode flip swaps the class on the SAME node AND the handler survives the flip', async () => {
72
- injectCss('.rsh-l2{color:rgb(10,20,30)}.rsh-d2{color:rgb(40,50,60)}')
73
- const isDark = signal(false)
74
- let clicks = 0
75
- const root = mountInto(
76
- _rsCollapseH('<button>X</button>', 'rsh-l2', 'rsh-d2', () => isDark(), {
77
- onClick: () => {
78
- clicks++
79
- },
80
- }),
81
- )
82
- await flush()
83
- const before = root.querySelector('button') as HTMLButtonElement
84
- expect(before.className).toBe('rsh-l2')
85
- before.click()
86
- expect(clicks).toBe(1)
87
-
88
- isDark.set(true)
89
- await flush()
90
- const after = root.querySelector('button') as HTMLButtonElement
91
- expect(after).toBe(before) // node identity preserved ⇒ reactive, not remount
92
- expect(after.className).toBe('rsh-d2')
93
- // The load-bearing partial-collapse contract: the reactive class
94
- // binding does NOT remount, so the handler attached at first mount is
95
- // still live after the mode flip.
96
- after.click()
97
- expect(clicks).toBe(2)
98
- })
99
-
100
- it('peels + binds MULTIPLE handlers with correct onXxx→event normalization', async () => {
101
- injectCss('.rsh-m{color:rgb(0,0,0)}')
102
- const isDark = signal(false)
103
- let clicks = 0
104
- let enters = 0
105
- const root = mountInto(
106
- _rsCollapseH('<button>M</button>', 'rsh-m', 'rsh-m', () => isDark(), {
107
- onClick: () => {
108
- clicks++
109
- },
110
- // onPointerEnter must normalize to the lowercase DOM event
111
- // `pointerenter` via the canonical path — a hand-rolled
112
- // `addEventListener('pointerEnter', …)` would never fire.
113
- onPointerEnter: () => {
114
- enters++
115
- },
116
- }),
117
- )
118
- await flush()
119
- const btn = root.querySelector('button') as HTMLButtonElement
120
- btn.click()
121
- btn.dispatchEvent(new PointerEvent('pointerenter', { bubbles: false }))
122
- expect(clicks).toBe(1)
123
- expect(enters).toBe(1)
124
- })
125
-
126
- it('dispose removes the listener (no leak) — composed cleanup is correct', async () => {
127
- injectCss('.rsh-c{color:rgb(5,5,5)}')
128
- const isDark = signal(false)
129
- let clicks = 0
130
- const root = document.createElement('div')
131
- document.body.appendChild(root)
132
- const dispose = mount(
133
- _rsCollapseH('<button>C</button>', 'rsh-c', 'rsh-c', () => isDark(), {
134
- onClick: () => {
135
- clicks++
136
- },
137
- }) as unknown as Parameters<typeof mount>[0],
138
- root,
139
- )
140
- await flush()
141
- const btn = root.querySelector('button') as HTMLButtonElement
142
- btn.click()
143
- expect(clicks).toBe(1)
144
-
145
- dispose()
146
- // After dispose the handler disposer ran — the listener is gone, so a
147
- // post-dispose click does NOT increment.
148
- btn.click()
149
- expect(clicks).toBe(1)
150
- root.remove()
151
- })
152
- })
@@ -1,237 +0,0 @@
1
- import { signal } from '@pyreon/reactivity'
2
- import { afterEach, describe, expect, it } from 'vitest'
3
- import { _rsCollapseH } from '../template'
4
-
5
- // PR 2 of the partial-collapse build (open-work #1) — happy-dom UNIT
6
- // layer (fast + locally bisect-verifiable). Imports ONLY `../template`
7
- // (template.test.ts style) — NOT `../index`, whose wide cross-package
8
- // re-export graph hits the documented fresh-worktree resolution trap.
9
- //
10
- // Event-path split (honest, per test-environment-parity — both layers):
11
- // - DELEGATED events (`click`, see `delegate.ts:DELEGATED_EVENTS`)
12
- // park in a prop-slot that only fires via the root listener
13
- // `mount()` installs → covered by `rs-collapse-h.browser.test.ts`
14
- // (real Chromium + real `mount()`, CI-authoritative; can't run in a
15
- // fresh worktree — `@pyreon/test-utils/browser` needs built lib).
16
- // - NON-delegated events (`pointerenter`, `mouseenter` — NOT in
17
- // `DELEGATED_EVENTS`) take `applyEventProp`'s direct
18
- // `el.addEventListener(name, …)` path → fire on a bare
19
- // `dispatchEvent` with NO `mount()`. This unit drives the
20
- // handler-attach delta on THAT path: it still proves `_rsCollapseH`
21
- // routes residual handlers through the canonical
22
- // `_bindEvent`→`applyEventProp` path (incl. the exact
23
- // `onXxx`→lowercase normalization) + composed cleanup — the
24
- // load-bearing addition vs `_rsCollapse`.
25
- //
26
- // `_rsCollapseH` returns a NativeItem (`{ __isNative, el, cleanup }`);
27
- // `_tpl` runs the bind synchronously at call time, so class + handlers
28
- // are live on `.el` immediately (same direct-NativeItem shape
29
- // `template.test.ts` uses for `_tpl`). Signal writes propagate
30
- // synchronously here (cf. `template.test.ts:_bindText`); a
31
- // `Promise.resolve()` tick covers any microtask-scheduled reactive
32
- // class update defensively.
33
- //
34
- // Bisect-verify (PR body): neutralize the handler loop in
35
- // `_rsCollapseH` (`for (const key in handlers)` body → skip) → the
36
- // handler/normalization/cleanup specs fail with `expected 0 to be 1`
37
- // while every class / mode-flip / no-remount / zero-handler / child
38
- // assertion still passes; restore → 8/8. The asymmetry proves the
39
- // handler re-attach is the load-bearing delta.
40
-
41
- const tick = (): Promise<void> => Promise.resolve()
42
-
43
- describe('_rsCollapseH (happy-dom unit) — PR 2 partial-collapse runtime', () => {
44
- const cleanup: Array<() => void> = []
45
- afterEach(() => {
46
- for (const u of cleanup.splice(0)) u()
47
- })
48
-
49
- function place(item: ReturnType<typeof _rsCollapseH>): HTMLElement {
50
- const el = item.el as HTMLElement
51
- document.body.appendChild(el)
52
- cleanup.push(() => {
53
- item.cleanup?.()
54
- el.remove()
55
- })
56
- return el
57
- }
58
-
59
- const fire = (el: HTMLElement, type: string): void => {
60
- el.dispatchEvent(new Event(type, { bubbles: false }))
61
- }
62
-
63
- it('sets the light class + static children AND fires the peeled handler', () => {
64
- const isDark = signal(false)
65
- let enters = 0
66
- const el = place(
67
- _rsCollapseH('<button>Save</button>', 'rsh-l', 'rsh-d', () => isDark(), {
68
- onPointerEnter: () => {
69
- enters++
70
- },
71
- }),
72
- )
73
- expect(el.tagName).toBe('BUTTON')
74
- expect(el.className).toBe('rsh-l')
75
- expect(el.textContent).toBe('Save')
76
-
77
- fire(el, 'pointerenter')
78
- expect(enters).toBe(1)
79
- fire(el, 'pointerenter')
80
- expect(enters).toBe(2)
81
- })
82
-
83
- it('normalizes onXxx → lowercase DOM event via the canonical path', () => {
84
- const isDark = signal(false)
85
- let enters = 0
86
- let mouse = 0
87
- const el = place(
88
- _rsCollapseH('<button>M</button>', 'rsh-m', 'rsh-m', () => isDark(), {
89
- // onPointerEnter MUST bind to `pointerenter` (lowercased whole
90
- // name) — a hand-rolled `addEventListener('pointerEnter', …)`
91
- // would never fire. onMouseEnter → `mouseenter` likewise.
92
- onPointerEnter: () => {
93
- enters++
94
- },
95
- onMouseEnter: () => {
96
- mouse++
97
- },
98
- }),
99
- )
100
- fire(el, 'pointerenter')
101
- fire(el, 'mouseenter')
102
- expect(enters).toBe(1)
103
- expect(mouse).toBe(1)
104
- // Wrong-case names must NOT fire (proves real normalization, not luck).
105
- fire(el, 'pointerEnter')
106
- fire(el, 'mouseEnter')
107
- expect(enters).toBe(1)
108
- expect(mouse).toBe(1)
109
- })
110
-
111
- it('mode flip swaps the class on the SAME node AND the handler survives the flip', async () => {
112
- const isDark = signal(false)
113
- let enters = 0
114
- const el = place(
115
- _rsCollapseH('<button>X</button>', 'rsh-l2', 'rsh-d2', () => isDark(), {
116
- onPointerEnter: () => {
117
- enters++
118
- },
119
- }),
120
- )
121
- expect(el.className).toBe('rsh-l2')
122
- fire(el, 'pointerenter')
123
- expect(enters).toBe(1)
124
-
125
- isDark.set(true)
126
- await tick()
127
- // Same node identity ⇒ reactive class swap, NOT a remount.
128
- expect(el.className).toBe('rsh-d2')
129
- // Load-bearing partial-collapse contract: no remount ⇒ the handler
130
- // bound at construction is still live after the mode flip.
131
- fire(el, 'pointerenter')
132
- expect(enters).toBe(2)
133
-
134
- isDark.set(false)
135
- await tick()
136
- expect(el.className).toBe('rsh-l2')
137
- })
138
-
139
- it('cleanup() removes the listener — composed disposer is correct (no leak)', () => {
140
- const isDark = signal(false)
141
- let enters = 0
142
- const item = _rsCollapseH('<button>C</button>', 'rsh-c', 'rsh-c', () => isDark(), {
143
- onPointerEnter: () => {
144
- enters++
145
- },
146
- })
147
- const el = item.el as HTMLElement
148
- document.body.appendChild(el)
149
- fire(el, 'pointerenter')
150
- expect(enters).toBe(1)
151
-
152
- item.cleanup?.()
153
- fire(el, 'pointerenter') // listener removed by the composed disposer
154
- expect(enters).toBe(1)
155
- el.remove()
156
- })
157
-
158
- it('binds MULTIPLE handlers; literal props stay out of the handler set', () => {
159
- const isDark = signal(false)
160
- let a = 0
161
- let b = 0
162
- const el = place(
163
- _rsCollapseH('<button>N</button>', 'rsh-n', 'rsh-n', () => isDark(), {
164
- onPointerEnter: () => {
165
- a++
166
- },
167
- onMouseEnter: () => {
168
- b++
169
- },
170
- }),
171
- )
172
- fire(el, 'pointerenter')
173
- fire(el, 'mouseenter')
174
- fire(el, 'pointerenter')
175
- expect(a).toBe(2)
176
- expect(b).toBe(1)
177
- })
178
-
179
- it('zero handlers: renders class + children, cleanup is safe (defensive)', async () => {
180
- const isDark = signal(false)
181
- const el = place(_rsCollapseH('<button>Z</button>', 'rsh-z', 'rsh-zd', () => isDark(), {}))
182
- expect(el.className).toBe('rsh-z')
183
- expect(el.textContent).toBe('Z')
184
- isDark.set(true)
185
- await tick()
186
- expect(el.className).toBe('rsh-zd')
187
- // cleanup runs via afterEach — must not throw with no handlers.
188
- })
189
-
190
- it('children bind runs alongside the class + handler binds', () => {
191
- const isDark = signal(false)
192
- let enters = 0
193
- const el = place(
194
- _rsCollapseH(
195
- '<button><span></span></button>',
196
- 'rsh-cb',
197
- 'rsh-cbd',
198
- () => isDark(),
199
- { onPointerEnter: () => enters++ },
200
- (root) => {
201
- ;(root.querySelector('span') as HTMLElement).textContent = 'child'
202
- return null
203
- },
204
- ),
205
- )
206
- expect(el.className).toBe('rsh-cb')
207
- expect((el.querySelector('span') as HTMLElement).textContent).toBe('child')
208
- fire(el, 'pointerenter')
209
- expect(enters).toBe(1)
210
- })
211
-
212
- it('disposes class + handler + child binds together (composition)', () => {
213
- const isDark = signal(false)
214
- let enters = 0
215
- let childDisposed = false
216
- const item = _rsCollapseH(
217
- '<button><span></span></button>',
218
- 'rsh-x',
219
- 'rsh-xd',
220
- () => isDark(),
221
- { onPointerEnter: () => enters++ },
222
- () => () => {
223
- childDisposed = true
224
- },
225
- )
226
- const el = item.el as HTMLElement
227
- document.body.appendChild(el)
228
- fire(el, 'pointerenter')
229
- expect(enters).toBe(1)
230
-
231
- item.cleanup?.()
232
- expect(childDisposed).toBe(true) // child disposer composed + ran
233
- fire(el, 'pointerenter')
234
- expect(enters).toBe(1) // handler disposer ran too
235
- el.remove()
236
- })
237
- })
@@ -1,128 +0,0 @@
1
- import { signal } from '@pyreon/reactivity'
2
- import { flush } from '@pyreon/test-utils/browser'
3
- import { afterEach, describe, expect, it } from 'vitest'
4
- import { _rsCollapse, mount } from '../index'
5
-
6
- // Layer 2 of the P0 rocketstyle-collapse slice, in real Chromium.
7
- // `_rsCollapse` deliberately does NOT import @pyreon/styler (layer
8
- // purity — runtime-dom is layer 4; the styler injection is the EMITTED
9
- // code's job). So this suite injects CSS via a plain <style> and proves
10
- // only what _rsCollapse owns: ONE _tpl() cloneNode whose class is
11
- // reactively bound to the app mode (dual-emit, RFC decision 1) — a mode
12
- // flip swaps the class on the SAME node with no remount.
13
-
14
- describe('_rsCollapse (real browser)', () => {
15
- const cleanup: Array<() => void> = []
16
- afterEach(() => {
17
- for (const u of cleanup.splice(0)) u()
18
- })
19
-
20
- function injectCss(css: string): void {
21
- const el = document.createElement('style')
22
- el.textContent = css
23
- document.head.appendChild(el)
24
- cleanup.push(() => el.remove())
25
- }
26
-
27
- function mountInto(node: ReturnType<typeof _rsCollapse>): HTMLElement {
28
- const root = document.createElement('div')
29
- document.body.appendChild(root)
30
- const dispose = mount(node as unknown as Parameters<typeof mount>[0], root)
31
- cleanup.push(() => {
32
- dispose()
33
- root.remove()
34
- })
35
- return root
36
- }
37
-
38
- it('applies the light class + static children; the class is real CSS', async () => {
39
- injectCss('.rsc-light{color:rgb(1,2,3)}.rsc-dark{color:rgb(9,8,7)}')
40
- const isDark = signal(false)
41
- const root = mountInto(
42
- _rsCollapse('<button>Save</button>', 'rsc-light', 'rsc-dark', () => isDark()),
43
- )
44
- await flush()
45
- const btn = root.querySelector('button')
46
- expect(btn).not.toBeNull()
47
- expect(btn?.className).toBe('rsc-light')
48
- expect(btn?.textContent).toBe('Save')
49
- expect(getComputedStyle(btn as Element).color).toBe('rgb(1, 2, 3)')
50
- })
51
-
52
- it('mode flip swaps to the dark class on the SAME node (no remount)', async () => {
53
- injectCss('.rsc-l2{color:rgb(10,20,30)}.rsc-d2{color:rgb(40,50,60)}')
54
- const isDark = signal(false)
55
- const root = mountInto(
56
- _rsCollapse('<button>X</button>', 'rsc-l2', 'rsc-d2', () => isDark()),
57
- )
58
- await flush()
59
- const before = root.querySelector('button') as HTMLElement
60
- expect(before.className).toBe('rsc-l2')
61
-
62
- isDark.set(true)
63
- await flush()
64
- const after = root.querySelector('button') as HTMLElement
65
- expect(after).toBe(before) // node identity preserved ⇒ reactive, not remount
66
- expect(after.className).toBe('rsc-d2')
67
- expect(getComputedStyle(after).color).toBe('rgb(40, 50, 60)')
68
-
69
- isDark.set(false)
70
- await flush()
71
- expect((root.querySelector('button') as HTMLElement).className).toBe('rsc-l2')
72
- })
73
-
74
- it('children bind runs alongside the class bind and disposes cleanly', async () => {
75
- injectCss('.rsc-c{color:rgb(2,2,2)}.rsc-cd{color:rgb(3,3,3)}')
76
- const label = signal('one')
77
- const isDark = signal(false)
78
- let childDisposed = false
79
- const root = mountInto(
80
- _rsCollapse(
81
- '<button><span></span></button>',
82
- 'rsc-c',
83
- 'rsc-cd',
84
- () => isDark(),
85
- (el) => {
86
- const span = el.querySelector('span') as HTMLElement
87
- const stop = (() => {
88
- // minimal reactive child without pulling the compiler in
89
- let raf = 0
90
- const tick = () => {
91
- span.textContent = label()
92
- raf = requestAnimationFrame(tick)
93
- }
94
- tick()
95
- return () => {
96
- cancelAnimationFrame(raf)
97
- childDisposed = true
98
- }
99
- })()
100
- return stop
101
- },
102
- ),
103
- )
104
- await flush()
105
- expect((root.querySelector('span') as HTMLElement).textContent).toBe('one')
106
- expect((root.querySelector('button') as HTMLElement).className).toBe('rsc-c')
107
- // dispose via afterEach → child cleanup must fire
108
- for (const u of cleanup.splice(0)) u()
109
- expect(childDisposed).toBe(true)
110
- })
111
-
112
- it('two instances of the same html share ONE parsed template, with independent reactivity', async () => {
113
- injectCss('.rsc-s{color:rgb(7,7,7)}.rsc-sd{color:rgb(8,8,8)}')
114
- const isDark = signal(false)
115
- const r1 = mountInto(_rsCollapse('<button>dup</button>', 'rsc-s', 'rsc-sd', () => isDark()))
116
- const r2 = mountInto(_rsCollapse('<button>dup</button>', 'rsc-s', 'rsc-sd', () => isDark()))
117
- await flush()
118
- const b1 = r1.querySelector('button') as HTMLElement
119
- const b2 = r2.querySelector('button') as HTMLElement
120
- expect(b1).not.toBe(b2) // distinct cloned nodes from the shared template
121
- expect(b1.className).toBe('rsc-s')
122
- expect(b2.className).toBe('rsc-s')
123
- isDark.set(true)
124
- await flush()
125
- expect((r1.querySelector('button') as HTMLElement).className).toBe('rsc-sd')
126
- expect((r2.querySelector('button') as HTMLElement).className).toBe('rsc-sd')
127
- })
128
- })