@pyreon/core 0.24.4 → 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 (56) hide show
  1. package/lib/analysis/index.js.html +1 -1
  2. package/lib/index.js +53 -31
  3. package/package.json +2 -6
  4. package/src/compat-marker.ts +0 -79
  5. package/src/compat-shared.ts +0 -80
  6. package/src/component.ts +0 -98
  7. package/src/context.ts +0 -349
  8. package/src/defer.ts +0 -279
  9. package/src/dynamic.ts +0 -32
  10. package/src/env.d.ts +0 -6
  11. package/src/error-boundary.ts +0 -90
  12. package/src/for.ts +0 -51
  13. package/src/h.ts +0 -80
  14. package/src/index.ts +0 -80
  15. package/src/jsx-dev-runtime.ts +0 -2
  16. package/src/jsx-runtime.ts +0 -747
  17. package/src/lazy.ts +0 -25
  18. package/src/lifecycle.ts +0 -152
  19. package/src/manifest.ts +0 -579
  20. package/src/map-array.ts +0 -42
  21. package/src/portal.ts +0 -39
  22. package/src/props.ts +0 -269
  23. package/src/ref.ts +0 -32
  24. package/src/show.ts +0 -121
  25. package/src/style.ts +0 -102
  26. package/src/suspense.ts +0 -52
  27. package/src/telemetry.ts +0 -120
  28. package/src/tests/compat-marker.test.ts +0 -96
  29. package/src/tests/compat-shared.test.ts +0 -99
  30. package/src/tests/component.test.ts +0 -281
  31. package/src/tests/context.test.ts +0 -629
  32. package/src/tests/core.test.ts +0 -1290
  33. package/src/tests/cx.test.ts +0 -70
  34. package/src/tests/defer.test.ts +0 -359
  35. package/src/tests/dynamic.test.ts +0 -87
  36. package/src/tests/error-boundary.test.ts +0 -181
  37. package/src/tests/extract-props-overloads.types.test.ts +0 -135
  38. package/src/tests/for.test.ts +0 -117
  39. package/src/tests/h.test.ts +0 -221
  40. package/src/tests/jsx-compat.test.tsx +0 -86
  41. package/src/tests/lazy.test.ts +0 -100
  42. package/src/tests/lifecycle.test.ts +0 -350
  43. package/src/tests/manifest-snapshot.test.ts +0 -100
  44. package/src/tests/map-array.test.ts +0 -313
  45. package/src/tests/native-marker-error-boundary.test.ts +0 -12
  46. package/src/tests/portal.test.ts +0 -48
  47. package/src/tests/props-extended.test.ts +0 -157
  48. package/src/tests/props.test.ts +0 -250
  49. package/src/tests/reactive-context.test.ts +0 -69
  50. package/src/tests/reactive-props.test.ts +0 -157
  51. package/src/tests/ref.test.ts +0 -70
  52. package/src/tests/show.test.ts +0 -314
  53. package/src/tests/style.test.ts +0 -157
  54. package/src/tests/suspense.test.ts +0 -139
  55. package/src/tests/telemetry.test.ts +0 -297
  56. package/src/types.ts +0 -116
