@pyreon/core 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 +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
package/src/style.ts
CHANGED
|
@@ -3,50 +3,50 @@
|
|
|
3
3
|
// CSS properties where numeric values are unitless (e.g. `opacity: 0.5`, `zIndex: 10`).
|
|
4
4
|
// All other numeric values get "px" appended automatically (e.g. `height: 100` → `"100px"`).
|
|
5
5
|
export const CSS_UNITLESS = new Set([
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
6
|
+
'animationIterationCount',
|
|
7
|
+
'aspectRatio',
|
|
8
|
+
'borderImageOutset',
|
|
9
|
+
'borderImageSlice',
|
|
10
|
+
'borderImageWidth',
|
|
11
|
+
'boxFlex',
|
|
12
|
+
'boxFlexGroup',
|
|
13
|
+
'boxOrdinalGroup',
|
|
14
|
+
'columnCount',
|
|
15
|
+
'columns',
|
|
16
|
+
'flex',
|
|
17
|
+
'flexGrow',
|
|
18
|
+
'flexPositive',
|
|
19
|
+
'flexShrink',
|
|
20
|
+
'flexNegative',
|
|
21
|
+
'flexOrder',
|
|
22
|
+
'gridArea',
|
|
23
|
+
'gridRow',
|
|
24
|
+
'gridRowEnd',
|
|
25
|
+
'gridRowSpan',
|
|
26
|
+
'gridRowStart',
|
|
27
|
+
'gridColumn',
|
|
28
|
+
'gridColumnEnd',
|
|
29
|
+
'gridColumnSpan',
|
|
30
|
+
'gridColumnStart',
|
|
31
|
+
'fontWeight',
|
|
32
|
+
'lineClamp',
|
|
33
|
+
'lineHeight',
|
|
34
|
+
'opacity',
|
|
35
|
+
'order',
|
|
36
|
+
'orphans',
|
|
37
|
+
'scale',
|
|
38
|
+
'tabSize',
|
|
39
|
+
'widows',
|
|
40
|
+
'zIndex',
|
|
41
|
+
'zoom',
|
|
42
|
+
'fillOpacity',
|
|
43
|
+
'floodOpacity',
|
|
44
|
+
'stopOpacity',
|
|
45
|
+
'strokeDasharray',
|
|
46
|
+
'strokeDashoffset',
|
|
47
|
+
'strokeMiterlimit',
|
|
48
|
+
'strokeOpacity',
|
|
49
|
+
'strokeWidth',
|
|
50
50
|
])
|
|
51
51
|
|
|
52
52
|
// ─── Class utilities ─────────────────────────────────────────────────────────
|
|
@@ -62,17 +62,17 @@ export type ClassValue =
|
|
|
62
62
|
| Record<string, boolean | null | undefined | (() => boolean)>
|
|
63
63
|
|
|
64
64
|
function cxObject(obj: Record<string, boolean | null | undefined | (() => boolean)>): string {
|
|
65
|
-
let result =
|
|
65
|
+
let result = ''
|
|
66
66
|
for (const key in obj) {
|
|
67
67
|
const v = obj[key]
|
|
68
|
-
const truthy = typeof v ===
|
|
68
|
+
const truthy = typeof v === 'function' ? v() : v
|
|
69
69
|
if (truthy) result = result ? `${result} ${key}` : key
|
|
70
70
|
}
|
|
71
71
|
return result
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function cxArray(arr: ClassValue[]): string {
|
|
75
|
-
let result =
|
|
75
|
+
let result = ''
|
|
76
76
|
for (const item of arr) {
|
|
77
77
|
const resolved = cx(item)
|
|
78
78
|
if (resolved) result = result ? `${result} ${resolved}` : resolved
|
|
@@ -82,9 +82,9 @@ function cxArray(arr: ClassValue[]): string {
|
|
|
82
82
|
|
|
83
83
|
/** Resolve a ClassValue into a flat class string (like clsx/cx). */
|
|
84
84
|
export function cx(value: ClassValue): string {
|
|
85
|
-
if (value == null || value === false || value === true) return
|
|
86
|
-
if (typeof value ===
|
|
87
|
-
if (typeof value ===
|
|
85
|
+
if (value == null || value === false || value === true) return ''
|
|
86
|
+
if (typeof value === 'string') return value
|
|
87
|
+
if (typeof value === 'number') return String(value)
|
|
88
88
|
if (Array.isArray(value)) return cxArray(value)
|
|
89
89
|
return cxObject(value)
|
|
90
90
|
}
|
|
@@ -98,5 +98,5 @@ export function toKebabCase(str: string): string {
|
|
|
98
98
|
|
|
99
99
|
/** Normalize a style value — appends "px" to numbers for non-unitless properties. */
|
|
100
100
|
export function normalizeStyleValue(key: string, value: unknown): string {
|
|
101
|
-
return typeof value ===
|
|
101
|
+
return typeof value === 'number' && !CSS_UNITLESS.has(key) ? `${value}px` : String(value)
|
|
102
102
|
}
|
package/src/suspense.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Fragment, h } from
|
|
2
|
-
import type { Props, VNode, VNodeChild } from
|
|
1
|
+
import { Fragment, h } from './h'
|
|
2
|
+
import type { Props, VNode, VNodeChild } from './types'
|
|
3
3
|
|
|
4
|
-
const __DEV__ = typeof process !==
|
|
4
|
+
const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
|
|
5
5
|
|
|
6
6
|
/** Internal marker attached to lazy()-wrapped components */
|
|
7
7
|
export type LazyComponent<P extends Props = Props> = ((props: P) => VNodeChild) & {
|
|
@@ -25,25 +25,25 @@ export function Suspense(props: { fallback: VNodeChild; children?: VNodeChild })
|
|
|
25
25
|
if (__DEV__ && props.fallback === undefined) {
|
|
26
26
|
// biome-ignore lint/suspicious/noConsole: dev-only warning
|
|
27
27
|
console.warn(
|
|
28
|
-
|
|
28
|
+
'[Pyreon] <Suspense> is missing a `fallback` prop. Provide fallback UI to show while loading.',
|
|
29
29
|
)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
return h(Fragment, null, () => {
|
|
33
33
|
const ch = props.children
|
|
34
|
-
const childNode = typeof ch ===
|
|
34
|
+
const childNode = typeof ch === 'function' ? ch() : ch
|
|
35
35
|
|
|
36
36
|
// Check if the child is a VNode whose type is a lazy component still loading
|
|
37
37
|
const isLoading =
|
|
38
38
|
childNode != null &&
|
|
39
|
-
typeof childNode ===
|
|
39
|
+
typeof childNode === 'object' &&
|
|
40
40
|
!Array.isArray(childNode) &&
|
|
41
|
-
typeof (childNode as VNode).type ===
|
|
41
|
+
typeof (childNode as VNode).type === 'function' &&
|
|
42
42
|
((childNode as VNode).type as unknown as LazyComponent).__loading?.()
|
|
43
43
|
|
|
44
44
|
if (isLoading) {
|
|
45
45
|
const fb = props.fallback
|
|
46
|
-
return typeof fb ===
|
|
46
|
+
return typeof fb === 'function' ? fb() : fb
|
|
47
47
|
}
|
|
48
48
|
return childNode
|
|
49
49
|
})
|
package/src/telemetry.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface ErrorContext {
|
|
|
16
16
|
/** Component function name, or "Anonymous" */
|
|
17
17
|
component: string
|
|
18
18
|
/** Lifecycle phase where the error occurred */
|
|
19
|
-
phase:
|
|
19
|
+
phase: 'setup' | 'render' | 'mount' | 'unmount' | 'effect'
|
|
20
20
|
/** The thrown value */
|
|
21
21
|
error: unknown
|
|
22
22
|
/** Unix timestamp (ms) */
|
|
@@ -5,28 +5,28 @@ import {
|
|
|
5
5
|
propagateError,
|
|
6
6
|
pushErrorBoundary,
|
|
7
7
|
runWithHooks,
|
|
8
|
-
} from
|
|
9
|
-
import { h } from
|
|
10
|
-
import { onErrorCaptured, onMount, onUnmount, onUpdate } from
|
|
11
|
-
import type { ComponentFn, LifecycleHooks, VNode } from
|
|
8
|
+
} from '../component'
|
|
9
|
+
import { h } from '../h'
|
|
10
|
+
import { onErrorCaptured, onMount, onUnmount, onUpdate } from '../lifecycle'
|
|
11
|
+
import type { ComponentFn, LifecycleHooks, VNode } from '../types'
|
|
12
12
|
|
|
13
|
-
describe(
|
|
14
|
-
test(
|
|
15
|
-
const fn: ComponentFn = () => h(
|
|
13
|
+
describe('defineComponent', () => {
|
|
14
|
+
test('returns the exact same function (identity)', () => {
|
|
15
|
+
const fn: ComponentFn = () => h('div', null)
|
|
16
16
|
expect(defineComponent(fn)).toBe(fn)
|
|
17
17
|
})
|
|
18
18
|
|
|
19
|
-
test(
|
|
19
|
+
test('preserves typed props', () => {
|
|
20
20
|
const Comp = defineComponent<{ count: number }>((props) => {
|
|
21
|
-
return h(
|
|
21
|
+
return h('span', null, String(props.count))
|
|
22
22
|
})
|
|
23
23
|
const node = Comp({ count: 10 })
|
|
24
|
-
expect((node as VNode).type).toBe(
|
|
24
|
+
expect((node as VNode).type).toBe('span')
|
|
25
25
|
})
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
describe(
|
|
29
|
-
test(
|
|
28
|
+
describe('runWithHooks', () => {
|
|
29
|
+
test('captures all lifecycle hook types', () => {
|
|
30
30
|
const mountFn = () => undefined
|
|
31
31
|
const unmountFn = () => {}
|
|
32
32
|
const updateFn = () => {}
|
|
@@ -37,7 +37,7 @@ describe("runWithHooks", () => {
|
|
|
37
37
|
onUnmount(unmountFn)
|
|
38
38
|
onUpdate(updateFn)
|
|
39
39
|
onErrorCaptured(errorFn)
|
|
40
|
-
return h(
|
|
40
|
+
return h('div', null)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const { vnode, hooks } = runWithHooks(Comp, {})
|
|
@@ -48,51 +48,51 @@ describe("runWithHooks", () => {
|
|
|
48
48
|
expect(hooks.error).toContain(errorFn)
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
-
test(
|
|
51
|
+
test('returns null vnode for component returning null', () => {
|
|
52
52
|
const { vnode } = runWithHooks(() => null, {})
|
|
53
53
|
expect(vnode).toBeNull()
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
test(
|
|
57
|
-
const { vnode } = runWithHooks(() =>
|
|
58
|
-
expect(vnode).toBe(
|
|
56
|
+
test('returns string vnode for component returning string', () => {
|
|
57
|
+
const { vnode } = runWithHooks(() => 'hello', {})
|
|
58
|
+
expect(vnode).toBe('hello')
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
-
test(
|
|
62
|
-
const Comp: ComponentFn = () => h(
|
|
61
|
+
test('clears hooks context after execution', () => {
|
|
62
|
+
const Comp: ComponentFn = () => h('div', null)
|
|
63
63
|
runWithHooks(Comp, {})
|
|
64
64
|
// After runWithHooks, lifecycle hooks should be no-ops
|
|
65
|
-
const warnSpy = vi.spyOn(console,
|
|
65
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
66
66
|
onMount(() => {})
|
|
67
67
|
expect(warnSpy).toHaveBeenCalled()
|
|
68
68
|
warnSpy.mockRestore()
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
-
test(
|
|
71
|
+
test('clears hooks context even when component throws', () => {
|
|
72
72
|
const Comp: ComponentFn = () => {
|
|
73
|
-
throw new Error(
|
|
73
|
+
throw new Error('boom')
|
|
74
74
|
}
|
|
75
|
-
expect(() => runWithHooks(Comp, {})).toThrow(
|
|
75
|
+
expect(() => runWithHooks(Comp, {})).toThrow('boom')
|
|
76
76
|
// Should still be cleared
|
|
77
|
-
const warnSpy = vi.spyOn(console,
|
|
77
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
78
78
|
onMount(() => {})
|
|
79
79
|
expect(warnSpy).toHaveBeenCalled()
|
|
80
80
|
warnSpy.mockRestore()
|
|
81
81
|
})
|
|
82
82
|
|
|
83
|
-
test(
|
|
83
|
+
test('passes props to component function', () => {
|
|
84
84
|
let received: unknown = null
|
|
85
85
|
runWithHooks(
|
|
86
86
|
((props: { msg: string }) => {
|
|
87
87
|
received = props
|
|
88
88
|
return null
|
|
89
89
|
}) as ComponentFn,
|
|
90
|
-
{ msg:
|
|
90
|
+
{ msg: 'hello' },
|
|
91
91
|
)
|
|
92
|
-
expect(received).toEqual({ msg:
|
|
92
|
+
expect(received).toEqual({ msg: 'hello' })
|
|
93
93
|
})
|
|
94
94
|
|
|
95
|
-
test(
|
|
95
|
+
test('captures multiple hooks of same type', () => {
|
|
96
96
|
const Comp: ComponentFn = () => {
|
|
97
97
|
onMount(() => undefined)
|
|
98
98
|
onMount(() => undefined)
|
|
@@ -105,8 +105,8 @@ describe("runWithHooks", () => {
|
|
|
105
105
|
expect(hooks.unmount).toHaveLength(2)
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
-
test(
|
|
109
|
-
const { hooks } = runWithHooks(() => h(
|
|
108
|
+
test('empty hooks when component registers none', () => {
|
|
109
|
+
const { hooks } = runWithHooks(() => h('div', null), {})
|
|
110
110
|
expect(hooks.mount).toHaveLength(0)
|
|
111
111
|
expect(hooks.unmount).toHaveLength(0)
|
|
112
112
|
expect(hooks.update).toHaveLength(0)
|
|
@@ -114,38 +114,38 @@ describe("runWithHooks", () => {
|
|
|
114
114
|
})
|
|
115
115
|
})
|
|
116
116
|
|
|
117
|
-
describe(
|
|
118
|
-
test(
|
|
117
|
+
describe('propagateError', () => {
|
|
118
|
+
test('returns true when handler returns true', () => {
|
|
119
119
|
const hooks: LifecycleHooks = {
|
|
120
120
|
mount: [],
|
|
121
121
|
unmount: [],
|
|
122
122
|
update: [],
|
|
123
123
|
error: [() => true],
|
|
124
124
|
}
|
|
125
|
-
expect(propagateError(new Error(
|
|
125
|
+
expect(propagateError(new Error('test'), hooks)).toBe(true)
|
|
126
126
|
})
|
|
127
127
|
|
|
128
|
-
test(
|
|
128
|
+
test('returns false when no handlers', () => {
|
|
129
129
|
const hooks: LifecycleHooks = {
|
|
130
130
|
mount: [],
|
|
131
131
|
unmount: [],
|
|
132
132
|
update: [],
|
|
133
133
|
error: [],
|
|
134
134
|
}
|
|
135
|
-
expect(propagateError(new Error(
|
|
135
|
+
expect(propagateError(new Error('test'), hooks)).toBe(false)
|
|
136
136
|
})
|
|
137
137
|
|
|
138
|
-
test(
|
|
138
|
+
test('returns false when handler returns undefined', () => {
|
|
139
139
|
const hooks: LifecycleHooks = {
|
|
140
140
|
mount: [],
|
|
141
141
|
unmount: [],
|
|
142
142
|
update: [],
|
|
143
143
|
error: [() => undefined],
|
|
144
144
|
}
|
|
145
|
-
expect(propagateError(new Error(
|
|
145
|
+
expect(propagateError(new Error('test'), hooks)).toBe(false)
|
|
146
146
|
})
|
|
147
147
|
|
|
148
|
-
test(
|
|
148
|
+
test('stops at first handler returning true', () => {
|
|
149
149
|
let secondCalled = false
|
|
150
150
|
const hooks: LifecycleHooks = {
|
|
151
151
|
mount: [],
|
|
@@ -159,11 +159,11 @@ describe("propagateError", () => {
|
|
|
159
159
|
},
|
|
160
160
|
],
|
|
161
161
|
}
|
|
162
|
-
expect(propagateError(
|
|
162
|
+
expect(propagateError('err', hooks)).toBe(true)
|
|
163
163
|
expect(secondCalled).toBe(false)
|
|
164
164
|
})
|
|
165
165
|
|
|
166
|
-
test(
|
|
166
|
+
test('continues to next handler when first returns undefined', () => {
|
|
167
167
|
const calls: number[] = []
|
|
168
168
|
const hooks: LifecycleHooks = {
|
|
169
169
|
mount: [],
|
|
@@ -180,11 +180,11 @@ describe("propagateError", () => {
|
|
|
180
180
|
},
|
|
181
181
|
],
|
|
182
182
|
}
|
|
183
|
-
expect(propagateError(
|
|
183
|
+
expect(propagateError('err', hooks)).toBe(true)
|
|
184
184
|
expect(calls).toEqual([1, 2])
|
|
185
185
|
})
|
|
186
186
|
|
|
187
|
-
test(
|
|
187
|
+
test('passes the error to each handler', () => {
|
|
188
188
|
const errors: unknown[] = []
|
|
189
189
|
const hooks: LifecycleHooks = {
|
|
190
190
|
mount: [],
|
|
@@ -201,37 +201,37 @@ describe("propagateError", () => {
|
|
|
201
201
|
},
|
|
202
202
|
],
|
|
203
203
|
}
|
|
204
|
-
const testErr = new Error(
|
|
204
|
+
const testErr = new Error('propagated')
|
|
205
205
|
propagateError(testErr, hooks)
|
|
206
206
|
expect(errors).toEqual([testErr, testErr])
|
|
207
207
|
})
|
|
208
208
|
})
|
|
209
209
|
|
|
210
|
-
describe(
|
|
210
|
+
describe('pushErrorBoundary / popErrorBoundary / dispatchToErrorBoundary', () => {
|
|
211
211
|
afterEach(() => {
|
|
212
212
|
// Clean up any leftover boundaries — pop until empty
|
|
213
213
|
// dispatchToErrorBoundary returns false when stack is empty
|
|
214
|
-
while (dispatchToErrorBoundary(
|
|
214
|
+
while (dispatchToErrorBoundary('cleanup-probe')) {
|
|
215
215
|
popErrorBoundary()
|
|
216
216
|
}
|
|
217
217
|
})
|
|
218
218
|
|
|
219
|
-
test(
|
|
219
|
+
test('dispatches to the most recently pushed boundary', () => {
|
|
220
220
|
let caught: unknown = null
|
|
221
221
|
pushErrorBoundary((err) => {
|
|
222
222
|
caught = err
|
|
223
223
|
return true
|
|
224
224
|
})
|
|
225
|
-
expect(dispatchToErrorBoundary(
|
|
226
|
-
expect(caught).toBe(
|
|
225
|
+
expect(dispatchToErrorBoundary('test-error')).toBe(true)
|
|
226
|
+
expect(caught).toBe('test-error')
|
|
227
227
|
popErrorBoundary()
|
|
228
228
|
})
|
|
229
229
|
|
|
230
|
-
test(
|
|
231
|
-
expect(dispatchToErrorBoundary(
|
|
230
|
+
test('returns false when no boundary is registered', () => {
|
|
231
|
+
expect(dispatchToErrorBoundary('no-boundary')).toBe(false)
|
|
232
232
|
})
|
|
233
233
|
|
|
234
|
-
test(
|
|
234
|
+
test('nested boundaries — innermost catches first', () => {
|
|
235
235
|
const caught: string[] = []
|
|
236
236
|
pushErrorBoundary((err) => {
|
|
237
237
|
caught.push(`outer: ${err}`)
|
|
@@ -241,17 +241,17 @@ describe("pushErrorBoundary / popErrorBoundary / dispatchToErrorBoundary", () =>
|
|
|
241
241
|
caught.push(`inner: ${err}`)
|
|
242
242
|
return true
|
|
243
243
|
})
|
|
244
|
-
dispatchToErrorBoundary(
|
|
245
|
-
expect(caught).toEqual([
|
|
244
|
+
dispatchToErrorBoundary('test')
|
|
245
|
+
expect(caught).toEqual(['inner: test'])
|
|
246
246
|
popErrorBoundary()
|
|
247
247
|
|
|
248
248
|
// After popping inner, outer should catch
|
|
249
|
-
dispatchToErrorBoundary(
|
|
250
|
-
expect(caught).toEqual([
|
|
249
|
+
dispatchToErrorBoundary('test2')
|
|
250
|
+
expect(caught).toEqual(['inner: test', 'outer: test2'])
|
|
251
251
|
popErrorBoundary()
|
|
252
252
|
})
|
|
253
253
|
|
|
254
|
-
test(
|
|
254
|
+
test('boundary handler returning false does not propagate to outer', () => {
|
|
255
255
|
// dispatchToErrorBoundary only calls the innermost handler
|
|
256
256
|
let outerCalled = false
|
|
257
257
|
pushErrorBoundary(() => {
|
|
@@ -259,23 +259,23 @@ describe("pushErrorBoundary / popErrorBoundary / dispatchToErrorBoundary", () =>
|
|
|
259
259
|
return true
|
|
260
260
|
})
|
|
261
261
|
pushErrorBoundary(() => false)
|
|
262
|
-
const result = dispatchToErrorBoundary(
|
|
262
|
+
const result = dispatchToErrorBoundary('test')
|
|
263
263
|
expect(result).toBe(false)
|
|
264
264
|
expect(outerCalled).toBe(false) // outer not called — only innermost is checked
|
|
265
265
|
popErrorBoundary()
|
|
266
266
|
popErrorBoundary()
|
|
267
267
|
})
|
|
268
268
|
|
|
269
|
-
test(
|
|
269
|
+
test('push and pop maintain stack correctly', () => {
|
|
270
270
|
const results: boolean[] = []
|
|
271
271
|
pushErrorBoundary(() => true)
|
|
272
272
|
pushErrorBoundary(() => true)
|
|
273
273
|
pushErrorBoundary(() => true)
|
|
274
274
|
popErrorBoundary()
|
|
275
275
|
popErrorBoundary()
|
|
276
|
-
results.push(dispatchToErrorBoundary(
|
|
276
|
+
results.push(dispatchToErrorBoundary('x'))
|
|
277
277
|
popErrorBoundary()
|
|
278
|
-
results.push(dispatchToErrorBoundary(
|
|
278
|
+
results.push(dispatchToErrorBoundary('y'))
|
|
279
279
|
expect(results).toEqual([true, false])
|
|
280
280
|
})
|
|
281
281
|
})
|