@pyreon/elements 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.
- package/package.json +10 -12
- package/src/Element/component.tsx +0 -315
- package/src/Element/constants.ts +0 -96
- package/src/Element/index.ts +0 -6
- package/src/Element/types.ts +0 -168
- package/src/Element/utils.ts +0 -15
- package/src/List/component.tsx +0 -105
- package/src/List/index.ts +0 -5
- package/src/Overlay/component.tsx +0 -140
- package/src/Overlay/context.tsx +0 -36
- package/src/Overlay/index.ts +0 -7
- package/src/Overlay/positioning.ts +0 -191
- package/src/Overlay/useOverlay.tsx +0 -461
- package/src/Portal/component.tsx +0 -54
- package/src/Portal/index.ts +0 -5
- package/src/Text/component.tsx +0 -67
- package/src/Text/index.ts +0 -5
- package/src/Text/styled.ts +0 -30
- package/src/Util/component.tsx +0 -43
- package/src/Util/index.ts +0 -5
- package/src/__tests__/Content.test.tsx +0 -123
- package/src/__tests__/Element-slot-reactivity.browser.test.tsx +0 -152
- package/src/__tests__/Element.test.ts +0 -819
- package/src/__tests__/Iterator.test.ts +0 -492
- package/src/__tests__/Iterator.types.test.ts +0 -237
- package/src/__tests__/List.test.ts +0 -199
- package/src/__tests__/Overlay.test.ts +0 -492
- package/src/__tests__/Portal.test.ts +0 -156
- package/src/__tests__/Text.test.ts +0 -274
- package/src/__tests__/Util.test.ts +0 -63
- package/src/__tests__/Wrapper-innerhtml.test.tsx +0 -178
- package/src/__tests__/Wrapper.test.tsx +0 -196
- package/src/__tests__/elements.browser.test.tsx +0 -132
- package/src/__tests__/equalBeforeAfter.test.ts +0 -122
- package/src/__tests__/helpers.test.ts +0 -65
- package/src/__tests__/integration.test.tsx +0 -118
- package/src/__tests__/internElementBundle.test.ts +0 -102
- package/src/__tests__/iterator-function-children.test.tsx +0 -120
- package/src/__tests__/native-markers.test.ts +0 -13
- package/src/__tests__/overlayContext.test.tsx +0 -78
- package/src/__tests__/perf-stress.browser.test.tsx +0 -119
- package/src/__tests__/positioning.test.ts +0 -90
- package/src/__tests__/responsiveProps.test.ts +0 -328
- package/src/__tests__/slot-component-reference.test.tsx +0 -157
- package/src/__tests__/useOverlay.test.ts +0 -1336
- package/src/__tests__/utils.test.ts +0 -69
- package/src/__tests__/wrapper-block-cascade.test.ts +0 -121
- package/src/constants.ts +0 -1
- package/src/env.d.ts +0 -6
- package/src/helpers/Content/component.tsx +0 -75
- package/src/helpers/Content/index.ts +0 -3
- package/src/helpers/Content/styled.ts +0 -105
- package/src/helpers/Content/types.ts +0 -49
- package/src/helpers/Iterator/component.tsx +0 -316
- package/src/helpers/Iterator/index.ts +0 -30
- package/src/helpers/Iterator/types.ts +0 -138
- package/src/helpers/Wrapper/component.tsx +0 -180
- package/src/helpers/Wrapper/constants.ts +0 -10
- package/src/helpers/Wrapper/index.ts +0 -3
- package/src/helpers/Wrapper/styled.ts +0 -64
- package/src/helpers/Wrapper/types.ts +0 -56
- package/src/helpers/Wrapper/utils.ts +0 -7
- package/src/helpers/index.ts +0 -4
- package/src/helpers/internElementBundle.ts +0 -37
- package/src/helpers/isPyreonComponent.ts +0 -46
- package/src/index.ts +0 -42
- package/src/manifest.ts +0 -190
- package/src/tests/manifest-snapshot.test.ts +0 -45
- package/src/types.ts +0 -112
- package/src/utils.ts +0 -5
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import type { VNode } from '@pyreon/core'
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
-
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// Mocks
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
vi.mock('~/utils', () => ({
|
|
8
|
-
IS_DEVELOPMENT: true,
|
|
9
|
-
}))
|
|
10
|
-
|
|
11
|
-
import Wrapper from '../helpers/Wrapper/component'
|
|
12
|
-
import Styled from '../helpers/Wrapper/styled'
|
|
13
|
-
|
|
14
|
-
const asVNode = (v: unknown) => v as VNode
|
|
15
|
-
|
|
16
|
-
describe('Wrapper component', () => {
|
|
17
|
-
describe('normal element (no flex fix needed)', () => {
|
|
18
|
-
it('returns a VNode whose type is the Styled component', () => {
|
|
19
|
-
const result = asVNode(Wrapper({ tag: 'div' }))
|
|
20
|
-
expect(result.type).toBe(Styled)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('passes normalElement as $element prop with block, direction, alignX, alignY, equalCols, extraStyles', () => {
|
|
24
|
-
const result = asVNode(
|
|
25
|
-
Wrapper({
|
|
26
|
-
tag: 'div',
|
|
27
|
-
block: true,
|
|
28
|
-
direction: 'inline',
|
|
29
|
-
alignX: 'center',
|
|
30
|
-
alignY: 'top',
|
|
31
|
-
equalCols: false,
|
|
32
|
-
extendCss: 'color: red;',
|
|
33
|
-
}),
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
expect(result.props.$element).toEqual({
|
|
37
|
-
block: true,
|
|
38
|
-
direction: 'inline',
|
|
39
|
-
alignX: 'center',
|
|
40
|
-
alignY: 'top',
|
|
41
|
-
equalCols: false,
|
|
42
|
-
extraStyles: 'color: red;',
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it("passes tag as the 'as' prop", () => {
|
|
47
|
-
const result = asVNode(Wrapper({ tag: 'div' }))
|
|
48
|
-
expect(result.props.as).toBe('div')
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('passes ref through', () => {
|
|
52
|
-
const ref = () => {}
|
|
53
|
-
const result = asVNode(Wrapper({ tag: 'div', ref }))
|
|
54
|
-
expect(result.props.ref).toBe(ref)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('passes children through', () => {
|
|
58
|
-
const result = asVNode(Wrapper({ tag: 'div', children: 'child content' }))
|
|
59
|
-
expect(result.props.children).toBe('child content')
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('adds data-pyr-element in development mode', () => {
|
|
63
|
-
const result = asVNode(Wrapper({ tag: 'div' }))
|
|
64
|
-
expect(result.props['data-pyr-element']).toBe('Element')
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('spreads extra props', () => {
|
|
68
|
-
const result = asVNode(Wrapper({ tag: 'section', id: 'wrapper', role: 'main' } as any))
|
|
69
|
-
expect(result.props.id).toBe('wrapper')
|
|
70
|
-
expect(result.props.role).toBe('main')
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
describe('flex-fix path (button/fieldset/legend)', () => {
|
|
75
|
-
it('renders parent Styled with parentFixElement for button', () => {
|
|
76
|
-
const result = asVNode(Wrapper({ tag: 'button', children: 'Click me' }))
|
|
77
|
-
expect(result.type).toBe(Styled)
|
|
78
|
-
expect((result.props.$element as any).parentFix).toBe(true)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('parent Styled receives parentFixElement props (block + extraStyles only)', () => {
|
|
82
|
-
const result = asVNode(
|
|
83
|
-
Wrapper({
|
|
84
|
-
tag: 'button',
|
|
85
|
-
block: true,
|
|
86
|
-
extendCss: 'border: none;',
|
|
87
|
-
direction: 'inline',
|
|
88
|
-
alignX: 'center',
|
|
89
|
-
alignY: 'center',
|
|
90
|
-
}),
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
expect(result.props.$element).toEqual({
|
|
94
|
-
parentFix: true,
|
|
95
|
-
block: true,
|
|
96
|
-
extraStyles: 'border: none;',
|
|
97
|
-
})
|
|
98
|
-
expect(result.props.as).toBe('button')
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('child Styled receives childFixElement props (direction, alignX, alignY, equalCols)', () => {
|
|
102
|
-
const result = asVNode(
|
|
103
|
-
Wrapper({
|
|
104
|
-
tag: 'fieldset',
|
|
105
|
-
direction: 'rows',
|
|
106
|
-
alignX: 'left',
|
|
107
|
-
alignY: 'bottom',
|
|
108
|
-
equalCols: true,
|
|
109
|
-
}),
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
// The child is the first (only) child of the parent
|
|
113
|
-
const child = asVNode(result.props.children)
|
|
114
|
-
expect(child.type).toBe(Styled)
|
|
115
|
-
expect(child.props.$childFix).toBe(true)
|
|
116
|
-
expect(child.props.$element).toEqual({
|
|
117
|
-
childFix: true,
|
|
118
|
-
direction: 'rows',
|
|
119
|
-
alignX: 'left',
|
|
120
|
-
alignY: 'bottom',
|
|
121
|
-
equalCols: true,
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it("uses 'div' as inner tag by default", () => {
|
|
126
|
-
const result = asVNode(Wrapper({ tag: 'button' }))
|
|
127
|
-
const child = asVNode(result.props.children)
|
|
128
|
-
expect(child.props.as).toBe('div')
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it("uses 'span' as inner tag when isInline is true", () => {
|
|
132
|
-
const result = asVNode(Wrapper({ tag: 'button', isInline: true }))
|
|
133
|
-
const child = asVNode(result.props.children)
|
|
134
|
-
expect(child.props.as).toBe('span')
|
|
135
|
-
})
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
describe('dangerouslySetInnerHTML skips fix', () => {
|
|
139
|
-
it('renders single Styled without parentFix for button when dangerouslySetInnerHTML is set', () => {
|
|
140
|
-
const result = asVNode(
|
|
141
|
-
Wrapper({
|
|
142
|
-
tag: 'button',
|
|
143
|
-
dangerouslySetInnerHTML: { __html: '<b>Bold</b>' },
|
|
144
|
-
} as any),
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
// Should be the normal (non-fix) path
|
|
148
|
-
expect(result.type).toBe(Styled)
|
|
149
|
-
expect((result.props.$element as any).parentFix).toBeUndefined()
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
// Void HTML elements (hr, input, img, br, …) cannot have children. Element
|
|
154
|
-
// already skips passing children to Wrapper for void tags, but the JSX
|
|
155
|
-
// `{own.children}` slot still serialized `undefined` into the vnode and
|
|
156
|
-
// tripped runtime-dom's void-element warning. Wrapper now drops the slot
|
|
157
|
-
// entirely for void tags.
|
|
158
|
-
describe('void HTML elements drop the children slot', () => {
|
|
159
|
-
const VOID_TAGS = ['hr', 'input', 'img', 'br', 'area', 'base', 'col', 'embed', 'link', 'source', 'track', 'wbr'] as const
|
|
160
|
-
|
|
161
|
-
for (const tag of VOID_TAGS) {
|
|
162
|
-
it(`omits children for <${tag}>`, () => {
|
|
163
|
-
// Cast to any — the Wrapper Props.tag type narrows out void
|
|
164
|
-
// elements, but the runtime guard still has to cover the case
|
|
165
|
-
// where a void tag reaches Wrapper (e.g. via rocketstyle attrs
|
|
166
|
-
// composing `tag: 'hr'` from a callback whose return type is wider).
|
|
167
|
-
const result = asVNode(Wrapper({ tag } as any))
|
|
168
|
-
expect(result.type).toBe(Styled)
|
|
169
|
-
expect(result.props.children).toBeUndefined()
|
|
170
|
-
expect(result.children).toEqual([])
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
it('still renders children for non-void tags', () => {
|
|
175
|
-
const result = asVNode(Wrapper({ tag: 'div', children: 'kept' }))
|
|
176
|
-
expect(result.props.children).toBe('kept')
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('omits children even when caller accidentally passes them to a void tag', () => {
|
|
180
|
-
const result = asVNode(Wrapper({ tag: 'hr', children: 'should-not-leak' }))
|
|
181
|
-
expect(result.props.children).toBeUndefined()
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('dangerouslySetInnerHTML on a normally-void tag bypasses the void path', () => {
|
|
185
|
-
const result = asVNode(
|
|
186
|
-
Wrapper({
|
|
187
|
-
tag: 'hr',
|
|
188
|
-
dangerouslySetInnerHTML: { __html: '<b>x</b>' },
|
|
189
|
-
} as any),
|
|
190
|
-
)
|
|
191
|
-
// dangerouslySetInnerHTML opts out of the void-element guard
|
|
192
|
-
// because the tag becomes a custom element / shadow host case.
|
|
193
|
-
expect(result.type).toBe(Styled)
|
|
194
|
-
})
|
|
195
|
-
})
|
|
196
|
-
})
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource @pyreon/core */
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { signal } from '@pyreon/reactivity'
|
|
4
|
-
import { flush, mountInBrowser } from '@pyreon/test-utils/browser'
|
|
5
|
-
import { Element } from '../Element'
|
|
6
|
-
import { Portal } from '../Portal'
|
|
7
|
-
import { Text } from '../Text'
|
|
8
|
-
|
|
9
|
-
describe('@pyreon/elements browser smoke', () => {
|
|
10
|
-
it('Element mounts into real DOM with structural rendering', () => {
|
|
11
|
-
const { container, unmount } = mountInBrowser(
|
|
12
|
-
<Element tag="div" data-id="el"><span>hello</span></Element>,
|
|
13
|
-
)
|
|
14
|
-
const el = container.querySelector('[data-id="el"]')
|
|
15
|
-
expect(el?.tagName.toLowerCase()).toBe('div')
|
|
16
|
-
expect(el?.querySelector('span')?.textContent).toBe('hello')
|
|
17
|
-
unmount()
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('Element forwards a reactive text child to the DOM', async () => {
|
|
21
|
-
const label = signal('hello')
|
|
22
|
-
const { container, unmount } = mountInBrowser(
|
|
23
|
-
<Element tag="div">
|
|
24
|
-
<span data-id="lbl">{() => label()}</span>
|
|
25
|
-
</Element>,
|
|
26
|
-
)
|
|
27
|
-
const lbl = container.querySelector('[data-id="lbl"]')
|
|
28
|
-
expect(lbl?.textContent).toBe('hello')
|
|
29
|
-
label.set('world')
|
|
30
|
-
await flush()
|
|
31
|
-
expect(lbl?.textContent).toBe('world')
|
|
32
|
-
unmount()
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('Text renders as inline element', () => {
|
|
36
|
-
const { container, unmount } = mountInBrowser(<Text tag="span" data-id="t">hi</Text>)
|
|
37
|
-
const el = container.querySelector('[data-id="t"]')
|
|
38
|
-
expect(el?.tagName.toLowerCase()).toBe('span')
|
|
39
|
-
expect(el?.textContent).toBe('hi')
|
|
40
|
-
unmount()
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('Portal projects children to document.body by default', () => {
|
|
44
|
-
const { unmount } = mountInBrowser(
|
|
45
|
-
<Portal>
|
|
46
|
-
<div data-portal-id="p">portal-content</div>
|
|
47
|
-
</Portal>,
|
|
48
|
-
)
|
|
49
|
-
const projected = document.querySelector('[data-portal-id="p"]')
|
|
50
|
-
expect(projected?.textContent).toBe('portal-content')
|
|
51
|
-
unmount()
|
|
52
|
-
expect(document.querySelector('[data-portal-id="p"]')).toBeNull()
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('runs in a real browser — Vitest defines `process.env.NODE_ENV !== "production"`', () => {
|
|
56
|
-
// Sanity check the test env: dev gates use bundler-agnostic
|
|
57
|
-
// `process.env.NODE_ENV !== 'production'`. Every modern bundler
|
|
58
|
-
// (incl. Vitest's Vite) replaces this at build time. In a real-browser
|
|
59
|
-
// test run the literal lands as `"development" !== "production"` →
|
|
60
|
-
// `true`, so dev warnings fire as expected.
|
|
61
|
-
expect(process.env.NODE_ENV).not.toBe('production')
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
// Void HTML elements via Element must not trip runtime-dom's
|
|
65
|
-
// "<X> is a void element and cannot have children" warning.
|
|
66
|
-
// Wrapper used to leak an `undefined` child into the vnode for void tags.
|
|
67
|
-
describe('void HTML element tags', () => {
|
|
68
|
-
const voidTags: Array<'hr' | 'br' | 'input' | 'img'> = ['hr', 'br', 'input', 'img']
|
|
69
|
-
|
|
70
|
-
for (const tag of voidTags) {
|
|
71
|
-
it(`<${tag}> mounts without "void element" console.warn`, () => {
|
|
72
|
-
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
73
|
-
const { container, unmount } = mountInBrowser(<Element tag={tag} data-id={tag} />)
|
|
74
|
-
const el = container.querySelector(`[data-id="${tag}"]`)
|
|
75
|
-
expect(el?.tagName.toLowerCase()).toBe(tag)
|
|
76
|
-
const voidWarnings = warnSpy.mock.calls.filter((args) =>
|
|
77
|
-
typeof args[0] === 'string' && args[0].includes('void element'),
|
|
78
|
-
)
|
|
79
|
-
expect(voidWarnings).toEqual([])
|
|
80
|
-
warnSpy.mockRestore()
|
|
81
|
-
unmount()
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
// Regression: Wrapper used to silently drop dangerouslySetInnerHTML.
|
|
87
|
-
// The unit test asserts Wrapper forwards the prop on the rendered
|
|
88
|
-
// vnode; this real-Chromium test asserts the SVG actually appears in
|
|
89
|
-
// the DOM after mount — the user-visible bug shape was "renders empty
|
|
90
|
-
// <div></div> instead of inlined SVG".
|
|
91
|
-
describe('dangerouslySetInnerHTML — real-Chromium DOM proof', () => {
|
|
92
|
-
it('inlines an SVG via Element + dangerouslySetInnerHTML', () => {
|
|
93
|
-
const { container, unmount } = mountInBrowser(
|
|
94
|
-
<Element
|
|
95
|
-
tag="div"
|
|
96
|
-
data-id="logo"
|
|
97
|
-
dangerouslySetInnerHTML={{
|
|
98
|
-
__html: '<svg viewBox="0 0 10 10"><rect width="10" height="10" /></svg>',
|
|
99
|
-
}}
|
|
100
|
-
/>,
|
|
101
|
-
)
|
|
102
|
-
const root = container.querySelector('[data-id="logo"]')
|
|
103
|
-
expect(root).toBeTruthy()
|
|
104
|
-
// The SVG must actually be in the DOM, not lost between Wrapper and
|
|
105
|
-
// the renderer. Pre-fix this assertion failed: container had
|
|
106
|
-
// <div data-id="logo"></div> with no children.
|
|
107
|
-
const svg = root?.querySelector('svg')
|
|
108
|
-
expect(svg).toBeTruthy()
|
|
109
|
-
expect(svg?.tagName.toLowerCase()).toBe('svg')
|
|
110
|
-
expect(svg?.querySelector('rect')).toBeTruthy()
|
|
111
|
-
unmount()
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('inlines markup on a button (needsFix path)', () => {
|
|
115
|
-
const { container, unmount } = mountInBrowser(
|
|
116
|
-
<Element
|
|
117
|
-
tag="button"
|
|
118
|
-
data-id="btn"
|
|
119
|
-
dangerouslySetInnerHTML={{ __html: '<strong>Save</strong>' }}
|
|
120
|
-
/>,
|
|
121
|
-
)
|
|
122
|
-
const btn = container.querySelector('[data-id="btn"]')
|
|
123
|
-
expect(btn).toBeTruthy()
|
|
124
|
-
// The bold text must reach the DOM. The needsFix gate's existing
|
|
125
|
-
// `!own.dangerouslySetInnerHTML` clause skips the two-layer fix,
|
|
126
|
-
// so this falls into the !needsFix branch and the fix's
|
|
127
|
-
// `if (innerHTML)` path takes over.
|
|
128
|
-
expect(btn?.querySelector('strong')?.textContent).toBe('Save')
|
|
129
|
-
unmount()
|
|
130
|
-
})
|
|
131
|
-
})
|
|
132
|
-
})
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import type { VNode } from '@pyreon/core'
|
|
2
|
-
import { h } from '@pyreon/core'
|
|
3
|
-
import { describe, expect, it } from 'vitest'
|
|
4
|
-
import { Element } from '../Element'
|
|
5
|
-
import Content from '../helpers/Content/component'
|
|
6
|
-
import Wrapper from '../helpers/Wrapper/component'
|
|
7
|
-
|
|
8
|
-
const asVNode = (v: unknown) => v as VNode
|
|
9
|
-
|
|
10
|
-
const getContentSlots = (result: VNode): VNode[] => {
|
|
11
|
-
const children = result.props.children
|
|
12
|
-
if (!Array.isArray(children)) return []
|
|
13
|
-
return children.filter(
|
|
14
|
-
(c: unknown) =>
|
|
15
|
-
c != null && typeof c === 'object' && 'type' in (c as VNode) && (c as VNode).type === Content,
|
|
16
|
-
) as VNode[]
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('Element equalBeforeAfter', () => {
|
|
20
|
-
it('always passes a ref function to Wrapper', () => {
|
|
21
|
-
const result = asVNode(
|
|
22
|
-
Element({
|
|
23
|
-
equalBeforeAfter: true,
|
|
24
|
-
direction: 'inline',
|
|
25
|
-
beforeContent: h('span', null, 'Before'),
|
|
26
|
-
afterContent: h('span', null, 'After'),
|
|
27
|
-
children: 'Main',
|
|
28
|
-
}),
|
|
29
|
-
)
|
|
30
|
-
expect(typeof result.type).toBe("function")
|
|
31
|
-
expect(typeof result.props.ref).toBe('function')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('does not crash without before/after content', () => {
|
|
35
|
-
const result = asVNode(Element({ equalBeforeAfter: true, children: 'Main only' }))
|
|
36
|
-
expect(typeof result.type).toBe("function")
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('does not crash with only beforeContent', () => {
|
|
40
|
-
const result = asVNode(
|
|
41
|
-
Element({
|
|
42
|
-
equalBeforeAfter: true,
|
|
43
|
-
beforeContent: h('span', null, 'Before'),
|
|
44
|
-
children: 'Main',
|
|
45
|
-
}),
|
|
46
|
-
)
|
|
47
|
-
expect(typeof result.type).toBe("function")
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('does not crash with only afterContent', () => {
|
|
51
|
-
const result = asVNode(
|
|
52
|
-
Element({
|
|
53
|
-
equalBeforeAfter: true,
|
|
54
|
-
afterContent: h('span', null, 'After'),
|
|
55
|
-
children: 'Main',
|
|
56
|
-
}),
|
|
57
|
-
)
|
|
58
|
-
expect(typeof result.type).toBe("function")
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('renders three slot children when both before and after exist', () => {
|
|
62
|
-
const result = asVNode(
|
|
63
|
-
Element({
|
|
64
|
-
equalBeforeAfter: true,
|
|
65
|
-
direction: 'inline',
|
|
66
|
-
beforeContent: h('span', null, 'Short'),
|
|
67
|
-
afterContent: h('span', null, 'Longer content'),
|
|
68
|
-
children: 'Center',
|
|
69
|
-
}),
|
|
70
|
-
)
|
|
71
|
-
const slots = getContentSlots(result)
|
|
72
|
-
expect(slots).toHaveLength(3)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('merged ref calls external function ref', () => {
|
|
76
|
-
let captured: HTMLElement | null = null
|
|
77
|
-
const ref = (node: HTMLElement | null) => {
|
|
78
|
-
captured = node
|
|
79
|
-
}
|
|
80
|
-
const result = asVNode(
|
|
81
|
-
Element({
|
|
82
|
-
equalBeforeAfter: true,
|
|
83
|
-
ref,
|
|
84
|
-
beforeContent: h('span', null, 'Before'),
|
|
85
|
-
afterContent: h('span', null, 'After'),
|
|
86
|
-
children: 'Main',
|
|
87
|
-
}),
|
|
88
|
-
)
|
|
89
|
-
const fakeNode = {} as HTMLElement
|
|
90
|
-
;(result.props.ref as (node: HTMLElement | null) => void)(fakeNode)
|
|
91
|
-
expect(captured).toBe(fakeNode)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('merged ref sets object ref current', () => {
|
|
95
|
-
const ref = { current: null as HTMLElement | null }
|
|
96
|
-
const result = asVNode(
|
|
97
|
-
Element({
|
|
98
|
-
equalBeforeAfter: true,
|
|
99
|
-
ref,
|
|
100
|
-
beforeContent: h('span', null, 'Before'),
|
|
101
|
-
afterContent: h('span', null, 'After'),
|
|
102
|
-
children: 'Main',
|
|
103
|
-
}),
|
|
104
|
-
)
|
|
105
|
-
const fakeNode = {} as HTMLElement
|
|
106
|
-
;(result.props.ref as (node: HTMLElement | null) => void)(fakeNode)
|
|
107
|
-
expect(ref.current).toBe(fakeNode)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('uses inline direction for equalBeforeAfter', () => {
|
|
111
|
-
const result = asVNode(
|
|
112
|
-
Element({
|
|
113
|
-
equalBeforeAfter: true,
|
|
114
|
-
direction: 'inline',
|
|
115
|
-
beforeContent: h('span', null, 'B'),
|
|
116
|
-
afterContent: h('span', null, 'A'),
|
|
117
|
-
children: 'Main',
|
|
118
|
-
}),
|
|
119
|
-
)
|
|
120
|
-
expect(result.props.direction).toBe('inline')
|
|
121
|
-
})
|
|
122
|
-
})
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { INLINE_ELEMENTS_FLEX_FIX } from '../helpers/Wrapper/constants'
|
|
3
|
-
import { isWebFixNeeded } from '../helpers/Wrapper/utils'
|
|
4
|
-
|
|
5
|
-
describe('Wrapper utils', () => {
|
|
6
|
-
describe('isWebFixNeeded', () => {
|
|
7
|
-
it('returns true for button', () => {
|
|
8
|
-
expect(isWebFixNeeded('button')).toBe(true)
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it('returns true for fieldset', () => {
|
|
12
|
-
expect(isWebFixNeeded('fieldset')).toBe(true)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it('returns true for legend', () => {
|
|
16
|
-
expect(isWebFixNeeded('legend')).toBe(true)
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('returns false for div', () => {
|
|
20
|
-
expect(isWebFixNeeded('div')).toBe(false)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('returns false for span', () => {
|
|
24
|
-
expect(isWebFixNeeded('span')).toBe(false)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('returns false for section', () => {
|
|
28
|
-
expect(isWebFixNeeded('section')).toBe(false)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('returns false for undefined', () => {
|
|
32
|
-
expect(isWebFixNeeded(undefined)).toBe(false)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('returns false for empty string', () => {
|
|
36
|
-
expect(isWebFixNeeded('')).toBe(false)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('returns false for input', () => {
|
|
40
|
-
expect(isWebFixNeeded('input')).toBe(false)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('returns false for form', () => {
|
|
44
|
-
expect(isWebFixNeeded('form')).toBe(false)
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
describe('INLINE_ELEMENTS_FLEX_FIX', () => {
|
|
49
|
-
it('contains button', () => {
|
|
50
|
-
expect(INLINE_ELEMENTS_FLEX_FIX.button).toBe(true)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('contains fieldset', () => {
|
|
54
|
-
expect(INLINE_ELEMENTS_FLEX_FIX.fieldset).toBe(true)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('contains legend', () => {
|
|
58
|
-
expect(INLINE_ELEMENTS_FLEX_FIX.legend).toBe(true)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('only has 3 entries', () => {
|
|
62
|
-
expect(Object.keys(INLINE_ELEMENTS_FLEX_FIX)).toHaveLength(3)
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
})
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import type { VNode } from '@pyreon/core'
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
-
|
|
4
|
-
// Element's simple-Element fast path inlines the Wrapper helper directly into
|
|
5
|
-
// a Styled invocation, so layout props move from `result.props.{tag, direction, …}`
|
|
6
|
-
// to `result.props.{as, $element.direction, …}`. This helper reads from
|
|
7
|
-
// whichever shape the result is in so assertions don't depend on which path.
|
|
8
|
-
const getLayoutProps = (result: VNode): Record<string, unknown> => {
|
|
9
|
-
const p = result.props as Record<string, unknown>
|
|
10
|
-
if (p.$element && typeof p.$element === 'object') {
|
|
11
|
-
const el = p.$element as Record<string, unknown>
|
|
12
|
-
return {
|
|
13
|
-
tag: p.as,
|
|
14
|
-
direction: el.direction,
|
|
15
|
-
alignX: el.alignX,
|
|
16
|
-
alignY: el.alignY,
|
|
17
|
-
block: el.block,
|
|
18
|
-
equalCols: el.equalCols,
|
|
19
|
-
extendCss: el.extraStyles,
|
|
20
|
-
isInline: undefined,
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return {
|
|
24
|
-
tag: p.tag,
|
|
25
|
-
direction: p.direction,
|
|
26
|
-
alignX: p.alignX,
|
|
27
|
-
alignY: p.alignY,
|
|
28
|
-
block: p.block,
|
|
29
|
-
equalCols: p.equalCols,
|
|
30
|
-
extendCss: p.extendCss,
|
|
31
|
-
isInline: p.isInline,
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// Mocks — match patterns from existing element tests
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
vi.mock('~/utils', () => ({
|
|
39
|
-
IS_DEVELOPMENT: true,
|
|
40
|
-
}))
|
|
41
|
-
|
|
42
|
-
vi.mock('@pyreon/ui-core', async (importOriginal) => {
|
|
43
|
-
const actual = (await importOriginal()) as Record<string, unknown>
|
|
44
|
-
return {
|
|
45
|
-
...actual,
|
|
46
|
-
render: (children: unknown) => children,
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
import { Element } from '../Element'
|
|
51
|
-
import Content from '../helpers/Content/component'
|
|
52
|
-
import Wrapper from '../helpers/Wrapper/component'
|
|
53
|
-
|
|
54
|
-
const asVNode = (v: unknown) => v as VNode
|
|
55
|
-
|
|
56
|
-
const getContentSlots = (result: VNode): VNode[] => {
|
|
57
|
-
const children = result.props.children
|
|
58
|
-
if (!Array.isArray(children)) return []
|
|
59
|
-
return children.filter(
|
|
60
|
-
(c: unknown) =>
|
|
61
|
-
c != null && typeof c === 'object' && 'type' in (c as VNode) && (c as VNode).type === Content,
|
|
62
|
-
) as VNode[]
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ─── Integration tests ───────────────────────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
describe('Element integration', () => {
|
|
68
|
-
it('renders with content prop producing Wrapper VNode', () => {
|
|
69
|
-
const result = asVNode(Element({ content: 'hello world', children: undefined }))
|
|
70
|
-
expect(typeof result.type).toBe("function")
|
|
71
|
-
const children = result.props.children
|
|
72
|
-
expect(children).toBeDefined()
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('three-section layout: beforeContent + content + afterContent', () => {
|
|
76
|
-
const result = asVNode(
|
|
77
|
-
Element({
|
|
78
|
-
beforeContent: 'icon',
|
|
79
|
-
content: 'main text',
|
|
80
|
-
afterContent: 'arrow',
|
|
81
|
-
children: undefined,
|
|
82
|
-
}),
|
|
83
|
-
)
|
|
84
|
-
expect(typeof result.type).toBe("function")
|
|
85
|
-
const slots = getContentSlots(result)
|
|
86
|
-
// With before/after content, there should be Content wrapper VNodes
|
|
87
|
-
expect(slots.length).toBeGreaterThanOrEqual(2)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('direction and alignX props pass through to Wrapper', () => {
|
|
91
|
-
const result = asVNode(
|
|
92
|
-
Element({
|
|
93
|
-
direction: 'inline',
|
|
94
|
-
alignX: 'center',
|
|
95
|
-
children: 'test',
|
|
96
|
-
}),
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
// When beforeContent/afterContent are absent, direction falls through
|
|
100
|
-
// to wrapper level and contentDirection/contentAlignX take effect
|
|
101
|
-
expect(typeof result.type).toBe("function")
|
|
102
|
-
// The wrapper receives alignment props
|
|
103
|
-
expect(result.props).toBeDefined()
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('contentDirection overrides default direction on wrapper for simple element', () => {
|
|
107
|
-
const result = asVNode(
|
|
108
|
-
Element({
|
|
109
|
-
contentDirection: 'inline',
|
|
110
|
-
contentAlignX: 'center',
|
|
111
|
-
children: 'test',
|
|
112
|
-
}),
|
|
113
|
-
)
|
|
114
|
-
// Simple element (no before/after) uses contentDirection as wrapper direction
|
|
115
|
-
expect(getLayoutProps(result).direction).toBe('inline')
|
|
116
|
-
expect(getLayoutProps(result).alignX).toBe('center')
|
|
117
|
-
})
|
|
118
|
-
})
|