@@ -1,96 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { isNativeCompat, NATIVE_COMPAT_MARKER, nativeCompat } from '../compat-marker'
3
-
4
- describe('NATIVE_COMPAT_MARKER', () => {
5
- it('is the same registry symbol regardless of how it is referenced', () => {
6
- // Symbol.for(...) registry contract — every consumer that uses the same
7
- // string key (compat layers reading it, framework packages writing it)
8
- // gets the SAME symbol identity. Changing the string is a breaking
9
- // change to the marker contract.
10
- expect(NATIVE_COMPAT_MARKER).toBe(Symbol.for('pyreon:native-compat'))
11
- })
12
-
13
- it('is a `symbol`-typed value', () => {
14
- expect(typeof NATIVE_COMPAT_MARKER).toBe('symbol')
15
- })
16
- })
17
-
18
- describe('nativeCompat', () => {
19
- it('attaches the marker to a function and returns the same reference', () => {
20
- function RouterView() {
21
- return null
22
- }
23
- const marked = nativeCompat(RouterView)
24
- expect(marked).toBe(RouterView)
25
- expect((RouterView as unknown as Record<symbol, boolean>)[NATIVE_COMPAT_MARKER]).toBe(true)
26
- })
27
-
28
- it('is idempotent — applying twice yields the same property state', () => {
29
- const Component = () => null
30
- nativeCompat(Component)
31
- nativeCompat(Component)
32
- expect((Component as unknown as Record<symbol, boolean>)[NATIVE_COMPAT_MARKER]).toBe(true)
33
- })
34
-
35
- it('passes non-function values through unchanged', () => {
36
- // Defensive: callers may pipe variables of unknown shape (e.g. lazy
37
- // imports that resolve to objects, or null during HMR boundary
38
- // teardown). The helper must be safe regardless.
39
- expect(nativeCompat(null as unknown)).toBe(null)
40
- expect(nativeCompat(undefined as unknown)).toBe(undefined)
41
- const obj = { foo: 'bar' }
42
- expect(nativeCompat(obj)).toBe(obj)
43
- expect((obj as unknown as Record<symbol, boolean>)[NATIVE_COMPAT_MARKER]).toBeUndefined()
44
- })
45
-
46
- it('preserves the function signature for typed callers', () => {
47
- // The generic `T` flows through unchanged so framework component
48
- // exports keep their typed callable shape after wrapping.
49
- const Typed = (props: { name: string }): string => `hello ${props.name}`
50
- const marked: typeof Typed = nativeCompat(Typed)
51
- expect(marked({ name: 'world' })).toBe('hello world')
52
- })
53
- })
54
-
55
- describe('isNativeCompat', () => {
56
- it('returns true for a marked function', () => {
57
- const Comp = nativeCompat(() => null)
58
- expect(isNativeCompat(Comp)).toBe(true)
59
- })
60
-
61
- it('returns false for an unmarked function', () => {
62
- expect(isNativeCompat(() => null)).toBe(false)
63
- })
64
-
65
- it('returns false for non-function inputs', () => {
66
- expect(isNativeCompat(null)).toBe(false)
67
- expect(isNativeCompat(undefined)).toBe(false)
68
- expect(isNativeCompat('string')).toBe(false)
69
- expect(isNativeCompat(42)).toBe(false)
70
- expect(isNativeCompat({ [NATIVE_COMPAT_MARKER]: true })).toBe(false)
71
- })
72
-
73
- it('returns false when the marker is set to a non-true value', () => {
74
- // Defensive against accidental shape mismatch — only `=== true` qualifies.
75
- function Comp() {
76
- return null
77
- }
78
- ;(Comp as unknown as Record<symbol, unknown>)[NATIVE_COMPAT_MARKER] = 1
79
- expect(isNativeCompat(Comp)).toBe(false)
80
- ;(Comp as unknown as Record<symbol, unknown>)[NATIVE_COMPAT_MARKER] = 'yes'
81
- expect(isNativeCompat(Comp)).toBe(false)
82
- })
83
-
84
- it('reads the same registry symbol that nativeCompat writes', () => {
85
- // Cross-side contract: a function marked here is detectable by
86
- // someone who looked up the symbol via `Symbol.for('pyreon:native-compat')`
87
- // independently — without importing NATIVE_COMPAT_MARKER from this module.
88
- const Comp = nativeCompat(function Comp() {
89
- return null
90
- })
91
- const externallyDiscoveredSymbol = Symbol.for('pyreon:native-compat')
92
- expect(
93
- (Comp as unknown as Record<symbol, boolean>)[externallyDiscoveredSymbol],
94
- ).toBe(true)
95
- })
96
- })
@@ -1,99 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { mapCompatDomProps, shallowEqualProps } from '../compat-shared'
3
-
4
- describe('shallowEqualProps', () => {
5
- it('equal for same-key same-value objects', () => {
6
- expect(shallowEqualProps({ a: 1, b: 'x' }, { a: 1, b: 'x' })).toBe(true)
7
- })
8
-
9
- it('not equal when a value differs', () => {
10
- expect(shallowEqualProps({ a: 1 }, { a: 2 })).toBe(false)
11
- })
12
-
13
- it('not equal when key counts differ', () => {
14
- expect(shallowEqualProps({ a: 1 }, { a: 1, b: 2 })).toBe(false)
15
- })
16
-
17
- it('uses Object.is semantics (NaN equal, ±0 distinct)', () => {
18
- expect(shallowEqualProps({ n: NaN }, { n: NaN })).toBe(true)
19
- expect(shallowEqualProps({ z: 0 }, { z: -0 })).toBe(false)
20
- })
21
-
22
- it('empty objects are equal', () => {
23
- expect(shallowEqualProps({}, {})).toBe(true)
24
- })
25
- })
26
-
27
- describe('mapCompatDomProps', () => {
28
- it('no-op for component (non-string) type', () => {
29
- const Comp = () => null
30
- const p: Record<string, unknown> = { className: 'x', htmlFor: 'y' }
31
- mapCompatDomProps(p, Comp)
32
- expect(p).toEqual({ className: 'x', htmlFor: 'y' })
33
- })
34
-
35
- it('className → class, htmlFor → for', () => {
36
- const p: Record<string, unknown> = { className: 'btn', htmlFor: 'email' }
37
- mapCompatDomProps(p, 'label')
38
- expect(p).toEqual({ class: 'btn', for: 'email' })
39
- })
40
-
41
- it('onChange → onInput on input/textarea/select', () => {
42
- for (const tag of ['input', 'textarea', 'select']) {
43
- const fn = () => {}
44
- const p: Record<string, unknown> = { onChange: fn }
45
- mapCompatDomProps(p, tag)
46
- expect(p).toEqual({ onInput: fn })
47
- }
48
- })
49
-
50
- it('onChange does not clobber an explicit onInput', () => {
51
- const onChange = () => {}
52
- const onInput = () => {}
53
- const p: Record<string, unknown> = { onChange, onInput }
54
- mapCompatDomProps(p, 'input')
55
- expect(p).toEqual({ onInput })
56
- })
57
-
58
- it('onChange left alone on non-form elements', () => {
59
- const onChange = () => {}
60
- const p: Record<string, unknown> = { onChange }
61
- mapCompatDomProps(p, 'div')
62
- expect(p).toEqual({ onChange })
63
- })
64
-
65
- it('autoFocus → autofocus', () => {
66
- const p: Record<string, unknown> = { autoFocus: true }
67
- mapCompatDomProps(p, 'input')
68
- expect(p).toEqual({ autofocus: true })
69
- })
70
-
71
- it('defaultValue/defaultChecked → value/checked only when uncontrolled', () => {
72
- const a: Record<string, unknown> = { defaultValue: 'd', defaultChecked: true }
73
- mapCompatDomProps(a, 'input')
74
- expect(a).toEqual({ value: 'd', checked: true })
75
-
76
- const b: Record<string, unknown> = {
77
- defaultValue: 'd',
78
- value: 'controlled',
79
- defaultChecked: true,
80
- checked: false,
81
- }
82
- mapCompatDomProps(b, 'input')
83
- expect(b).toEqual({
84
- defaultValue: 'd',
85
- value: 'controlled',
86
- defaultChecked: true,
87
- checked: false,
88
- })
89
- })
90
-
91
- it('strips authoring-only props with no DOM equivalent', () => {
92
- const p: Record<string, unknown> = {
93
- suppressHydrationWarning: true,
94
- suppressContentEditableWarning: true,
95
- }
96
- mapCompatDomProps(p, 'div')
97
- expect(p).toEqual({})
98
- })
99
- })
@@ -1,281 +0,0 @@
1
- import {
2
- defineComponent,
3
- dispatchToErrorBoundary,
4
- popErrorBoundary,
5
- propagateError,
6
- pushErrorBoundary,
7
- runWithHooks,
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
-
13
- describe('defineComponent', () => {
14
- test('returns the exact same function (identity)', () => {
15
- const fn: ComponentFn = () => h('div', null)
16
- expect(defineComponent(fn)).toBe(fn)
17
- })
18
-
19
- test('preserves typed props', () => {
20
- const Comp = defineComponent<{ count: number }>((props) => {
21
- return h('span', null, String(props.count))
22
- })
23
- const node = Comp({ count: 10 })
24
- expect((node as VNode).type).toBe('span')
25
- })
26
- })
27
-
28
- describe('runWithHooks', () => {
29
- test('captures all lifecycle hook types', () => {
30
- const mountFn = () => undefined
31
- const unmountFn = () => {}
32
- const updateFn = () => {}
33
- const errorFn = () => true
34
-
35
- const Comp: ComponentFn = () => {
36
- onMount(mountFn)
37
- onUnmount(unmountFn)
38
- onUpdate(updateFn)
39
- onErrorCaptured(errorFn)
40
- return h('div', null)
41
- }
42
-
43
- const { vnode, hooks } = runWithHooks(Comp, {})
44
- expect(vnode).not.toBeNull()
45
- expect(hooks.mount).toContain(mountFn)
46
- expect(hooks.unmount).toContain(unmountFn)
47
- expect(hooks.update).toContain(updateFn)
48
- expect(hooks.error).toContain(errorFn)
49
- })
50
-
51
- test('returns null vnode for component returning null', () => {
52
- const { vnode } = runWithHooks(() => null, {})
53
- expect(vnode).toBeNull()
54
- })
55
-
56
- test('returns string vnode for component returning string', () => {
57
- const { vnode } = runWithHooks(() => 'hello', {})
58
- expect(vnode).toBe('hello')
59
- })
60
-
61
- test('clears hooks context after execution', () => {
62
- const Comp: ComponentFn = () => h('div', null)
63
- runWithHooks(Comp, {})
64
- // After runWithHooks, lifecycle hooks should be no-ops
65
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
66
- onMount(() => {})
67
- expect(warnSpy).toHaveBeenCalled()
68
- warnSpy.mockRestore()
69
- })
70
-
71
- test('clears hooks context even when component throws', () => {
72
- const Comp: ComponentFn = () => {
73
- throw new Error('boom')
74
- }
75
- expect(() => runWithHooks(Comp, {})).toThrow('boom')
76
- // Should still be cleared
77
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
78
- onMount(() => {})
79
- expect(warnSpy).toHaveBeenCalled()
80
- warnSpy.mockRestore()
81
- })
82
-
83
- test('passes props to component function', () => {
84
- let received: unknown = null
85
- runWithHooks(
86
- ((props: { msg: string }) => {
87
- received = props
88
- return null
89
- }) as ComponentFn,
90
- { msg: 'hello' },
91
- )
92
- expect(received).toEqual({ msg: 'hello' })
93
- })
94
-
95
- test('captures multiple hooks of same type', () => {
96
- const Comp: ComponentFn = () => {
97
- onMount(() => undefined)
98
- onMount(() => undefined)
99
- onUnmount(() => {})
100
- onUnmount(() => {})
101
- return null
102
- }
103
- const { hooks } = runWithHooks(Comp, {})
104
- expect(hooks.mount).toHaveLength(2)
105
- expect(hooks.unmount).toHaveLength(2)
106
- })
107
-
108
- test('null hooks when component registers none (lazy allocation)', () => {
109
- const { hooks } = runWithHooks(() => h('div', null), {})
110
- expect(hooks.mount).toBeNull()
111
- expect(hooks.unmount).toBeNull()
112
- expect(hooks.update).toBeNull()
113
- expect(hooks.error).toBeNull()
114
- })
115
- })
116
-
117
- describe('propagateError', () => {
118
- test('returns true when handler returns true', () => {
119
- const hooks: LifecycleHooks = {
120
- mount: [],
121
- unmount: [],
122
- update: [],
123
- error: [() => true],
124
- }
125
- expect(propagateError(new Error('test'), hooks)).toBe(true)
126
- })
127
-
128
- test('returns false when no handlers', () => {
129
- const hooks: LifecycleHooks = {
130
- mount: [],
131
- unmount: [],
132
- update: [],
133
- error: [],
134
- }
135
- expect(propagateError(new Error('test'), hooks)).toBe(false)
136
- })
137
-
138
- test('returns false when handler returns undefined', () => {
139
- const hooks: LifecycleHooks = {
140
- mount: [],
141
- unmount: [],
142
- update: [],
143
- error: [() => undefined],
144
- }
145
- expect(propagateError(new Error('test'), hooks)).toBe(false)
146
- })
147
-
148
- test('stops at first handler returning true', () => {
149
- let secondCalled = false
150
- const hooks: LifecycleHooks = {
151
- mount: [],
152
- unmount: [],
153
- update: [],
154
- error: [
155
- () => true,
156
- () => {
157
- secondCalled = true
158
- return true
159
- },
160
- ],
161
- }
162
- expect(propagateError('err', hooks)).toBe(true)
163
- expect(secondCalled).toBe(false)
164
- })
165
-
166
- test('continues to next handler when first returns undefined', () => {
167
- const calls: number[] = []
168
- const hooks: LifecycleHooks = {
169
- mount: [],
170
- unmount: [],
171
- update: [],
172
- error: [
173
- () => {
174
- calls.push(1)
175
- return undefined
176
- },
177
- () => {
178
- calls.push(2)
179
- return true
180
- },
181
- ],
182
- }
183
- expect(propagateError('err', hooks)).toBe(true)
184
- expect(calls).toEqual([1, 2])
185
- })
186
-
187
- test('passes the error to each handler', () => {
188
- const errors: unknown[] = []
189
- const hooks: LifecycleHooks = {
190
- mount: [],
191
- unmount: [],
192
- update: [],
193
- error: [
194
- (err) => {
195
- errors.push(err)
196
- return undefined
197
- },
198
- (err) => {
199
- errors.push(err)
200
- return true
201
- },
202
- ],
203
- }
204
- const testErr = new Error('propagated')
205
- propagateError(testErr, hooks)
206
- expect(errors).toEqual([testErr, testErr])
207
- })
208
- })
209
-
210
- describe('pushErrorBoundary / popErrorBoundary / dispatchToErrorBoundary', () => {
211
- afterEach(() => {
212
- // Clean up any leftover boundaries — pop until empty
213
- // dispatchToErrorBoundary returns false when stack is empty
214
- while (dispatchToErrorBoundary('cleanup-probe')) {
215
- popErrorBoundary()
216
- }
217
- })
218
-
219
- test('dispatches to the most recently pushed boundary', () => {
220
- let caught: unknown = null
221
- pushErrorBoundary((err) => {
222
- caught = err
223
- return true
224
- })
225
- expect(dispatchToErrorBoundary('test-error')).toBe(true)
226
- expect(caught).toBe('test-error')
227
- popErrorBoundary()
228
- })
229
-
230
- test('returns false when no boundary is registered', () => {
231
- expect(dispatchToErrorBoundary('no-boundary')).toBe(false)
232
- })
233
-
234
- test('nested boundaries — innermost catches first', () => {
235
- const caught: string[] = []
236
- pushErrorBoundary((err) => {
237
- caught.push(`outer: ${err}`)
238
- return true
239
- })
240
- pushErrorBoundary((err) => {
241
- caught.push(`inner: ${err}`)
242
- return true
243
- })
244
- dispatchToErrorBoundary('test')
245
- expect(caught).toEqual(['inner: test'])
246
- popErrorBoundary()
247
-
248
- // After popping inner, outer should catch
249
- dispatchToErrorBoundary('test2')
250
- expect(caught).toEqual(['inner: test', 'outer: test2'])
251
- popErrorBoundary()
252
- })
253
-
254
- test('boundary handler returning false does not propagate to outer', () => {
255
- // dispatchToErrorBoundary only calls the innermost handler
256
- let outerCalled = false
257
- pushErrorBoundary(() => {
258
- outerCalled = true
259
- return true
260
- })
261
- pushErrorBoundary(() => false)
262
- const result = dispatchToErrorBoundary('test')
263
- expect(result).toBe(false)
264
- expect(outerCalled).toBe(false) // outer not called — only innermost is checked
265
- popErrorBoundary()
266
- popErrorBoundary()
267
- })
268
-
269
- test('push and pop maintain stack correctly', () => {
270
- const results: boolean[] = []
271
- pushErrorBoundary(() => true)
272
- pushErrorBoundary(() => true)
273
- pushErrorBoundary(() => true)
274
- popErrorBoundary()
275
- popErrorBoundary()
276
- results.push(dispatchToErrorBoundary('x'))
277
- popErrorBoundary()
278
- results.push(dispatchToErrorBoundary('y'))
279
- expect(results).toEqual([true, false])
280
- })
281
- })