@pyreon/elements 0.11.1 → 0.11.3
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 +8 -7
- package/src/Element/component.tsx +211 -0
- package/src/Element/constants.ts +96 -0
- package/src/Element/index.ts +6 -0
- package/src/Element/types.ts +168 -0
- package/src/Element/utils.ts +15 -0
- package/src/List/component.tsx +57 -0
- package/src/List/index.ts +5 -0
- package/src/Overlay/component.tsx +131 -0
- package/src/Overlay/context.tsx +37 -0
- package/src/Overlay/index.ts +7 -0
- package/src/Overlay/useOverlay.tsx +616 -0
- package/src/Portal/component.tsx +41 -0
- package/src/Portal/index.ts +5 -0
- package/src/Text/component.tsx +65 -0
- package/src/Text/index.ts +5 -0
- package/src/Text/styled.ts +30 -0
- package/src/Util/component.tsx +43 -0
- package/src/Util/index.ts +5 -0
- package/src/__tests__/Content.test.tsx +115 -0
- package/src/__tests__/Element.test.ts +604 -0
- package/src/__tests__/Iterator.test.ts +483 -0
- package/src/__tests__/List.test.ts +199 -0
- package/src/__tests__/Overlay.test.ts +485 -0
- package/src/__tests__/Portal.test.ts +82 -0
- package/src/__tests__/Text.test.ts +274 -0
- package/src/__tests__/Util.test.ts +63 -0
- package/src/__tests__/Wrapper.test.tsx +152 -0
- package/src/__tests__/equalBeforeAfter.test.ts +122 -0
- package/src/__tests__/helpers.test.ts +65 -0
- package/src/__tests__/overlayContext.test.tsx +78 -0
- package/src/__tests__/responsiveProps.test.ts +298 -0
- package/src/__tests__/useOverlay.test.ts +1330 -0
- package/src/__tests__/utils.test.ts +69 -0
- package/src/constants.ts +1 -0
- package/src/helpers/Content/component.tsx +51 -0
- package/src/helpers/Content/index.ts +3 -0
- package/src/helpers/Content/styled.ts +105 -0
- package/src/helpers/Content/types.ts +49 -0
- package/src/helpers/Iterator/component.tsx +252 -0
- package/src/helpers/Iterator/index.ts +13 -0
- package/src/helpers/Iterator/types.ts +79 -0
- package/src/helpers/Wrapper/component.tsx +78 -0
- package/src/helpers/Wrapper/constants.ts +10 -0
- package/src/helpers/Wrapper/index.ts +3 -0
- package/src/helpers/Wrapper/styled.ts +69 -0
- package/src/helpers/Wrapper/types.ts +56 -0
- package/src/helpers/Wrapper/utils.ts +7 -0
- package/src/helpers/index.ts +4 -0
- package/src/index.ts +37 -0
- package/src/types.ts +81 -0
- package/src/utils.ts +1 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import type { VNode } from "@pyreon/core"
|
|
2
|
+
import { h } from "@pyreon/core"
|
|
3
|
+
import { describe, expect, it } from "vitest"
|
|
4
|
+
import { Text } from "../Text"
|
|
5
|
+
|
|
6
|
+
const asVNode = (v: unknown) => v as VNode
|
|
7
|
+
|
|
8
|
+
describe("Text", () => {
|
|
9
|
+
describe("static properties", () => {
|
|
10
|
+
it("has isText set to true", () => {
|
|
11
|
+
expect(Text.isText).toBe(true)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("has correct displayName", () => {
|
|
15
|
+
expect(Text.displayName).toBe("@pyreon/elements/Text")
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it("has correct pkgName", () => {
|
|
19
|
+
expect(Text.pkgName).toBe("@pyreon/elements")
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("has correct PYREON__COMPONENT", () => {
|
|
23
|
+
expect(Text.PYREON__COMPONENT).toBe("@pyreon/elements/Text")
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe("default rendering", () => {
|
|
28
|
+
it("returns a VNode whose type is the Styled component (a function)", () => {
|
|
29
|
+
const result = asVNode(Text({ children: "Hello" }))
|
|
30
|
+
expect(typeof result.type).toBe("function")
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("does not set as prop when no tag or paragraph provided", () => {
|
|
34
|
+
const result = asVNode(Text({ children: "Hello" }))
|
|
35
|
+
expect(result.props.as).toBeUndefined()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it("renders children in output", () => {
|
|
39
|
+
const result = asVNode(Text({ children: "Hello" }))
|
|
40
|
+
expect(result.props.children).toBe("Hello")
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it("passes $text prop with extraStyles", () => {
|
|
44
|
+
const result = asVNode(Text({ children: "Hello" }))
|
|
45
|
+
expect(result.props.$text).toEqual({ extraStyles: undefined })
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("renders null content when no children or label", () => {
|
|
49
|
+
const result = asVNode(Text({}))
|
|
50
|
+
expect(result.props.children).toBeUndefined()
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe("content fallback chain", () => {
|
|
55
|
+
it("renders children as content", () => {
|
|
56
|
+
const result = asVNode(Text({ children: "child content" }))
|
|
57
|
+
expect(result.props.children).toBe("child content")
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it("renders label when children not provided", () => {
|
|
61
|
+
const result = asVNode(Text({ label: "label text" }))
|
|
62
|
+
expect(result.props.children).toBe("label text")
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it("prefers children over label", () => {
|
|
66
|
+
const result = asVNode(Text({ children: "child", label: "label" }))
|
|
67
|
+
expect(result.props.children).toBe("child")
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it("renders VNode children", () => {
|
|
71
|
+
const child = h("strong", null, "bold")
|
|
72
|
+
const result = asVNode(Text({ children: child }))
|
|
73
|
+
expect(result.props.children).toBe(child)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it("renders number label", () => {
|
|
77
|
+
const result = asVNode(Text({ label: 42 }))
|
|
78
|
+
expect(result.props.children).toBe(42)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe("tag prop", () => {
|
|
83
|
+
it("sets as prop to h1", () => {
|
|
84
|
+
const result = asVNode(Text({ tag: "h1", children: "Heading" }))
|
|
85
|
+
expect(result.props.as).toBe("h1")
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it("sets as prop to h2", () => {
|
|
89
|
+
const result = asVNode(Text({ tag: "h2", children: "Sub heading" }))
|
|
90
|
+
expect(result.props.as).toBe("h2")
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("sets as prop to strong", () => {
|
|
94
|
+
const result = asVNode(Text({ tag: "strong", children: "Bold" }))
|
|
95
|
+
expect(result.props.as).toBe("strong")
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it("sets as prop to em", () => {
|
|
99
|
+
const result = asVNode(Text({ tag: "em", children: "Italic" }))
|
|
100
|
+
expect(result.props.as).toBe("em")
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it("does not forward tag as a prop", () => {
|
|
104
|
+
const result = asVNode(Text({ tag: "h1", children: "Heading" }))
|
|
105
|
+
expect(result.props.tag).toBeUndefined()
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe("paragraph prop", () => {
|
|
110
|
+
it("sets as prop to p when paragraph is true", () => {
|
|
111
|
+
const result = asVNode(Text({ paragraph: true, children: "Paragraph text" }))
|
|
112
|
+
expect(result.props.as).toBe("p")
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it("does not set as prop when paragraph is false", () => {
|
|
116
|
+
const result = asVNode(Text({ paragraph: false, children: "Inline text" }))
|
|
117
|
+
expect(result.props.as).toBeUndefined()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it("paragraph is overridden when tag is also set (tag wins because paragraph branch is skipped)", () => {
|
|
121
|
+
// When paragraph is true, finalTag = 'p'
|
|
122
|
+
// When tag is also set, the else branch is not reached, so paragraph wins
|
|
123
|
+
const result = asVNode(Text({ tag: "h1", paragraph: true, children: "Heading" }))
|
|
124
|
+
expect(result.props.as).toBe("p")
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it("uses tag when paragraph is false", () => {
|
|
128
|
+
const result = asVNode(Text({ tag: "h1", paragraph: false, children: "Heading" }))
|
|
129
|
+
expect(result.props.as).toBe("h1")
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("does not forward paragraph as a prop", () => {
|
|
133
|
+
const result = asVNode(Text({ paragraph: true, children: "text" }))
|
|
134
|
+
expect(result.props.paragraph).toBeUndefined()
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe("css prop", () => {
|
|
139
|
+
it("passes css value through $text.extraStyles", () => {
|
|
140
|
+
const customCss = "color: red;"
|
|
141
|
+
const result = asVNode(Text({ css: customCss, children: "styled" }))
|
|
142
|
+
expect(result.props.$text).toEqual({ extraStyles: customCss })
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it("passes undefined extraStyles when no css prop", () => {
|
|
146
|
+
const result = asVNode(Text({ children: "plain" }))
|
|
147
|
+
expect(result.props.$text).toEqual({ extraStyles: undefined })
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it("does not forward css as a direct prop", () => {
|
|
151
|
+
const result = asVNode(Text({ css: "color: blue;", children: "text" }))
|
|
152
|
+
expect(result.props.css).toBeUndefined()
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
describe("HTML attribute forwarding", () => {
|
|
157
|
+
it("passes through id", () => {
|
|
158
|
+
const result = asVNode(Text({ id: "text-id", children: "text" }))
|
|
159
|
+
expect(result.props.id).toBe("text-id")
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it("passes through role", () => {
|
|
163
|
+
const result = asVNode(Text({ role: "heading", children: "text" }))
|
|
164
|
+
expect(result.props.role).toBe("heading")
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it("passes through title", () => {
|
|
168
|
+
const result = asVNode(Text({ title: "tooltip", children: "text" }))
|
|
169
|
+
expect(result.props.title).toBe("tooltip")
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it("passes through data- attributes", () => {
|
|
173
|
+
const result = asVNode(Text({ "data-testid": "txt", children: "text" }))
|
|
174
|
+
expect(result.props["data-testid"]).toBe("txt")
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it("passes through aria- attributes", () => {
|
|
178
|
+
const result = asVNode(Text({ "aria-label": "label", children: "text" }))
|
|
179
|
+
expect(result.props["aria-label"]).toBe("label")
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it("passes through on-prefixed event handlers", () => {
|
|
183
|
+
const handler = () => undefined
|
|
184
|
+
const result = asVNode(Text({ onClick: handler, children: "text" }))
|
|
185
|
+
expect(result.props.onClick).toBe(handler)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it("passes through ref", () => {
|
|
189
|
+
const ref = { current: null }
|
|
190
|
+
const result = asVNode(Text({ ref, children: "text" }))
|
|
191
|
+
expect(result.props.ref).toBe(ref)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it("passes through class", () => {
|
|
195
|
+
const result = asVNode(Text({ class: "title", children: "text" }))
|
|
196
|
+
expect(result.props.class).toBe("title")
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it("passes through style", () => {
|
|
200
|
+
const result = asVNode(Text({ style: "color: red;", children: "text" }))
|
|
201
|
+
expect(result.props.style).toBe("color: red;")
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it("does not set class when not provided", () => {
|
|
205
|
+
const result = asVNode(Text({ children: "text" }))
|
|
206
|
+
expect(result.props.class).toBeUndefined()
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it("does not set style when not provided", () => {
|
|
210
|
+
const result = asVNode(Text({ children: "text" }))
|
|
211
|
+
expect(result.props.style).toBeUndefined()
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
describe("reserved props are consumed and not forwarded", () => {
|
|
216
|
+
it("does not forward paragraph, label, tag, or css", () => {
|
|
217
|
+
const result = asVNode(
|
|
218
|
+
Text({
|
|
219
|
+
paragraph: true,
|
|
220
|
+
label: "lbl",
|
|
221
|
+
children: "text",
|
|
222
|
+
tag: "h1",
|
|
223
|
+
css: "font-size: 2rem;",
|
|
224
|
+
}),
|
|
225
|
+
)
|
|
226
|
+
expect(result.props.paragraph).toBeUndefined()
|
|
227
|
+
expect(result.props.label).toBeUndefined()
|
|
228
|
+
expect(result.props.tag).toBeUndefined()
|
|
229
|
+
expect(result.props.css).toBeUndefined()
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
describe("combined props", () => {
|
|
234
|
+
it("renders with paragraph, css, class, and data attribute together", () => {
|
|
235
|
+
const result = asVNode(
|
|
236
|
+
Text({
|
|
237
|
+
paragraph: true,
|
|
238
|
+
css: "margin: 0;",
|
|
239
|
+
class: "intro",
|
|
240
|
+
"data-testid": "intro-text",
|
|
241
|
+
children: "Hello world",
|
|
242
|
+
}),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
expect(typeof result.type).toBe("function")
|
|
246
|
+
expect(result.props.as).toBe("p")
|
|
247
|
+
expect(result.props.$text).toEqual({ extraStyles: "margin: 0;" })
|
|
248
|
+
expect(result.props.class).toBe("intro")
|
|
249
|
+
expect(result.props["data-testid"]).toBe("intro-text")
|
|
250
|
+
expect(result.props.children).toBe("Hello world")
|
|
251
|
+
// Reserved props not forwarded
|
|
252
|
+
expect(result.props.paragraph).toBeUndefined()
|
|
253
|
+
expect(result.props.css).toBeUndefined()
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it("renders with tag, ref, and event handler together", () => {
|
|
257
|
+
const ref = { current: null }
|
|
258
|
+
const handler = () => undefined
|
|
259
|
+
const result = asVNode(
|
|
260
|
+
Text({
|
|
261
|
+
tag: "h2",
|
|
262
|
+
ref,
|
|
263
|
+
onClick: handler,
|
|
264
|
+
children: "Subtitle",
|
|
265
|
+
}),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
expect(result.props.as).toBe("h2")
|
|
269
|
+
expect(result.props.ref).toBe(ref)
|
|
270
|
+
expect(result.props.onClick).toBe(handler)
|
|
271
|
+
expect(result.props.children).toBe("Subtitle")
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
2
|
+
import { h } from "@pyreon/core"
|
|
3
|
+
import { describe, expect, it } from "vitest"
|
|
4
|
+
import Util from "../Util/component"
|
|
5
|
+
|
|
6
|
+
describe("Util", () => {
|
|
7
|
+
describe("statics", () => {
|
|
8
|
+
it("has displayName", () => {
|
|
9
|
+
expect(Util.displayName).toBe("@pyreon/elements/Util")
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it("has pkgName", () => {
|
|
13
|
+
expect(Util.pkgName).toBe("@pyreon/elements")
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it("has PYREON__COMPONENT", () => {
|
|
17
|
+
expect(Util.PYREON__COMPONENT).toBe("@pyreon/elements/Util")
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe("className injection", () => {
|
|
22
|
+
it("calls render with className prop for string className", () => {
|
|
23
|
+
const child = h("div", { "data-testid": "child" }, "Content")
|
|
24
|
+
const result = Util({ children: child, className: "my-class" }) as VNodeChild
|
|
25
|
+
// render() should merge className into the child
|
|
26
|
+
expect(result).toBeDefined()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("joins array className into space-separated string", () => {
|
|
30
|
+
const child = h("div", { "data-testid": "child" }, "Content")
|
|
31
|
+
const result = Util({ children: child, className: ["cls-a", "cls-b"] }) as VNodeChild
|
|
32
|
+
expect(result).toBeDefined()
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe("style injection", () => {
|
|
37
|
+
it("calls render with style prop for style object", () => {
|
|
38
|
+
const child = h("div", { "data-testid": "child" }, "Content")
|
|
39
|
+
const result = Util({ children: child, style: { color: "red" } }) as VNodeChild
|
|
40
|
+
expect(result).toBeDefined()
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe("both className and style", () => {
|
|
45
|
+
it("passes both className and style to render", () => {
|
|
46
|
+
const child = h("div", { "data-testid": "child" }, "Content")
|
|
47
|
+
const result = Util({
|
|
48
|
+
children: child,
|
|
49
|
+
className: "my-class",
|
|
50
|
+
style: { color: "blue" },
|
|
51
|
+
}) as VNodeChild
|
|
52
|
+
expect(result).toBeDefined()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe("no-op when no props", () => {
|
|
57
|
+
it("renders children without modification when no className or style", () => {
|
|
58
|
+
const child = h("div", { "data-testid": "child" }, "Content")
|
|
59
|
+
const result = Util({ children: child }) as VNodeChild
|
|
60
|
+
expect(result).toBeDefined()
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
})
|
|
@@ -0,0 +1,122 @@
|
|
|
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(result.type).toBe(Wrapper)
|
|
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(result.type).toBe(Wrapper)
|
|
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(result.type).toBe(Wrapper)
|
|
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(result.type).toBe(Wrapper)
|
|
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
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
})
|