@pyreon/core 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 +2 -2
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +33 -5
- package/lib/index.js.map +1 -1
- package/lib/jsx-dev-runtime.js.map +1 -1
- package/lib/jsx-runtime.js.map +1 -1
- package/lib/types/index.d.ts +145 -98
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/jsx-dev-runtime.d.ts +94 -94
- package/lib/types/jsx-runtime.d.ts +94 -94
- package/package.json +11 -11
- package/src/component.ts +2 -2
- package/src/context.ts +75 -4
- package/src/dynamic.ts +4 -4
- package/src/error-boundary.ts +10 -10
- package/src/for.ts +8 -2
- package/src/h.ts +4 -4
- package/src/index.ts +30 -27
- package/src/jsx-dev-runtime.ts +1 -1
- package/src/jsx-runtime.ts +108 -108
- package/src/lazy.ts +4 -4
- package/src/lifecycle.ts +6 -6
- package/src/portal.ts +2 -2
- package/src/show.ts +4 -4
- package/src/style.ts +51 -51
- package/src/suspense.ts +8 -8
- package/src/telemetry.ts +1 -1
- package/src/tests/component.test.ts +60 -60
- package/src/tests/context.test.ts +102 -102
- package/src/tests/core.test.ts +376 -376
- package/src/tests/cx.test.ts +34 -34
- package/src/tests/dynamic.test.ts +28 -28
- package/src/tests/error-boundary.test.ts +51 -51
- package/src/tests/for.test.ts +26 -26
- package/src/tests/h.test.ts +100 -100
- package/src/tests/jsx-compat.test.tsx +41 -41
- package/src/tests/lazy.test.ts +28 -28
- package/src/tests/lifecycle.test.ts +35 -35
- package/src/tests/map-array.test.ts +36 -36
- package/src/tests/portal.test.ts +21 -21
- package/src/tests/props-extended.test.ts +51 -51
- package/src/tests/props.test.ts +62 -62
- package/src/tests/ref.test.ts +20 -20
- package/src/tests/show.test.ts +94 -94
- package/src/tests/style.test.ts +101 -101
- package/src/tests/suspense.test.ts +44 -44
- package/src/tests/telemetry.test.ts +35 -35
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import { Fragment, h } from
|
|
2
|
-
import { Suspense } from
|
|
3
|
-
import type { ComponentFn, VNodeChild } from
|
|
1
|
+
import { Fragment, h } from '../h'
|
|
2
|
+
import { Suspense } from '../suspense'
|
|
3
|
+
import type { ComponentFn, VNodeChild } from '../types'
|
|
4
4
|
|
|
5
|
-
describe(
|
|
6
|
-
test(
|
|
5
|
+
describe('Suspense', () => {
|
|
6
|
+
test('returns a Fragment VNode', () => {
|
|
7
7
|
const node = Suspense({
|
|
8
|
-
fallback: h(
|
|
9
|
-
children: h(
|
|
8
|
+
fallback: h('div', null, 'loading'),
|
|
9
|
+
children: h('div', null, 'content'),
|
|
10
10
|
})
|
|
11
11
|
expect(node.type).toBe(Fragment)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
-
test(
|
|
14
|
+
test('Fragment contains a single reactive getter child', () => {
|
|
15
15
|
const node = Suspense({
|
|
16
|
-
fallback: h(
|
|
17
|
-
children: h(
|
|
16
|
+
fallback: h('span', null, 'loading'),
|
|
17
|
+
children: h('div', null, 'content'),
|
|
18
18
|
})
|
|
19
19
|
expect(node.children).toHaveLength(1)
|
|
20
|
-
expect(typeof node.children[0]).toBe(
|
|
20
|
+
expect(typeof node.children[0]).toBe('function')
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
-
test(
|
|
24
|
-
const child = h(
|
|
23
|
+
test('renders children when not loading (plain VNode)', () => {
|
|
24
|
+
const child = h('div', null, 'loaded')
|
|
25
25
|
const node = Suspense({
|
|
26
|
-
fallback: h(
|
|
26
|
+
fallback: h('span', null, 'loading'),
|
|
27
27
|
children: child,
|
|
28
28
|
})
|
|
29
29
|
const getter = node.children[0] as () => VNodeChild
|
|
30
30
|
expect(getter()).toBe(child)
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
test(
|
|
34
|
-
const regularComp: ComponentFn = () => h(
|
|
33
|
+
test('renders children when child type has no __loading', () => {
|
|
34
|
+
const regularComp: ComponentFn = () => h('div', null)
|
|
35
35
|
const child = h(regularComp, null)
|
|
36
36
|
const node = Suspense({
|
|
37
|
-
fallback:
|
|
37
|
+
fallback: 'loading',
|
|
38
38
|
children: child,
|
|
39
39
|
})
|
|
40
40
|
const getter = node.children[0] as () => VNodeChild
|
|
41
41
|
expect(getter()).toBe(child)
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
test(
|
|
45
|
-
const fallback = h(
|
|
46
|
-
const lazyFn = (() => h(
|
|
44
|
+
test('renders fallback when child __loading() is true', () => {
|
|
45
|
+
const fallback = h('span', null, 'loading...')
|
|
46
|
+
const lazyFn = (() => h('div', null)) as unknown as ComponentFn & {
|
|
47
47
|
__loading: () => boolean
|
|
48
48
|
}
|
|
49
49
|
lazyFn.__loading = () => true
|
|
@@ -54,9 +54,9 @@ describe("Suspense", () => {
|
|
|
54
54
|
expect(getter()).toBe(fallback)
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
-
test(
|
|
58
|
-
const fallback = h(
|
|
59
|
-
const lazyFn = (() => h(
|
|
57
|
+
test('renders children when __loading() is false', () => {
|
|
58
|
+
const fallback = h('span', null, 'loading...')
|
|
59
|
+
const lazyFn = (() => h('div', null)) as unknown as ComponentFn & {
|
|
60
60
|
__loading: () => boolean
|
|
61
61
|
}
|
|
62
62
|
lazyFn.__loading = () => false
|
|
@@ -67,19 +67,19 @@ describe("Suspense", () => {
|
|
|
67
67
|
expect(getter()).toBe(child)
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
test(
|
|
71
|
-
const child = h(
|
|
70
|
+
test('handles function children (reactive getter)', () => {
|
|
71
|
+
const child = h('div', null, 'content')
|
|
72
72
|
const node = Suspense({
|
|
73
|
-
fallback: h(
|
|
73
|
+
fallback: h('span', null, 'loading'),
|
|
74
74
|
children: () => child,
|
|
75
75
|
})
|
|
76
76
|
const getter = node.children[0] as () => VNodeChild
|
|
77
77
|
expect(getter()).toBe(child)
|
|
78
78
|
})
|
|
79
79
|
|
|
80
|
-
test(
|
|
81
|
-
const fbNode = h(
|
|
82
|
-
const lazyFn = (() => h(
|
|
80
|
+
test('evaluates function fallback', () => {
|
|
81
|
+
const fbNode = h('div', null, 'fb')
|
|
82
|
+
const lazyFn = (() => h('div', null)) as unknown as ComponentFn & {
|
|
83
83
|
__loading: () => boolean
|
|
84
84
|
}
|
|
85
85
|
lazyFn.__loading = () => true
|
|
@@ -90,44 +90,44 @@ describe("Suspense", () => {
|
|
|
90
90
|
expect(getter()).toBe(fbNode)
|
|
91
91
|
})
|
|
92
92
|
|
|
93
|
-
test(
|
|
94
|
-
const node = Suspense({ fallback:
|
|
93
|
+
test('handles null/undefined children', () => {
|
|
94
|
+
const node = Suspense({ fallback: 'loading' })
|
|
95
95
|
const getter = node.children[0] as () => VNodeChild
|
|
96
96
|
// undefined children — not a VNode, not loading
|
|
97
97
|
expect(getter()).toBeUndefined()
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
-
test(
|
|
101
|
-
const node = Suspense({ fallback:
|
|
100
|
+
test('handles string children', () => {
|
|
101
|
+
const node = Suspense({ fallback: 'loading', children: 'text content' })
|
|
102
102
|
const getter = node.children[0] as () => VNodeChild
|
|
103
|
-
expect(getter()).toBe(
|
|
103
|
+
expect(getter()).toBe('text content')
|
|
104
104
|
})
|
|
105
105
|
|
|
106
|
-
test(
|
|
107
|
-
const children = [h(
|
|
106
|
+
test('handles array children (not loading)', () => {
|
|
107
|
+
const children = [h('a', null), h('b', null)]
|
|
108
108
|
const node = Suspense({
|
|
109
|
-
fallback:
|
|
109
|
+
fallback: 'loading',
|
|
110
110
|
children: children as unknown as VNodeChild,
|
|
111
111
|
})
|
|
112
112
|
const getter = node.children[0] as () => VNodeChild
|
|
113
113
|
expect(getter()).toBe(children)
|
|
114
114
|
})
|
|
115
115
|
|
|
116
|
-
test(
|
|
117
|
-
const warnSpy = vi.spyOn(console,
|
|
118
|
-
Suspense({ fallback: undefined as unknown as VNodeChild, children:
|
|
119
|
-
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining(
|
|
116
|
+
test('warns when fallback prop is missing', () => {
|
|
117
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
118
|
+
Suspense({ fallback: undefined as unknown as VNodeChild, children: 'x' })
|
|
119
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('<Suspense>'))
|
|
120
120
|
warnSpy.mockRestore()
|
|
121
121
|
})
|
|
122
122
|
|
|
123
|
-
test(
|
|
123
|
+
test('transition from loading to loaded', () => {
|
|
124
124
|
let isLoading = true
|
|
125
|
-
const lazyFn = (() => h(
|
|
125
|
+
const lazyFn = (() => h('div', null)) as unknown as ComponentFn & {
|
|
126
126
|
__loading: () => boolean
|
|
127
127
|
}
|
|
128
128
|
lazyFn.__loading = () => isLoading
|
|
129
129
|
const child = h(lazyFn, null)
|
|
130
|
-
const fallback = h(
|
|
130
|
+
const fallback = h('span', null, 'loading')
|
|
131
131
|
|
|
132
132
|
const node = Suspense({ fallback, children: child })
|
|
133
133
|
const getter = node.children[0] as () => VNodeChild
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import type { ErrorContext } from
|
|
2
|
-
import { registerErrorHandler, reportError } from
|
|
1
|
+
import type { ErrorContext } from '../telemetry'
|
|
2
|
+
import { registerErrorHandler, reportError } from '../telemetry'
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
test(
|
|
4
|
+
describe('registerErrorHandler', () => {
|
|
5
|
+
test('registers handler that receives error context', () => {
|
|
6
6
|
const contexts: ErrorContext[] = []
|
|
7
7
|
const unsub = registerErrorHandler((ctx) => {
|
|
8
8
|
contexts.push(ctx)
|
|
9
9
|
})
|
|
10
10
|
|
|
11
11
|
const ctx: ErrorContext = {
|
|
12
|
-
component:
|
|
13
|
-
phase:
|
|
14
|
-
error: new Error(
|
|
12
|
+
component: 'TestComp',
|
|
13
|
+
phase: 'render',
|
|
14
|
+
error: new Error('test'),
|
|
15
15
|
timestamp: 1234567890,
|
|
16
16
|
}
|
|
17
17
|
reportError(ctx)
|
|
@@ -21,28 +21,28 @@ describe("registerErrorHandler", () => {
|
|
|
21
21
|
unsub()
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
test(
|
|
24
|
+
test('returns unregister function', () => {
|
|
25
25
|
let count = 0
|
|
26
26
|
const unsub = registerErrorHandler(() => {
|
|
27
27
|
count++
|
|
28
28
|
})
|
|
29
29
|
|
|
30
|
-
reportError({ component:
|
|
30
|
+
reportError({ component: 'A', phase: 'setup', error: 'e1', timestamp: 0 })
|
|
31
31
|
expect(count).toBe(1)
|
|
32
32
|
|
|
33
33
|
unsub()
|
|
34
34
|
|
|
35
|
-
reportError({ component:
|
|
35
|
+
reportError({ component: 'B', phase: 'render', error: 'e2', timestamp: 0 })
|
|
36
36
|
expect(count).toBe(1) // not called after unregister
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
-
test(
|
|
39
|
+
test('multiple handlers are all called', () => {
|
|
40
40
|
let count = 0
|
|
41
41
|
const unsub1 = registerErrorHandler(() => count++)
|
|
42
42
|
const unsub2 = registerErrorHandler(() => count++)
|
|
43
43
|
const unsub3 = registerErrorHandler(() => count++)
|
|
44
44
|
|
|
45
|
-
reportError({ component:
|
|
45
|
+
reportError({ component: 'X', phase: 'mount', error: 'err', timestamp: 0 })
|
|
46
46
|
expect(count).toBe(3)
|
|
47
47
|
|
|
48
48
|
unsub1()
|
|
@@ -50,12 +50,12 @@ describe("registerErrorHandler", () => {
|
|
|
50
50
|
unsub3()
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
-
test(
|
|
53
|
+
test('handler errors are swallowed — subsequent handlers still called', () => {
|
|
54
54
|
let secondCalled = false
|
|
55
55
|
let thirdCalled = false
|
|
56
56
|
|
|
57
57
|
const unsub1 = registerErrorHandler(() => {
|
|
58
|
-
throw new Error(
|
|
58
|
+
throw new Error('handler crash')
|
|
59
59
|
})
|
|
60
60
|
const unsub2 = registerErrorHandler(() => {
|
|
61
61
|
secondCalled = true
|
|
@@ -66,7 +66,7 @@ describe("registerErrorHandler", () => {
|
|
|
66
66
|
|
|
67
67
|
// Should not throw
|
|
68
68
|
expect(() =>
|
|
69
|
-
reportError({ component:
|
|
69
|
+
reportError({ component: 'Y', phase: 'unmount', error: 'err', timestamp: 0 }),
|
|
70
70
|
).not.toThrow()
|
|
71
71
|
expect(secondCalled).toBe(true)
|
|
72
72
|
expect(thirdCalled).toBe(true)
|
|
@@ -76,64 +76,64 @@ describe("registerErrorHandler", () => {
|
|
|
76
76
|
unsub3()
|
|
77
77
|
})
|
|
78
78
|
|
|
79
|
-
test(
|
|
79
|
+
test('unregistering one handler does not affect others', () => {
|
|
80
80
|
const calls: string[] = []
|
|
81
|
-
const unsub1 = registerErrorHandler(() => calls.push(
|
|
82
|
-
const unsub2 = registerErrorHandler(() => calls.push(
|
|
83
|
-
const unsub3 = registerErrorHandler(() => calls.push(
|
|
81
|
+
const unsub1 = registerErrorHandler(() => calls.push('a'))
|
|
82
|
+
const unsub2 = registerErrorHandler(() => calls.push('b'))
|
|
83
|
+
const unsub3 = registerErrorHandler(() => calls.push('c'))
|
|
84
84
|
|
|
85
85
|
unsub2() // remove middle handler
|
|
86
86
|
|
|
87
|
-
reportError({ component:
|
|
88
|
-
expect(calls).toEqual([
|
|
87
|
+
reportError({ component: 'Z', phase: 'effect', error: 'e', timestamp: 0 })
|
|
88
|
+
expect(calls).toEqual(['a', 'c'])
|
|
89
89
|
|
|
90
90
|
unsub1()
|
|
91
91
|
unsub3()
|
|
92
92
|
})
|
|
93
93
|
})
|
|
94
94
|
|
|
95
|
-
describe(
|
|
96
|
-
test(
|
|
95
|
+
describe('reportError', () => {
|
|
96
|
+
test('no-op when no handlers registered', () => {
|
|
97
97
|
// Should not throw
|
|
98
98
|
expect(() =>
|
|
99
|
-
reportError({ component:
|
|
99
|
+
reportError({ component: 'None', phase: 'setup', error: 'err', timestamp: 0 }),
|
|
100
100
|
).not.toThrow()
|
|
101
101
|
})
|
|
102
102
|
|
|
103
|
-
test(
|
|
103
|
+
test('passes full ErrorContext to handler', () => {
|
|
104
104
|
let received: ErrorContext | null = null
|
|
105
105
|
const unsub = registerErrorHandler((ctx) => {
|
|
106
106
|
received = ctx
|
|
107
107
|
})
|
|
108
108
|
|
|
109
109
|
const ctx: ErrorContext = {
|
|
110
|
-
component:
|
|
111
|
-
phase:
|
|
112
|
-
error: new Error(
|
|
110
|
+
component: 'MyComp',
|
|
111
|
+
phase: 'render',
|
|
112
|
+
error: new Error('detail'),
|
|
113
113
|
timestamp: 999,
|
|
114
|
-
props: { a: 1, b:
|
|
114
|
+
props: { a: 1, b: 'two' },
|
|
115
115
|
}
|
|
116
116
|
reportError(ctx)
|
|
117
117
|
|
|
118
118
|
expect(received).not.toBeNull()
|
|
119
|
-
expect(received!.component).toBe(
|
|
120
|
-
expect(received!.phase).toBe(
|
|
119
|
+
expect(received!.component).toBe('MyComp')
|
|
120
|
+
expect(received!.phase).toBe('render')
|
|
121
121
|
expect(received!.error).toBeInstanceOf(Error)
|
|
122
122
|
expect(received!.timestamp).toBe(999)
|
|
123
|
-
expect(received!.props).toEqual({ a: 1, b:
|
|
123
|
+
expect(received!.props).toEqual({ a: 1, b: 'two' })
|
|
124
124
|
|
|
125
125
|
unsub()
|
|
126
126
|
})
|
|
127
127
|
|
|
128
|
-
test(
|
|
129
|
-
const phases: ErrorContext[
|
|
128
|
+
test('handles all phase types', () => {
|
|
129
|
+
const phases: ErrorContext['phase'][] = ['setup', 'render', 'mount', 'unmount', 'effect']
|
|
130
130
|
const seen: string[] = []
|
|
131
131
|
const unsub = registerErrorHandler((ctx) => {
|
|
132
132
|
seen.push(ctx.phase)
|
|
133
133
|
})
|
|
134
134
|
|
|
135
135
|
for (const phase of phases) {
|
|
136
|
-
reportError({ component:
|
|
136
|
+
reportError({ component: 'X', phase, error: 'e', timestamp: 0 })
|
|
137
137
|
}
|
|
138
138
|
expect(seen).toEqual(phases)
|
|
139
139
|
|