@pyreon/solid-compat 0.13.1 → 0.15.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/README.md +20 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/analysis/jsx-runtime.js.html +1 -1
- package/lib/index.js +459 -20
- package/lib/jsx-runtime.js +5 -1
- package/lib/types/index.d.ts +194 -6
- package/package.json +8 -4
- package/src/env.d.ts +6 -0
- package/src/index.ts +738 -25
- package/src/jsx-runtime.ts +14 -0
- package/src/solid-compat.browser.test.ts +32 -0
- package/src/tests/native-marker-bypass.test.tsx +70 -0
- package/src/tests/new-apis.test.ts +1539 -0
- package/src/tests/solid-compat.test.ts +366 -0
- package/lib/index.js.map +0 -1
- package/lib/jsx-runtime.js.map +0 -1
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/jsx-runtime.d.ts.map +0 -1
package/src/jsx-runtime.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
For,
|
|
33
33
|
Fragment,
|
|
34
34
|
h,
|
|
35
|
+
isNativeCompat,
|
|
35
36
|
Match,
|
|
36
37
|
onUnmount,
|
|
37
38
|
Show,
|
|
@@ -269,11 +270,24 @@ export function jsx(
|
|
|
269
270
|
const propsWithKey = (key != null ? { ...rest, key } : rest) as Props
|
|
270
271
|
|
|
271
272
|
if (typeof type === 'function') {
|
|
273
|
+
// Defense-in-depth: hardcoded set of Pyreon core control-flow primitives
|
|
274
|
+
// that are always native (kept even after the marker convergence — these
|
|
275
|
+
// are imported into solid-compat directly, so guarding their identity
|
|
276
|
+
// doesn't cost a property lookup and ensures the marker is never lost
|
|
277
|
+
// through any tree-shaking edge case).
|
|
272
278
|
if (_nativeComponents.has(type)) {
|
|
273
279
|
const componentProps = children !== undefined ? { ...propsWithKey, children } : propsWithKey
|
|
274
280
|
return h(type as ComponentFn, componentProps)
|
|
275
281
|
}
|
|
276
282
|
|
|
283
|
+
// Native Pyreon framework components (context Providers, RouterView, etc.)
|
|
284
|
+
// skip compat wrapping — see `@pyreon/core`'s `nativeCompat()` for the
|
|
285
|
+
// full contract.
|
|
286
|
+
if (isNativeCompat(type)) {
|
|
287
|
+
const componentProps = children !== undefined ? { ...propsWithKey, children } : propsWithKey
|
|
288
|
+
return h(type as ComponentFn, componentProps)
|
|
289
|
+
}
|
|
290
|
+
|
|
277
291
|
const wrapped = wrapCompatComponent(type)
|
|
278
292
|
const componentProps =
|
|
279
293
|
children !== undefined ? { ...propsWithKey, children } : { ...propsWithKey }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { h } from '@pyreon/core'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { mountInBrowser } from '@pyreon/test-utils/browser'
|
|
4
|
+
import { createSignal } from './index'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Real-browser smoke test for `@pyreon/solid-compat`.
|
|
8
|
+
*
|
|
9
|
+
* Per the test-environment-parity rule (`pyreon/require-browser-smoke-test`),
|
|
10
|
+
* every browser-categorized package must ship at least one
|
|
11
|
+
* `*.browser.test.*` file. This catches regressions that happy-dom unit
|
|
12
|
+
* tests can hide: importing the public API and exercising the SolidJS
|
|
13
|
+
* `createSignal` shim end-to-end in real Chromium.
|
|
14
|
+
*/
|
|
15
|
+
describe('@pyreon/solid-compat — browser smoke', () => {
|
|
16
|
+
it('createSignal returns [getter, setter] that round-trip', () => {
|
|
17
|
+
const [count, setCount] = createSignal(0)
|
|
18
|
+
expect(count()).toBe(0)
|
|
19
|
+
setCount(7)
|
|
20
|
+
expect(count()).toBe(7)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('mounts a static element in real browser', () => {
|
|
24
|
+
const [name] = createSignal('solid-compat')
|
|
25
|
+
const vnode = h('div', { id: 'solid-compat' }, name())
|
|
26
|
+
const { container, unmount } = mountInBrowser(vnode)
|
|
27
|
+
const el = container.querySelector('#solid-compat')!
|
|
28
|
+
expect(el.textContent).toBe('solid-compat')
|
|
29
|
+
unmount()
|
|
30
|
+
expect(document.getElementById('solid-compat')).toBeNull()
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ComponentFn } from '@pyreon/core'
|
|
2
|
+
import { createContext, h, nativeCompat, provide, useContext } from '@pyreon/core'
|
|
3
|
+
import { mount } from '@pyreon/runtime-dom'
|
|
4
|
+
import { describe, expect, it } from 'vitest'
|
|
5
|
+
import { jsx } from '../jsx-runtime'
|
|
6
|
+
|
|
7
|
+
// Per-compat unit-level regression test for the marker-bypass contract.
|
|
8
|
+
// See `react-compat/src/tests/native-marker-bypass.test.tsx` for the full
|
|
9
|
+
// rationale + bisect-verification notes.
|
|
10
|
+
//
|
|
11
|
+
// Solid-compat note: solid-compat's jsx() has TWO native-routing paths in
|
|
12
|
+
// sequence — first the hardcoded `_nativeComponents` Set as defense-in-depth
|
|
13
|
+
// (Show, For, Switch, Match, Suspense, ErrorBoundary), then the marker
|
|
14
|
+
// check. These tests use USER-defined NativeProvider/Consumer (NOT in the
|
|
15
|
+
// hardcoded set), so the bypass MUST come from the marker path
|
|
16
|
+
// (`isNativeCompat(type)`), proving the marker check fires correctly.
|
|
17
|
+
//
|
|
18
|
+
// Bisect-verified per file: removing the `if (isNativeCompat(type))` branch
|
|
19
|
+
// from solid-compat's jsx-runtime (while keeping the `_nativeComponents`
|
|
20
|
+
// set check) causes test #1 to fail with
|
|
21
|
+
// `expected [Function wrapped] to be [Function Native]`.
|
|
22
|
+
|
|
23
|
+
function container(): HTMLElement {
|
|
24
|
+
const el = document.createElement('div')
|
|
25
|
+
document.body.appendChild(el)
|
|
26
|
+
return el
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('solid-compat — nativeCompat() marker bypass', () => {
|
|
30
|
+
it('jsx() routes marked components through h() directly (no wrapper)', () => {
|
|
31
|
+
const Native = (props: { children?: unknown }) => h('div', null, props.children as never)
|
|
32
|
+
nativeCompat(Native)
|
|
33
|
+
|
|
34
|
+
const vnode = jsx(Native, {})
|
|
35
|
+
|
|
36
|
+
expect(vnode.type).toBe(Native)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('jsx() wraps UNMARKED components (control — bypass is selective)', () => {
|
|
40
|
+
const Unmarked = (props: { children?: unknown }) => h('div', null, props.children as never)
|
|
41
|
+
|
|
42
|
+
const vnode = jsx(Unmarked, {})
|
|
43
|
+
|
|
44
|
+
expect(vnode.type).not.toBe(Unmarked)
|
|
45
|
+
expect(typeof vnode.type).toBe('function')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('marked Provider mounts inside Pyreon setup frame — provide() reaches descendants', () => {
|
|
49
|
+
const Ctx = createContext<string>('default')
|
|
50
|
+
|
|
51
|
+
const Provider: ComponentFn = (props) => {
|
|
52
|
+
provide(Ctx, props.value as string)
|
|
53
|
+
return props.children as never
|
|
54
|
+
}
|
|
55
|
+
nativeCompat(Provider)
|
|
56
|
+
|
|
57
|
+
const Consumer: ComponentFn = () => {
|
|
58
|
+
const value = useContext(Ctx)
|
|
59
|
+
return h('span', { 'data-value': value }, value)
|
|
60
|
+
}
|
|
61
|
+
nativeCompat(Consumer)
|
|
62
|
+
|
|
63
|
+
const el = container()
|
|
64
|
+
mount(jsx(Provider, { value: 'native', children: jsx(Consumer, {}) }), el)
|
|
65
|
+
|
|
66
|
+
const span = el.querySelector('span')
|
|
67
|
+
expect(span?.getAttribute('data-value')).toBe('native')
|
|
68
|
+
expect(span?.textContent).toBe('native')
|
|
69
|
+
})
|
|
70
|
+
})
|