@pyreon/solid-compat 0.11.5 → 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 +11 -15
- package/lib/index.js.map +1 -1
- package/lib/jsx-runtime.js.map +1 -1
- package/package.json +13 -13
- package/src/index.ts +8 -8
- package/src/jsx-dev-runtime.ts +1 -1
- package/src/jsx-runtime.ts +8 -8
- package/src/tests/repro.test.ts +50 -50
- package/src/tests/setup.ts +1 -1
- package/src/tests/solid-compat.test.ts +121 -121
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h } from
|
|
1
|
+
import { h } from '@pyreon/core'
|
|
2
2
|
import {
|
|
3
3
|
batch,
|
|
4
4
|
children,
|
|
@@ -26,9 +26,9 @@ import {
|
|
|
26
26
|
splitProps,
|
|
27
27
|
untrack,
|
|
28
28
|
useContext,
|
|
29
|
-
} from
|
|
30
|
-
import type { RenderContext } from
|
|
31
|
-
import { beginRender, endRender } from
|
|
29
|
+
} from '../index'
|
|
30
|
+
import type { RenderContext } from '../jsx-runtime'
|
|
31
|
+
import { beginRender, endRender } from '../jsx-runtime'
|
|
32
32
|
|
|
33
33
|
// ─── Test helpers ─────────────────────────────────────────────────────────────
|
|
34
34
|
|
|
@@ -53,27 +53,27 @@ function createHookRunner() {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
describe(
|
|
56
|
+
describe('@pyreon/solid-compat', () => {
|
|
57
57
|
// ─── createSignal ─────────────────────────────────────────────────────
|
|
58
58
|
|
|
59
|
-
it(
|
|
59
|
+
it('createSignal returns [getter, setter] tuple', () => {
|
|
60
60
|
const [count, setCount] = createSignal(0)
|
|
61
|
-
expect(typeof count).toBe(
|
|
62
|
-
expect(typeof setCount).toBe(
|
|
61
|
+
expect(typeof count).toBe('function')
|
|
62
|
+
expect(typeof setCount).toBe('function')
|
|
63
63
|
})
|
|
64
64
|
|
|
65
|
-
it(
|
|
65
|
+
it('getter returns current value', () => {
|
|
66
66
|
const [count] = createSignal(42)
|
|
67
67
|
expect(count()).toBe(42)
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
it(
|
|
70
|
+
it('setter updates value', () => {
|
|
71
71
|
const [count, setCount] = createSignal(0)
|
|
72
72
|
setCount(5)
|
|
73
73
|
expect(count()).toBe(5)
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
it(
|
|
76
|
+
it('setter with updater function', () => {
|
|
77
77
|
const [count, setCount] = createSignal(10)
|
|
78
78
|
setCount((prev) => prev + 5)
|
|
79
79
|
expect(count()).toBe(15)
|
|
@@ -81,7 +81,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
81
81
|
|
|
82
82
|
// ─── createSignal in component context ─────────────────────────────────
|
|
83
83
|
|
|
84
|
-
it(
|
|
84
|
+
it('createSignal in component context stores in hooks', () => {
|
|
85
85
|
const runner = createHookRunner()
|
|
86
86
|
const [count] = runner.run(() => createSignal(42))
|
|
87
87
|
expect(count()).toBe(42)
|
|
@@ -90,7 +90,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
90
90
|
expect(count2()).toBe(42)
|
|
91
91
|
})
|
|
92
92
|
|
|
93
|
-
it(
|
|
93
|
+
it('createSignal setter in component context triggers scheduleRerender', () => {
|
|
94
94
|
const runner = createHookRunner()
|
|
95
95
|
let rerenders = 0
|
|
96
96
|
runner.ctx.scheduleRerender = () => {
|
|
@@ -101,7 +101,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
101
101
|
expect(rerenders).toBe(1)
|
|
102
102
|
})
|
|
103
103
|
|
|
104
|
-
it(
|
|
104
|
+
it('createSignal setter persists across re-renders', () => {
|
|
105
105
|
const runner = createHookRunner()
|
|
106
106
|
const [, setCount] = runner.run(() => createSignal(0))
|
|
107
107
|
setCount(99)
|
|
@@ -111,7 +111,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
111
111
|
|
|
112
112
|
// ─── createEffect ─────────────────────────────────────────────────────
|
|
113
113
|
|
|
114
|
-
it(
|
|
114
|
+
it('createEffect tracks signal reads', () => {
|
|
115
115
|
let effectValue = 0
|
|
116
116
|
createRoot((dispose) => {
|
|
117
117
|
const [count, setCount] = createSignal(0)
|
|
@@ -125,7 +125,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
125
125
|
})
|
|
126
126
|
})
|
|
127
127
|
|
|
128
|
-
it(
|
|
128
|
+
it('createEffect in component context is hook-indexed', () => {
|
|
129
129
|
const runner = createHookRunner()
|
|
130
130
|
let effectRuns = 0
|
|
131
131
|
runner.run(() => {
|
|
@@ -146,7 +146,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
146
146
|
expect(effectRuns).toBe(1) // still 1, not re-created
|
|
147
147
|
})
|
|
148
148
|
|
|
149
|
-
it(
|
|
149
|
+
it('createEffect in component context is disposed on unmount', () => {
|
|
150
150
|
const runner = createHookRunner()
|
|
151
151
|
let effectRuns = 0
|
|
152
152
|
const [, setCount] = runner.run(() => {
|
|
@@ -166,7 +166,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
166
166
|
|
|
167
167
|
// ─── createRenderEffect ────────────────────────────────────────────────
|
|
168
168
|
|
|
169
|
-
it(
|
|
169
|
+
it('createRenderEffect tracks signal reads like createEffect', () => {
|
|
170
170
|
let effectValue = 0
|
|
171
171
|
createRoot((dispose) => {
|
|
172
172
|
const [count, setCount] = createSignal(0)
|
|
@@ -182,7 +182,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
182
182
|
|
|
183
183
|
// ─── createComputed (alias) ────────────────────────────────────────────
|
|
184
184
|
|
|
185
|
-
it(
|
|
185
|
+
it('createComputed is an alias for createEffect', () => {
|
|
186
186
|
let effectValue = 0
|
|
187
187
|
createRoot((dispose) => {
|
|
188
188
|
const [count, setCount] = createSignal(0)
|
|
@@ -198,7 +198,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
198
198
|
|
|
199
199
|
// ─── createMemo ───────────────────────────────────────────────────────
|
|
200
200
|
|
|
201
|
-
it(
|
|
201
|
+
it('createMemo derives computed value', () => {
|
|
202
202
|
createRoot((dispose) => {
|
|
203
203
|
const [count] = createSignal(3)
|
|
204
204
|
const doubled = createMemo(() => count() * 2)
|
|
@@ -207,7 +207,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
207
207
|
})
|
|
208
208
|
})
|
|
209
209
|
|
|
210
|
-
it(
|
|
210
|
+
it('createMemo updates when dependency changes', () => {
|
|
211
211
|
createRoot((dispose) => {
|
|
212
212
|
const [count, setCount] = createSignal(3)
|
|
213
213
|
const doubled = createMemo(() => count() * 2)
|
|
@@ -218,7 +218,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
218
218
|
})
|
|
219
219
|
})
|
|
220
220
|
|
|
221
|
-
it(
|
|
221
|
+
it('createMemo in component context is hook-indexed', () => {
|
|
222
222
|
const runner = createHookRunner()
|
|
223
223
|
const doubled = runner.run(() => {
|
|
224
224
|
const [count] = createSignal(5)
|
|
@@ -235,7 +235,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
235
235
|
|
|
236
236
|
// ─── createRoot ───────────────────────────────────────────────────────
|
|
237
237
|
|
|
238
|
-
it(
|
|
238
|
+
it('createRoot provides cleanup', () => {
|
|
239
239
|
let effectRan = false
|
|
240
240
|
let disposed = false
|
|
241
241
|
createRoot((dispose) => {
|
|
@@ -254,7 +254,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
254
254
|
expect(disposed).toBe(true)
|
|
255
255
|
})
|
|
256
256
|
|
|
257
|
-
it(
|
|
257
|
+
it('createRoot returns value from fn', () => {
|
|
258
258
|
const result = createRoot((dispose) => {
|
|
259
259
|
dispose()
|
|
260
260
|
return 42
|
|
@@ -264,7 +264,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
264
264
|
|
|
265
265
|
// ─── batch ────────────────────────────────────────────────────────────
|
|
266
266
|
|
|
267
|
-
it(
|
|
267
|
+
it('batch coalesces updates', () => {
|
|
268
268
|
let runs = 0
|
|
269
269
|
createRoot((dispose) => {
|
|
270
270
|
const [a, setA] = createSignal(1)
|
|
@@ -286,7 +286,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
286
286
|
|
|
287
287
|
// ─── untrack ──────────────────────────────────────────────────────────
|
|
288
288
|
|
|
289
|
-
it(
|
|
289
|
+
it('untrack prevents tracking', () => {
|
|
290
290
|
let runs = 0
|
|
291
291
|
createRoot((dispose) => {
|
|
292
292
|
const [count, setCount] = createSignal(0)
|
|
@@ -307,7 +307,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
307
307
|
|
|
308
308
|
// ─── on ───────────────────────────────────────────────────────────────
|
|
309
309
|
|
|
310
|
-
it(
|
|
310
|
+
it('on() tracks specific single dependency', () => {
|
|
311
311
|
createRoot((dispose) => {
|
|
312
312
|
const [count, setCount] = createSignal(0)
|
|
313
313
|
const values: number[] = []
|
|
@@ -328,7 +328,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
328
328
|
})
|
|
329
329
|
})
|
|
330
330
|
|
|
331
|
-
it(
|
|
331
|
+
it('on() tracks array of dependencies', () => {
|
|
332
332
|
createRoot((dispose) => {
|
|
333
333
|
const [a, setA] = createSignal(1)
|
|
334
334
|
const [b, _setB] = createSignal(2)
|
|
@@ -356,7 +356,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
356
356
|
})
|
|
357
357
|
})
|
|
358
358
|
|
|
359
|
-
it(
|
|
359
|
+
it('on() provides prevValue on subsequent calls', () => {
|
|
360
360
|
createRoot((dispose) => {
|
|
361
361
|
const [count, setCount] = createSignal(0)
|
|
362
362
|
const prevValues: unknown[] = []
|
|
@@ -379,7 +379,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
379
379
|
|
|
380
380
|
// ─── createSelector ───────────────────────────────────────────────────
|
|
381
381
|
|
|
382
|
-
it(
|
|
382
|
+
it('createSelector returns equality checker', () => {
|
|
383
383
|
createRoot((dispose) => {
|
|
384
384
|
const [selected, setSelected] = createSignal(1)
|
|
385
385
|
const isSelected = createSelector(selected)
|
|
@@ -394,7 +394,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
394
394
|
})
|
|
395
395
|
})
|
|
396
396
|
|
|
397
|
-
it(
|
|
397
|
+
it('createSelector in component context is hook-indexed', () => {
|
|
398
398
|
const runner = createHookRunner()
|
|
399
399
|
const isSelected = runner.run(() => {
|
|
400
400
|
const [selected] = createSignal(1)
|
|
@@ -411,19 +411,19 @@ describe("@pyreon/solid-compat", () => {
|
|
|
411
411
|
|
|
412
412
|
// ─── mergeProps ───────────────────────────────────────────────────────
|
|
413
413
|
|
|
414
|
-
it(
|
|
415
|
-
const defaults = { color:
|
|
416
|
-
const overrides = { size: 20, weight:
|
|
414
|
+
it('mergeProps combines objects', () => {
|
|
415
|
+
const defaults = { color: 'red', size: 10 }
|
|
416
|
+
const overrides = { size: 20, weight: 'bold' }
|
|
417
417
|
const merged = mergeProps(defaults, overrides)
|
|
418
|
-
expect((merged as Record<string, unknown>).color).toBe(
|
|
418
|
+
expect((merged as Record<string, unknown>).color).toBe('red')
|
|
419
419
|
expect((merged as Record<string, unknown>).size).toBe(20)
|
|
420
|
-
expect((merged as Record<string, unknown>).weight).toBe(
|
|
420
|
+
expect((merged as Record<string, unknown>).weight).toBe('bold')
|
|
421
421
|
})
|
|
422
422
|
|
|
423
|
-
it(
|
|
423
|
+
it('mergeProps preserves getters for reactivity', () => {
|
|
424
424
|
const [count, setCount] = createSignal(0)
|
|
425
425
|
const props = {}
|
|
426
|
-
Object.defineProperty(props,
|
|
426
|
+
Object.defineProperty(props, 'count', {
|
|
427
427
|
get: count,
|
|
428
428
|
enumerable: true,
|
|
429
429
|
configurable: true,
|
|
@@ -436,67 +436,67 @@ describe("@pyreon/solid-compat", () => {
|
|
|
436
436
|
|
|
437
437
|
// ─── splitProps ───────────────────────────────────────────────────────
|
|
438
438
|
|
|
439
|
-
it(
|
|
440
|
-
const props = { name:
|
|
441
|
-
const [local, rest] = splitProps(props,
|
|
442
|
-
expect((local as Record<string, unknown>).name).toBe(
|
|
439
|
+
it('splitProps separates props', () => {
|
|
440
|
+
const props = { name: 'hello', class: 'btn', onClick: () => {} }
|
|
441
|
+
const [local, rest] = splitProps(props, 'name')
|
|
442
|
+
expect((local as Record<string, unknown>).name).toBe('hello')
|
|
443
443
|
expect((local as Record<string, unknown>).class).toBeUndefined()
|
|
444
|
-
expect((rest as Record<string, unknown>).class).toBe(
|
|
444
|
+
expect((rest as Record<string, unknown>).class).toBe('btn')
|
|
445
445
|
expect((rest as Record<string, unknown>).onClick).toBeDefined()
|
|
446
446
|
})
|
|
447
447
|
|
|
448
|
-
it(
|
|
448
|
+
it('splitProps preserves getters', () => {
|
|
449
449
|
const [count, setCount] = createSignal(0)
|
|
450
450
|
const props = {} as Record<string, unknown>
|
|
451
|
-
Object.defineProperty(props,
|
|
451
|
+
Object.defineProperty(props, 'count', {
|
|
452
452
|
get: count,
|
|
453
453
|
enumerable: true,
|
|
454
454
|
configurable: true,
|
|
455
455
|
})
|
|
456
|
-
Object.defineProperty(props,
|
|
457
|
-
value:
|
|
456
|
+
Object.defineProperty(props, 'label', {
|
|
457
|
+
value: 'test',
|
|
458
458
|
writable: true,
|
|
459
459
|
enumerable: true,
|
|
460
460
|
configurable: true,
|
|
461
461
|
})
|
|
462
462
|
|
|
463
|
-
const [local, rest] = splitProps(props as { count: number; label: string },
|
|
463
|
+
const [local, rest] = splitProps(props as { count: number; label: string }, 'count')
|
|
464
464
|
expect((local as Record<string, unknown>).count).toBe(0)
|
|
465
465
|
setCount(10)
|
|
466
466
|
expect((local as Record<string, unknown>).count).toBe(10)
|
|
467
|
-
expect((rest as Record<string, unknown>).label).toBe(
|
|
467
|
+
expect((rest as Record<string, unknown>).label).toBe('test')
|
|
468
468
|
})
|
|
469
469
|
|
|
470
470
|
// ─── children ─────────────────────────────────────────────────────────
|
|
471
471
|
|
|
472
|
-
it(
|
|
473
|
-
const resolved = children(() =>
|
|
474
|
-
expect(resolved()).toBe(
|
|
472
|
+
it('children resolves static values', () => {
|
|
473
|
+
const resolved = children(() => 'hello')
|
|
474
|
+
expect(resolved()).toBe('hello')
|
|
475
475
|
})
|
|
476
476
|
|
|
477
|
-
it(
|
|
478
|
-
const resolved = children(() => (() =>
|
|
479
|
-
expect(resolved()).toBe(
|
|
477
|
+
it('children resolves function children (reactive getters)', () => {
|
|
478
|
+
const resolved = children(() => (() => 'dynamic') as unknown as ReturnType<typeof h>)
|
|
479
|
+
expect(resolved()).toBe('dynamic')
|
|
480
480
|
})
|
|
481
481
|
|
|
482
482
|
// ─── lazy ─────────────────────────────────────────────────────────────
|
|
483
483
|
|
|
484
|
-
it(
|
|
485
|
-
const Lazy = lazy(() => Promise.resolve({ default: () => h(
|
|
486
|
-
expect(typeof Lazy).toBe(
|
|
487
|
-
expect(typeof Lazy.preload).toBe(
|
|
484
|
+
it('lazy returns a component with preload', () => {
|
|
485
|
+
const Lazy = lazy(() => Promise.resolve({ default: () => h('div', null, 'loaded') }))
|
|
486
|
+
expect(typeof Lazy).toBe('function')
|
|
487
|
+
expect(typeof Lazy.preload).toBe('function')
|
|
488
488
|
})
|
|
489
489
|
|
|
490
|
-
it(
|
|
491
|
-
const Lazy = lazy(() => Promise.resolve({ default: () => h(
|
|
490
|
+
it('lazy component uses __loading protocol before loaded (for Suspense)', () => {
|
|
491
|
+
const Lazy = lazy(() => Promise.resolve({ default: () => h('div', null, 'loaded') }))
|
|
492
492
|
// Before resolved, __loading returns true and component returns null
|
|
493
493
|
expect(Lazy.__loading()).toBe(true)
|
|
494
494
|
const result = Lazy({})
|
|
495
495
|
expect(result).toBeNull()
|
|
496
496
|
})
|
|
497
497
|
|
|
498
|
-
it(
|
|
499
|
-
const MyComp = () => h(
|
|
498
|
+
it('lazy component renders after loading', async () => {
|
|
499
|
+
const MyComp = () => h('div', null, 'loaded')
|
|
500
500
|
const Lazy = lazy(() => Promise.resolve({ default: MyComp }))
|
|
501
501
|
|
|
502
502
|
// Trigger load
|
|
@@ -509,8 +509,8 @@ describe("@pyreon/solid-compat", () => {
|
|
|
509
509
|
expect(result).not.toBeNull()
|
|
510
510
|
})
|
|
511
511
|
|
|
512
|
-
it(
|
|
513
|
-
const MyComp = () => h(
|
|
512
|
+
it('lazy preload triggers loading', async () => {
|
|
513
|
+
const MyComp = () => h('div', null, 'loaded')
|
|
514
514
|
const Lazy = lazy(() => Promise.resolve({ default: MyComp }))
|
|
515
515
|
|
|
516
516
|
const promise = Lazy.preload()
|
|
@@ -521,9 +521,9 @@ describe("@pyreon/solid-compat", () => {
|
|
|
521
521
|
expect(result).not.toBeNull()
|
|
522
522
|
})
|
|
523
523
|
|
|
524
|
-
it(
|
|
524
|
+
it('lazy preload only loads once', async () => {
|
|
525
525
|
let loadCount = 0
|
|
526
|
-
const MyComp = () => h(
|
|
526
|
+
const MyComp = () => h('div', null, 'loaded')
|
|
527
527
|
const Lazy = lazy(() => {
|
|
528
528
|
loadCount++
|
|
529
529
|
return Promise.resolve({ default: MyComp })
|
|
@@ -538,7 +538,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
538
538
|
|
|
539
539
|
// ─── getOwner / runWithOwner ──────────────────────────────────────────
|
|
540
540
|
|
|
541
|
-
it(
|
|
541
|
+
it('getOwner returns current scope or null', () => {
|
|
542
542
|
// Outside any scope, may return null
|
|
543
543
|
const _outerOwner = getOwner()
|
|
544
544
|
|
|
@@ -549,7 +549,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
549
549
|
})
|
|
550
550
|
})
|
|
551
551
|
|
|
552
|
-
it(
|
|
552
|
+
it('runWithOwner runs fn within the given scope', () => {
|
|
553
553
|
createRoot((dispose) => {
|
|
554
554
|
const owner = getOwner()
|
|
555
555
|
let ranInScope = false
|
|
@@ -564,30 +564,30 @@ describe("@pyreon/solid-compat", () => {
|
|
|
564
564
|
})
|
|
565
565
|
})
|
|
566
566
|
|
|
567
|
-
it(
|
|
567
|
+
it('runWithOwner with null owner', () => {
|
|
568
568
|
const result = runWithOwner(null, () => 42)
|
|
569
569
|
expect(result).toBe(42)
|
|
570
570
|
})
|
|
571
571
|
|
|
572
|
-
it(
|
|
572
|
+
it('runWithOwner restores previous scope even on error', () => {
|
|
573
573
|
createRoot((dispose) => {
|
|
574
574
|
expect(() => {
|
|
575
575
|
runWithOwner(null, () => {
|
|
576
|
-
throw new Error(
|
|
576
|
+
throw new Error('test error')
|
|
577
577
|
})
|
|
578
|
-
}).toThrow(
|
|
578
|
+
}).toThrow('test error')
|
|
579
579
|
dispose()
|
|
580
580
|
})
|
|
581
581
|
})
|
|
582
582
|
|
|
583
583
|
// ─── onMount / onCleanup ──────────────────────────────────────────────
|
|
584
584
|
|
|
585
|
-
it(
|
|
586
|
-
expect(typeof onMount).toBe(
|
|
587
|
-
expect(typeof onCleanup).toBe(
|
|
585
|
+
it('onMount and onCleanup are functions', () => {
|
|
586
|
+
expect(typeof onMount).toBe('function')
|
|
587
|
+
expect(typeof onCleanup).toBe('function')
|
|
588
588
|
})
|
|
589
589
|
|
|
590
|
-
it(
|
|
590
|
+
it('onMount in component context only runs on first render', () => {
|
|
591
591
|
const runner = createHookRunner()
|
|
592
592
|
runner.run(() => {
|
|
593
593
|
onMount(() => undefined)
|
|
@@ -600,7 +600,7 @@ describe("@pyreon/solid-compat", () => {
|
|
|
600
600
|
expect(runner.ctx.pendingEffects).toHaveLength(0) // cleared by beginRender
|
|
601
601
|
})
|
|
602
602
|
|
|
603
|
-
it(
|
|
603
|
+
it('onCleanup in component context registers unmount callback', () => {
|
|
604
604
|
const runner = createHookRunner()
|
|
605
605
|
let cleaned = false
|
|
606
606
|
runner.run(() => {
|
|
@@ -615,40 +615,40 @@ describe("@pyreon/solid-compat", () => {
|
|
|
615
615
|
|
|
616
616
|
// ─── createContext / useContext ────────────────────────────────────────
|
|
617
617
|
|
|
618
|
-
it(
|
|
619
|
-
const Ctx = createContext(
|
|
620
|
-
expect(useContext(Ctx)).toBe(
|
|
618
|
+
it('createContext creates context with default value', () => {
|
|
619
|
+
const Ctx = createContext('default-value')
|
|
620
|
+
expect(useContext(Ctx)).toBe('default-value')
|
|
621
621
|
})
|
|
622
622
|
|
|
623
623
|
// ─── Re-exports ───────────────────────────────────────────────────────
|
|
624
624
|
|
|
625
|
-
it(
|
|
626
|
-
expect(typeof Show).toBe(
|
|
625
|
+
it('Show is exported', () => {
|
|
626
|
+
expect(typeof Show).toBe('function')
|
|
627
627
|
})
|
|
628
628
|
|
|
629
|
-
it(
|
|
630
|
-
expect(typeof Switch).toBe(
|
|
629
|
+
it('Switch is exported', () => {
|
|
630
|
+
expect(typeof Switch).toBe('function')
|
|
631
631
|
})
|
|
632
632
|
|
|
633
|
-
it(
|
|
634
|
-
expect(typeof Match).toBe(
|
|
633
|
+
it('Match is exported', () => {
|
|
634
|
+
expect(typeof Match).toBe('function')
|
|
635
635
|
})
|
|
636
636
|
|
|
637
|
-
it(
|
|
638
|
-
expect(typeof For).toBe(
|
|
637
|
+
it('For is exported', () => {
|
|
638
|
+
expect(typeof For).toBe('function')
|
|
639
639
|
})
|
|
640
640
|
|
|
641
|
-
it(
|
|
642
|
-
expect(typeof Suspense).toBe(
|
|
641
|
+
it('Suspense is exported', () => {
|
|
642
|
+
expect(typeof Suspense).toBe('function')
|
|
643
643
|
})
|
|
644
644
|
|
|
645
|
-
it(
|
|
646
|
-
expect(typeof ErrorBoundary).toBe(
|
|
645
|
+
it('ErrorBoundary is exported', () => {
|
|
646
|
+
expect(typeof ErrorBoundary).toBe('function')
|
|
647
647
|
})
|
|
648
648
|
|
|
649
649
|
// ─── on() edge cases ──────────────────────────────────────────────────
|
|
650
650
|
|
|
651
|
-
it(
|
|
651
|
+
it('on() with single accessor (non-array) tracks correctly', () => {
|
|
652
652
|
createRoot((dispose) => {
|
|
653
653
|
const [count, setCount] = createSignal(10)
|
|
654
654
|
const results: unknown[] = []
|
|
@@ -680,16 +680,16 @@ describe("@pyreon/solid-compat", () => {
|
|
|
680
680
|
|
|
681
681
|
// ─── mergeProps — getter preservation ──────────────────────────────────
|
|
682
682
|
|
|
683
|
-
it(
|
|
683
|
+
it('mergeProps preserves getters for reactivity', () => {
|
|
684
684
|
const source = {} as Record<string, unknown>
|
|
685
|
-
Object.defineProperty(source,
|
|
685
|
+
Object.defineProperty(source, 'x', { get: () => 42, enumerable: true, configurable: true })
|
|
686
686
|
const merged = mergeProps(source) as Record<string, unknown>
|
|
687
687
|
expect(merged.x).toBe(42)
|
|
688
688
|
})
|
|
689
689
|
|
|
690
690
|
// ─── mergeProps edge cases ─────────────────────────────────────────────
|
|
691
691
|
|
|
692
|
-
it(
|
|
692
|
+
it('mergeProps with multiple sources overrides in order', () => {
|
|
693
693
|
const a = { x: 1, y: 2 }
|
|
694
694
|
const b = { y: 3, z: 4 }
|
|
695
695
|
const c = { z: 5 }
|
|
@@ -699,41 +699,41 @@ describe("@pyreon/solid-compat", () => {
|
|
|
699
699
|
expect(merged.z).toBe(5)
|
|
700
700
|
})
|
|
701
701
|
|
|
702
|
-
it(
|
|
702
|
+
it('mergeProps with empty source', () => {
|
|
703
703
|
const merged = mergeProps({}, { a: 1 }) as Record<string, number>
|
|
704
704
|
expect(merged.a).toBe(1)
|
|
705
705
|
})
|
|
706
706
|
|
|
707
707
|
// ─── splitProps — getter preservation ──────────────────────────────────
|
|
708
708
|
|
|
709
|
-
it(
|
|
709
|
+
it('splitProps preserves getters in picked set', () => {
|
|
710
710
|
const source = {} as Record<string, unknown>
|
|
711
|
-
Object.defineProperty(source,
|
|
711
|
+
Object.defineProperty(source, 'a', { get: () => 99, enumerable: true, configurable: true })
|
|
712
712
|
source.b = 2
|
|
713
|
-
const [local, rest] = splitProps(source as { a: number; b: number },
|
|
713
|
+
const [local, rest] = splitProps(source as { a: number; b: number }, 'a')
|
|
714
714
|
expect((local as Record<string, unknown>).a).toBe(99)
|
|
715
715
|
expect((rest as Record<string, unknown>).b).toBe(2)
|
|
716
716
|
})
|
|
717
717
|
|
|
718
718
|
// ─── splitProps edge cases ─────────────────────────────────────────────
|
|
719
719
|
|
|
720
|
-
it(
|
|
720
|
+
it('splitProps with getter in rest', () => {
|
|
721
721
|
const [count, setCount] = createSignal(0)
|
|
722
722
|
const props = {} as Record<string, unknown>
|
|
723
|
-
Object.defineProperty(props,
|
|
723
|
+
Object.defineProperty(props, 'count', {
|
|
724
724
|
get: count,
|
|
725
725
|
enumerable: true,
|
|
726
726
|
configurable: true,
|
|
727
727
|
})
|
|
728
|
-
Object.defineProperty(props,
|
|
729
|
-
value:
|
|
728
|
+
Object.defineProperty(props, 'name', {
|
|
729
|
+
value: 'test',
|
|
730
730
|
writable: true,
|
|
731
731
|
enumerable: true,
|
|
732
732
|
configurable: true,
|
|
733
733
|
})
|
|
734
734
|
|
|
735
|
-
const [local, rest] = splitProps(props as { count: number; name: string },
|
|
736
|
-
expect((local as Record<string, unknown>).name).toBe(
|
|
735
|
+
const [local, rest] = splitProps(props as { count: number; name: string }, 'name')
|
|
736
|
+
expect((local as Record<string, unknown>).name).toBe('test')
|
|
737
737
|
expect((rest as Record<string, unknown>).count).toBe(0)
|
|
738
738
|
setCount(42)
|
|
739
739
|
expect((rest as Record<string, unknown>).count).toBe(42)
|
|
@@ -741,30 +741,30 @@ describe("@pyreon/solid-compat", () => {
|
|
|
741
741
|
|
|
742
742
|
// ─── children edge cases ───────────────────────────────────────────────
|
|
743
743
|
|
|
744
|
-
it(
|
|
744
|
+
it('children resolves non-function values as-is', () => {
|
|
745
745
|
const resolved = children(() => 42 as unknown as ReturnType<typeof h>)
|
|
746
746
|
expect(resolved()).toBe(42)
|
|
747
747
|
})
|
|
748
748
|
|
|
749
|
-
it(
|
|
749
|
+
it('children resolves null', () => {
|
|
750
750
|
const resolved = children(() => null)
|
|
751
751
|
expect(resolved()).toBeNull()
|
|
752
752
|
})
|
|
753
753
|
|
|
754
754
|
// ─── lazy edge: preload called multiple times ──────────────────────────
|
|
755
755
|
|
|
756
|
-
it(
|
|
757
|
-
const MyComp = (props: { msg: string }) => h(
|
|
756
|
+
it('lazy component called after preload resolves renders correctly', async () => {
|
|
757
|
+
const MyComp = (props: { msg: string }) => h('span', null, props.msg)
|
|
758
758
|
const Lazy = lazy(() => Promise.resolve({ default: MyComp }))
|
|
759
759
|
|
|
760
760
|
await Lazy.preload()
|
|
761
|
-
const result = Lazy({ msg:
|
|
761
|
+
const result = Lazy({ msg: 'loaded' })
|
|
762
762
|
expect(result).not.toBeNull()
|
|
763
763
|
})
|
|
764
764
|
|
|
765
765
|
// ─── createRoot restores scope ─────────────────────────────────────────
|
|
766
766
|
|
|
767
|
-
it(
|
|
767
|
+
it('createRoot restores previous scope after fn completes', () => {
|
|
768
768
|
const outerOwner = getOwner()
|
|
769
769
|
createRoot((dispose) => {
|
|
770
770
|
const innerOwner = getOwner()
|
|
@@ -778,17 +778,17 @@ describe("@pyreon/solid-compat", () => {
|
|
|
778
778
|
|
|
779
779
|
// ─── runWithOwner restores scope ───────────────────────────────────────
|
|
780
780
|
|
|
781
|
-
it(
|
|
782
|
-
const result = runWithOwner(null, () =>
|
|
783
|
-
expect(result).toBe(
|
|
781
|
+
it('runWithOwner returns value from fn', () => {
|
|
782
|
+
const result = runWithOwner(null, () => 'hello')
|
|
783
|
+
expect(result).toBe('hello')
|
|
784
784
|
})
|
|
785
785
|
|
|
786
786
|
// ─── JSX runtime ───────────────────────────────────────────────────────
|
|
787
787
|
|
|
788
|
-
it(
|
|
789
|
-
const jsxRuntime = await import(
|
|
790
|
-
expect(typeof jsxRuntime.jsx).toBe(
|
|
791
|
-
expect(typeof jsxRuntime.jsxs).toBe(
|
|
792
|
-
expect(typeof jsxRuntime.Fragment).toBe(
|
|
788
|
+
it('jsx-runtime exports are available', async () => {
|
|
789
|
+
const jsxRuntime = await import('../jsx-runtime')
|
|
790
|
+
expect(typeof jsxRuntime.jsx).toBe('function')
|
|
791
|
+
expect(typeof jsxRuntime.jsxs).toBe('function')
|
|
792
|
+
expect(typeof jsxRuntime.Fragment).toBe('symbol')
|
|
793
793
|
})
|
|
794
794
|
})
|