@pyreon/vue-compat 0.2.0 → 0.3.0
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/lib/analysis/index.js.html +1 -1
- package/lib/index.js +261 -65
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +265 -64
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index2.d.ts +22 -49
- package/lib/types/index2.d.ts.map +1 -1
- package/package.json +14 -4
- package/src/index.ts +307 -81
- package/src/jsx-runtime.ts +178 -0
- package/src/tests/vue-compat.test.ts +348 -119
|
@@ -29,6 +29,17 @@ import {
|
|
|
29
29
|
watch,
|
|
30
30
|
watchEffect,
|
|
31
31
|
} from "../index"
|
|
32
|
+
import {
|
|
33
|
+
beginRender,
|
|
34
|
+
endRender,
|
|
35
|
+
getCurrentCtx,
|
|
36
|
+
jsx,
|
|
37
|
+
jsxDEV,
|
|
38
|
+
jsxs,
|
|
39
|
+
type RenderContext,
|
|
40
|
+
} from "../jsx-runtime"
|
|
41
|
+
|
|
42
|
+
// ─── Test helpers ──────────────────────────────────────────────────────────────
|
|
32
43
|
|
|
33
44
|
function container(): HTMLElement {
|
|
34
45
|
const el = document.createElement("div")
|
|
@@ -36,6 +47,46 @@ function container(): HTMLElement {
|
|
|
36
47
|
return el
|
|
37
48
|
}
|
|
38
49
|
|
|
50
|
+
/** Create a hook context for testing hooks outside of a full render cycle */
|
|
51
|
+
function withHookCtx<T>(fn: (ctx: RenderContext) => T): { result: T; ctx: RenderContext } {
|
|
52
|
+
const ctx: RenderContext = {
|
|
53
|
+
hooks: [],
|
|
54
|
+
scheduleRerender: () => {},
|
|
55
|
+
pendingEffects: [],
|
|
56
|
+
pendingLayoutEffects: [],
|
|
57
|
+
unmounted: false,
|
|
58
|
+
unmountCallbacks: [],
|
|
59
|
+
}
|
|
60
|
+
beginRender(ctx)
|
|
61
|
+
const result = fn(ctx)
|
|
62
|
+
endRender()
|
|
63
|
+
return { result, ctx }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Run a hook function multiple times to simulate re-renders */
|
|
67
|
+
function createHookRunner<T>(fn: () => T): {
|
|
68
|
+
run: () => T
|
|
69
|
+
ctx: RenderContext
|
|
70
|
+
} {
|
|
71
|
+
const ctx: RenderContext = {
|
|
72
|
+
hooks: [],
|
|
73
|
+
scheduleRerender: () => {},
|
|
74
|
+
pendingEffects: [],
|
|
75
|
+
pendingLayoutEffects: [],
|
|
76
|
+
unmounted: false,
|
|
77
|
+
unmountCallbacks: [],
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
run: () => {
|
|
81
|
+
beginRender(ctx)
|
|
82
|
+
const result = fn()
|
|
83
|
+
endRender()
|
|
84
|
+
return result
|
|
85
|
+
},
|
|
86
|
+
ctx,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
39
90
|
describe("@pyreon/vue-compat", () => {
|
|
40
91
|
// ─── ref ────────────────────────────────────────────────────────────────
|
|
41
92
|
|
|
@@ -50,6 +101,38 @@ describe("@pyreon/vue-compat", () => {
|
|
|
50
101
|
expect(count.value).toBe(5)
|
|
51
102
|
})
|
|
52
103
|
|
|
104
|
+
it("ref() is hook-indexed inside component", () => {
|
|
105
|
+
const runner = createHookRunner(() => {
|
|
106
|
+
const count = ref(42)
|
|
107
|
+
return count
|
|
108
|
+
})
|
|
109
|
+
const r1 = runner.run()
|
|
110
|
+
r1.value = 100
|
|
111
|
+
const r2 = runner.run()
|
|
112
|
+
expect(r1).toBe(r2)
|
|
113
|
+
expect(r2.value).toBe(100)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it("ref() setter calls scheduleRerender inside component", () => {
|
|
117
|
+
let rerenders = 0
|
|
118
|
+
const ctx: RenderContext = {
|
|
119
|
+
hooks: [],
|
|
120
|
+
scheduleRerender: () => {
|
|
121
|
+
rerenders++
|
|
122
|
+
},
|
|
123
|
+
pendingEffects: [],
|
|
124
|
+
pendingLayoutEffects: [],
|
|
125
|
+
unmounted: false,
|
|
126
|
+
unmountCallbacks: [],
|
|
127
|
+
}
|
|
128
|
+
beginRender(ctx)
|
|
129
|
+
const count = ref(0)
|
|
130
|
+
endRender()
|
|
131
|
+
|
|
132
|
+
count.value = 1
|
|
133
|
+
expect(rerenders).toBe(1)
|
|
134
|
+
})
|
|
135
|
+
|
|
53
136
|
// ─── shallowRef ────────────────────────────────────────────────────────
|
|
54
137
|
|
|
55
138
|
it("shallowRef() creates a ref (same as ref in Pyreon)", () => {
|
|
@@ -73,10 +156,35 @@ describe("@pyreon/vue-compat", () => {
|
|
|
73
156
|
|
|
74
157
|
expect(runs).toBe(1)
|
|
75
158
|
triggerRef(r)
|
|
76
|
-
expect(runs).toBe(3)
|
|
159
|
+
expect(runs).toBe(3)
|
|
77
160
|
stop()
|
|
78
161
|
})
|
|
79
162
|
|
|
163
|
+
it("triggerRef calls scheduleRerender for hook-indexed refs", () => {
|
|
164
|
+
let rerenders = 0
|
|
165
|
+
const ctx: RenderContext = {
|
|
166
|
+
hooks: [],
|
|
167
|
+
scheduleRerender: () => {
|
|
168
|
+
rerenders++
|
|
169
|
+
},
|
|
170
|
+
pendingEffects: [],
|
|
171
|
+
pendingLayoutEffects: [],
|
|
172
|
+
unmounted: false,
|
|
173
|
+
unmountCallbacks: [],
|
|
174
|
+
}
|
|
175
|
+
beginRender(ctx)
|
|
176
|
+
const r = ref(0)
|
|
177
|
+
endRender()
|
|
178
|
+
|
|
179
|
+
triggerRef(r)
|
|
180
|
+
expect(rerenders).toBe(1)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it("triggerRef is a no-op if ref has no _signal", () => {
|
|
184
|
+
const fakeRef = { value: 42 } as unknown as ReturnType<typeof ref>
|
|
185
|
+
expect(() => triggerRef(fakeRef)).not.toThrow()
|
|
186
|
+
})
|
|
187
|
+
|
|
80
188
|
// ─── isRef ─────────────────────────────────────────────────────────────
|
|
81
189
|
|
|
82
190
|
it("isRef() detects refs", () => {
|
|
@@ -90,6 +198,14 @@ describe("@pyreon/vue-compat", () => {
|
|
|
90
198
|
expect(isRef(c)).toBe(true)
|
|
91
199
|
})
|
|
92
200
|
|
|
201
|
+
it("isRef returns false for undefined", () => {
|
|
202
|
+
expect(isRef(undefined)).toBe(false)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it("isRef returns false for string", () => {
|
|
206
|
+
expect(isRef("hello")).toBe(false)
|
|
207
|
+
})
|
|
208
|
+
|
|
93
209
|
// ─── unref ─────────────────────────────────────────────────────────────
|
|
94
210
|
|
|
95
211
|
it("unref() unwraps refs", () => {
|
|
@@ -116,6 +232,19 @@ describe("@pyreon/vue-compat", () => {
|
|
|
116
232
|
}).toThrow("readonly")
|
|
117
233
|
})
|
|
118
234
|
|
|
235
|
+
it("computed() is hook-indexed inside component", () => {
|
|
236
|
+
const count = ref(5)
|
|
237
|
+
const runner = createHookRunner(() => {
|
|
238
|
+
return computed(() => count.value * 2)
|
|
239
|
+
})
|
|
240
|
+
const c1 = runner.run()
|
|
241
|
+
expect(c1.value).toBe(10)
|
|
242
|
+
count.value = 10
|
|
243
|
+
const c2 = runner.run()
|
|
244
|
+
expect(c1).toBe(c2)
|
|
245
|
+
expect(c2.value).toBe(20)
|
|
246
|
+
})
|
|
247
|
+
|
|
119
248
|
// ─── reactive ──────────────────────────────────────────────────────────
|
|
120
249
|
|
|
121
250
|
it("reactive() creates deep reactive object", () => {
|
|
@@ -133,6 +262,37 @@ describe("@pyreon/vue-compat", () => {
|
|
|
133
262
|
stop()
|
|
134
263
|
})
|
|
135
264
|
|
|
265
|
+
it("reactive() is hook-indexed inside component", () => {
|
|
266
|
+
const runner = createHookRunner(() => {
|
|
267
|
+
return reactive({ x: 0 })
|
|
268
|
+
})
|
|
269
|
+
const s1 = runner.run()
|
|
270
|
+
s1.x = 42
|
|
271
|
+
const s2 = runner.run()
|
|
272
|
+
expect(s1).toBe(s2)
|
|
273
|
+
expect(s2.x).toBe(42)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it("reactive() setter calls scheduleRerender inside component", () => {
|
|
277
|
+
let rerenders = 0
|
|
278
|
+
const ctx: RenderContext = {
|
|
279
|
+
hooks: [],
|
|
280
|
+
scheduleRerender: () => {
|
|
281
|
+
rerenders++
|
|
282
|
+
},
|
|
283
|
+
pendingEffects: [],
|
|
284
|
+
pendingLayoutEffects: [],
|
|
285
|
+
unmounted: false,
|
|
286
|
+
unmountCallbacks: [],
|
|
287
|
+
}
|
|
288
|
+
beginRender(ctx)
|
|
289
|
+
const state = reactive({ count: 0 })
|
|
290
|
+
endRender()
|
|
291
|
+
|
|
292
|
+
state.count = 1
|
|
293
|
+
expect(rerenders).toBe(1)
|
|
294
|
+
})
|
|
295
|
+
|
|
136
296
|
// ─── shallowReactive ──────────────────────────────────────────────────
|
|
137
297
|
|
|
138
298
|
it("shallowReactive() creates reactive object (same as reactive)", () => {
|
|
@@ -168,16 +328,18 @@ describe("@pyreon/vue-compat", () => {
|
|
|
168
328
|
it("readonly() throws on symbol property set", () => {
|
|
169
329
|
const obj = readonly({ count: 0 })
|
|
170
330
|
const sym = Symbol("test")
|
|
171
|
-
// Only internal symbols (V_IS_READONLY, V_RAW) are allowed; all others throw
|
|
172
331
|
expect(() => {
|
|
173
332
|
;(obj as Record<symbol, unknown>)[sym] = "value"
|
|
174
333
|
}).toThrow("readonly")
|
|
175
334
|
})
|
|
176
335
|
|
|
177
|
-
it("readonly()
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
336
|
+
it("readonly() is hook-indexed inside component", () => {
|
|
337
|
+
const runner = createHookRunner(() => {
|
|
338
|
+
return readonly({ count: 0 })
|
|
339
|
+
})
|
|
340
|
+
const r1 = runner.run()
|
|
341
|
+
const r2 = runner.run()
|
|
342
|
+
expect(r1).toBe(r2)
|
|
181
343
|
})
|
|
182
344
|
|
|
183
345
|
// ─── toRaw ─────────────────────────────────────────────────────────────
|
|
@@ -210,15 +372,23 @@ describe("@pyreon/vue-compat", () => {
|
|
|
210
372
|
expect(isRef(countRef)).toBe(true)
|
|
211
373
|
expect(countRef.value).toBe(0)
|
|
212
374
|
|
|
213
|
-
// Writing through ref updates original
|
|
214
375
|
countRef.value = 10
|
|
215
376
|
expect(state.count).toBe(10)
|
|
216
377
|
|
|
217
|
-
// Writing to original updates ref
|
|
218
378
|
state.count = 20
|
|
219
379
|
expect(countRef.value).toBe(20)
|
|
220
380
|
})
|
|
221
381
|
|
|
382
|
+
it("toRef() is hook-indexed inside component", () => {
|
|
383
|
+
const state = reactive({ count: 0 })
|
|
384
|
+
const runner = createHookRunner(() => {
|
|
385
|
+
return toRef(state, "count")
|
|
386
|
+
})
|
|
387
|
+
const r1 = runner.run()
|
|
388
|
+
const r2 = runner.run()
|
|
389
|
+
expect(r1).toBe(r2)
|
|
390
|
+
})
|
|
391
|
+
|
|
222
392
|
// ─── toRefs ────────────────────────────────────────────────────────────
|
|
223
393
|
|
|
224
394
|
it("toRefs() converts reactive to refs", () => {
|
|
@@ -233,6 +403,16 @@ describe("@pyreon/vue-compat", () => {
|
|
|
233
403
|
expect(state.a).toBe(10)
|
|
234
404
|
})
|
|
235
405
|
|
|
406
|
+
it("toRefs() is hook-indexed inside component", () => {
|
|
407
|
+
const state = reactive({ x: 1, y: 2 })
|
|
408
|
+
const runner = createHookRunner(() => {
|
|
409
|
+
return toRefs(state)
|
|
410
|
+
})
|
|
411
|
+
const r1 = runner.run()
|
|
412
|
+
const r2 = runner.run()
|
|
413
|
+
expect(r1).toBe(r2)
|
|
414
|
+
})
|
|
415
|
+
|
|
236
416
|
// ─── watch ──────────────────────────────────────────────────────────────
|
|
237
417
|
|
|
238
418
|
it("watch() fires on ref change", () => {
|
|
@@ -314,7 +494,61 @@ describe("@pyreon/vue-compat", () => {
|
|
|
314
494
|
|
|
315
495
|
stop()
|
|
316
496
|
count.value = 2
|
|
317
|
-
expect(calls).toEqual([1])
|
|
497
|
+
expect(calls).toEqual([1])
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
it("watch() is hook-indexed inside component", () => {
|
|
501
|
+
const count = ref(0)
|
|
502
|
+
const calls: number[] = []
|
|
503
|
+
const runner = createHookRunner(() => {
|
|
504
|
+
return watch(count, (newVal) => {
|
|
505
|
+
calls.push(newVal)
|
|
506
|
+
})
|
|
507
|
+
})
|
|
508
|
+
const stop1 = runner.run()
|
|
509
|
+
const stop2 = runner.run()
|
|
510
|
+
expect(stop1).toBe(stop2)
|
|
511
|
+
|
|
512
|
+
count.value = 1
|
|
513
|
+
expect(calls).toEqual([1])
|
|
514
|
+
stop1()
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
it("watch with immediate tracks subsequent changes too", () => {
|
|
518
|
+
const count = ref(0)
|
|
519
|
+
const calls: [number, number | undefined][] = []
|
|
520
|
+
|
|
521
|
+
const stop = watch(
|
|
522
|
+
count,
|
|
523
|
+
(newVal, oldVal) => {
|
|
524
|
+
calls.push([newVal, oldVal])
|
|
525
|
+
},
|
|
526
|
+
{ immediate: true },
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
expect(calls[0]).toEqual([0, undefined])
|
|
530
|
+
|
|
531
|
+
count.value = 10
|
|
532
|
+
const lastCall = calls[calls.length - 1]!
|
|
533
|
+
expect(lastCall[0]).toBe(10)
|
|
534
|
+
|
|
535
|
+
stop()
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
it("watch with getter function and immediate", () => {
|
|
539
|
+
const count = ref(5)
|
|
540
|
+
const calls: [number, number | undefined][] = []
|
|
541
|
+
|
|
542
|
+
const stop = watch(
|
|
543
|
+
() => count.value * 2,
|
|
544
|
+
(newVal, oldVal) => {
|
|
545
|
+
calls.push([newVal, oldVal])
|
|
546
|
+
},
|
|
547
|
+
{ immediate: true },
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
expect(calls[0]).toEqual([10, undefined])
|
|
551
|
+
stop()
|
|
318
552
|
})
|
|
319
553
|
|
|
320
554
|
// ─── watchEffect ───────────────────────────────────────────────────────
|
|
@@ -337,6 +571,23 @@ describe("@pyreon/vue-compat", () => {
|
|
|
337
571
|
expect(values).toEqual([0, 1, 2])
|
|
338
572
|
})
|
|
339
573
|
|
|
574
|
+
it("watchEffect() is hook-indexed inside component", () => {
|
|
575
|
+
const count = ref(0)
|
|
576
|
+
const values: number[] = []
|
|
577
|
+
const runner = createHookRunner(() => {
|
|
578
|
+
return watchEffect(() => {
|
|
579
|
+
values.push(count.value)
|
|
580
|
+
})
|
|
581
|
+
})
|
|
582
|
+
const stop1 = runner.run()
|
|
583
|
+
const stop2 = runner.run()
|
|
584
|
+
expect(stop1).toBe(stop2)
|
|
585
|
+
|
|
586
|
+
count.value = 1
|
|
587
|
+
expect(values).toEqual([0, 1])
|
|
588
|
+
stop1()
|
|
589
|
+
})
|
|
590
|
+
|
|
340
591
|
// ─── nextTick ──────────────────────────────────────────────────────────
|
|
341
592
|
|
|
342
593
|
it("nextTick() resolves after flush", async () => {
|
|
@@ -346,9 +597,9 @@ describe("@pyreon/vue-compat", () => {
|
|
|
346
597
|
expect(count.value).toBe(42)
|
|
347
598
|
})
|
|
348
599
|
|
|
349
|
-
// ─── lifecycle
|
|
600
|
+
// ─── lifecycle (with Pyreon fallback) ──────────────────────────────────
|
|
350
601
|
|
|
351
|
-
it("onMounted/onUnmounted lifecycle hooks work", () => {
|
|
602
|
+
it("onMounted/onUnmounted lifecycle hooks work with defineComponent", () => {
|
|
352
603
|
const mounted: string[] = []
|
|
353
604
|
const unmounted: string[] = []
|
|
354
605
|
|
|
@@ -416,6 +667,25 @@ describe("@pyreon/vue-compat", () => {
|
|
|
416
667
|
expect(typeof onUpdated).toBe("function")
|
|
417
668
|
})
|
|
418
669
|
|
|
670
|
+
it("onMounted queues pendingEffect inside hook context", () => {
|
|
671
|
+
const { ctx } = withHookCtx(() => {
|
|
672
|
+
onMounted(() => {})
|
|
673
|
+
})
|
|
674
|
+
expect(ctx.pendingEffects.length).toBe(1)
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
it("onUnmounted pushes to unmountCallbacks inside hook context", () => {
|
|
678
|
+
const calls: string[] = []
|
|
679
|
+
const { ctx } = withHookCtx(() => {
|
|
680
|
+
onUnmounted(() => {
|
|
681
|
+
calls.push("unmounted")
|
|
682
|
+
})
|
|
683
|
+
})
|
|
684
|
+
expect(ctx.unmountCallbacks.length).toBe(1)
|
|
685
|
+
ctx.unmountCallbacks[0]!()
|
|
686
|
+
expect(calls).toEqual(["unmounted"])
|
|
687
|
+
})
|
|
688
|
+
|
|
419
689
|
// ─── provide / inject ─────────────────────────────────────────────────
|
|
420
690
|
|
|
421
691
|
it("provide/inject with string key", () => {
|
|
@@ -439,6 +709,23 @@ describe("@pyreon/vue-compat", () => {
|
|
|
439
709
|
expect(inject(key)).toBeUndefined()
|
|
440
710
|
})
|
|
441
711
|
|
|
712
|
+
it("provide is hook-indexed inside component", () => {
|
|
713
|
+
const key = "hook-provide-test"
|
|
714
|
+
const runner = createHookRunner(() => {
|
|
715
|
+
provide(key, "value")
|
|
716
|
+
})
|
|
717
|
+
runner.run()
|
|
718
|
+
runner.run()
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
it("provide overwrites previously provided value (outside component)", () => {
|
|
722
|
+
const key = "overwrite-test"
|
|
723
|
+
provide(key, "first")
|
|
724
|
+
expect(inject(key)).toBe("first")
|
|
725
|
+
provide(key, "second")
|
|
726
|
+
expect(inject(key)).toBe("second")
|
|
727
|
+
})
|
|
728
|
+
|
|
442
729
|
// ─── defineComponent ──────────────────────────────────────────────────
|
|
443
730
|
|
|
444
731
|
it("defineComponent with setup function returning render fn", () => {
|
|
@@ -521,12 +808,12 @@ describe("@pyreon/vue-compat", () => {
|
|
|
521
808
|
|
|
522
809
|
it("createApp().mount with string selector", () => {
|
|
523
810
|
const el = container()
|
|
524
|
-
el.id = "test-app-mount"
|
|
811
|
+
el.id = "test-app-mount-vue"
|
|
525
812
|
document.body.appendChild(el)
|
|
526
813
|
|
|
527
814
|
const Comp = () => h("div", null, "selector-app")
|
|
528
815
|
const app = createApp(Comp)
|
|
529
|
-
const unmount = app.mount("#test-app-mount")
|
|
816
|
+
const unmount = app.mount("#test-app-mount-vue")
|
|
530
817
|
expect(el.textContent).toBe("selector-app")
|
|
531
818
|
unmount()
|
|
532
819
|
})
|
|
@@ -546,6 +833,15 @@ describe("@pyreon/vue-compat", () => {
|
|
|
546
833
|
unmount()
|
|
547
834
|
})
|
|
548
835
|
|
|
836
|
+
it("createApp with no props", () => {
|
|
837
|
+
const Comp = () => h("div", null, "no-props")
|
|
838
|
+
const el = container()
|
|
839
|
+
const app = createApp(Comp)
|
|
840
|
+
const unmount = app.mount(el)
|
|
841
|
+
expect(el.textContent).toBe("no-props")
|
|
842
|
+
unmount()
|
|
843
|
+
})
|
|
844
|
+
|
|
549
845
|
// ─── batch ────────────────────────────────────────────────────────────
|
|
550
846
|
|
|
551
847
|
it("batch is re-exported and coalesces updates", () => {
|
|
@@ -562,137 +858,70 @@ describe("@pyreon/vue-compat", () => {
|
|
|
562
858
|
count.value = 3
|
|
563
859
|
})
|
|
564
860
|
|
|
565
|
-
// Should have initial (0) and then final batch result (3)
|
|
566
861
|
expect(values[0]).toBe(0)
|
|
567
862
|
expect(values[values.length - 1]).toBe(3)
|
|
568
863
|
stop()
|
|
569
864
|
})
|
|
570
865
|
|
|
571
|
-
// ───
|
|
866
|
+
// ─── jsx-runtime ─────────────────────────────────────────────────────
|
|
572
867
|
|
|
573
|
-
it("
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
// Should not throw
|
|
577
|
-
expect(() => triggerRef(fakeRef)).not.toThrow()
|
|
868
|
+
it("jsx creates DOM element VNodes", () => {
|
|
869
|
+
const vnode = jsx("div", { class: "test", children: "hello" })
|
|
870
|
+
expect(vnode.type).toBe("div")
|
|
578
871
|
})
|
|
579
872
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
it("isRef returns false for undefined", () => {
|
|
583
|
-
expect(isRef(undefined)).toBe(false)
|
|
873
|
+
it("jsxs is same as jsx", () => {
|
|
874
|
+
expect(jsxs).toBe(jsx)
|
|
584
875
|
})
|
|
585
876
|
|
|
586
|
-
it("
|
|
587
|
-
expect(
|
|
877
|
+
it("jsxDEV is same as jsx", () => {
|
|
878
|
+
expect(jsxDEV).toBe(jsx)
|
|
588
879
|
})
|
|
589
880
|
|
|
590
|
-
|
|
881
|
+
it("jsx wraps component functions", () => {
|
|
882
|
+
function MyComp() {
|
|
883
|
+
return h("div", null, "test")
|
|
884
|
+
}
|
|
885
|
+
const vnode = jsx(MyComp, {})
|
|
886
|
+
expect(vnode.type).not.toBe(MyComp)
|
|
887
|
+
expect(typeof vnode.type).toBe("function")
|
|
888
|
+
})
|
|
591
889
|
|
|
592
|
-
it("
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
// The proxy get trap handles this symbol
|
|
596
|
-
const _V_IS_READONLY = Symbol("__v_isReadonly")
|
|
597
|
-
// We can't access the private symbol directly, but we can verify it doesn't throw
|
|
598
|
-
// when accessing regular properties
|
|
599
|
-
expect(obj.count).toBe(0)
|
|
890
|
+
it("jsx passes key as prop", () => {
|
|
891
|
+
const vnode = jsx("div", { children: "test" }, "my-key")
|
|
892
|
+
expect(vnode.props?.key).toBe("my-key")
|
|
600
893
|
})
|
|
601
894
|
|
|
602
|
-
|
|
895
|
+
it("getCurrentCtx returns null outside render", () => {
|
|
896
|
+
expect(getCurrentCtx()).toBeNull()
|
|
897
|
+
})
|
|
603
898
|
|
|
604
|
-
it("
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
expect(
|
|
899
|
+
it("getCurrentCtx returns context during render", () => {
|
|
900
|
+
withHookCtx((c) => {
|
|
901
|
+
expect(getCurrentCtx()).toBe(c)
|
|
902
|
+
})
|
|
903
|
+
expect(getCurrentCtx()).toBeNull()
|
|
609
904
|
})
|
|
610
905
|
|
|
611
|
-
// ───
|
|
906
|
+
// ─── standalone (outside component) ──────────────────────────────────
|
|
612
907
|
|
|
613
|
-
it("
|
|
908
|
+
it("ref() outside component creates standalone ref", () => {
|
|
614
909
|
const count = ref(0)
|
|
615
|
-
const calls: [number, number | undefined][] = []
|
|
616
|
-
|
|
617
|
-
const stop = watch(
|
|
618
|
-
count,
|
|
619
|
-
(newVal, oldVal) => {
|
|
620
|
-
calls.push([newVal, oldVal])
|
|
621
|
-
},
|
|
622
|
-
{ immediate: true },
|
|
623
|
-
)
|
|
624
|
-
|
|
625
|
-
// First call from immediate
|
|
626
|
-
expect(calls[0]).toEqual([0, undefined])
|
|
627
|
-
|
|
628
910
|
count.value = 10
|
|
629
|
-
|
|
630
|
-
const lastCall = calls[calls.length - 1]!
|
|
631
|
-
expect(lastCall[0]).toBe(10)
|
|
632
|
-
|
|
633
|
-
stop()
|
|
911
|
+
expect(count.value).toBe(10)
|
|
634
912
|
})
|
|
635
913
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
it("watch with getter function and immediate", () => {
|
|
914
|
+
it("computed() outside component creates standalone computed", () => {
|
|
639
915
|
const count = ref(5)
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
(newVal, oldVal) => {
|
|
645
|
-
calls.push([newVal, oldVal])
|
|
646
|
-
},
|
|
647
|
-
{ immediate: true },
|
|
648
|
-
)
|
|
649
|
-
|
|
650
|
-
expect(calls[0]).toEqual([10, undefined])
|
|
651
|
-
stop()
|
|
652
|
-
})
|
|
653
|
-
|
|
654
|
-
// ─── createApp with no props ───────────────────────────────────────────
|
|
655
|
-
|
|
656
|
-
it("createApp with no props", () => {
|
|
657
|
-
const Comp = () => h("div", null, "no-props")
|
|
658
|
-
const el = container()
|
|
659
|
-
const app = createApp(Comp)
|
|
660
|
-
const unmount = app.mount(el)
|
|
661
|
-
expect(el.textContent).toBe("no-props")
|
|
662
|
-
unmount()
|
|
663
|
-
})
|
|
664
|
-
|
|
665
|
-
// ─── defineComponent setup returning function ──────────────────────────
|
|
666
|
-
|
|
667
|
-
it("defineComponent setup returning VNodeChild (non-function) renders", () => {
|
|
668
|
-
const Comp = defineComponent({
|
|
669
|
-
setup() {
|
|
670
|
-
return h("p", null, "static-vnode")
|
|
671
|
-
},
|
|
672
|
-
})
|
|
673
|
-
const el = container()
|
|
674
|
-
const unmount = mount(h(Comp, null), el)
|
|
675
|
-
expect(el.querySelector("p")?.textContent).toBe("static-vnode")
|
|
676
|
-
unmount()
|
|
677
|
-
})
|
|
678
|
-
|
|
679
|
-
// ─── provide overwrite ──────────────────────────────────────────────────
|
|
680
|
-
|
|
681
|
-
it("provide overwrites previously provided value", () => {
|
|
682
|
-
const key = "overwrite-test"
|
|
683
|
-
provide(key, "first")
|
|
684
|
-
expect(inject(key)).toBe("first")
|
|
685
|
-
provide(key, "second")
|
|
686
|
-
expect(inject(key)).toBe("second")
|
|
916
|
+
const doubled = computed(() => count.value * 2)
|
|
917
|
+
expect(doubled.value).toBe(10)
|
|
918
|
+
count.value = 7
|
|
919
|
+
expect(doubled.value).toBe(14)
|
|
687
920
|
})
|
|
688
921
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
provide(key, 123)
|
|
694
|
-
expect(inject(key)).toBe(123)
|
|
695
|
-
// Call again to ensure it reuses
|
|
696
|
-
expect(inject(key)).toBe(123)
|
|
922
|
+
it("reactive() outside component creates standalone reactive", () => {
|
|
923
|
+
const state = reactive({ x: 1 })
|
|
924
|
+
state.x = 2
|
|
925
|
+
expect(state.x).toBe(2)
|
|
697
926
|
})
|
|
698
927
|
})
|