@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.
@@ -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
+ })