@pyreon/styler 0.11.4 → 0.11.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/README.md +27 -23
- package/lib/index.d.ts +9 -2
- package/lib/index.js +47 -4
- package/package.json +22 -22
- package/src/ThemeProvider.ts +10 -3
- package/src/__tests__/ThemeProvider.test.ts +21 -21
- package/src/__tests__/benchmark.bench.ts +56 -45
- package/src/__tests__/composition-chain.test.ts +200 -151
- package/src/__tests__/forward.test.ts +122 -122
- package/src/__tests__/globalStyle.test.ts +18 -18
- package/src/__tests__/hash.test.ts +27 -27
- package/src/__tests__/hybrid-injection.test.ts +83 -59
- package/src/__tests__/index.ts +10 -10
- package/src/__tests__/insertion-effect.test.ts +45 -32
- package/src/__tests__/integration.test.ts +81 -51
- package/src/__tests__/keyframes.test.ts +13 -13
- package/src/__tests__/memory-growth.test.ts +21 -21
- package/src/__tests__/p3-features.test.ts +162 -104
- package/src/__tests__/shared.test.ts +51 -33
- package/src/__tests__/sheet-advanced.test.ts +227 -227
- package/src/__tests__/sheet-split-atrules.test.ts +85 -85
- package/src/__tests__/sheet.test.ts +69 -69
- package/src/__tests__/styled-ssr.test.ts +36 -28
- package/src/__tests__/styled.test.ts +214 -145
- package/src/__tests__/theme.test.ts +11 -11
- package/src/__tests__/useCSS.test.ts +89 -59
- package/src/css.ts +1 -1
- package/src/forward.ts +187 -187
- package/src/globalStyle.ts +5 -5
- package/src/index.ts +15 -15
- package/src/keyframes.ts +3 -3
- package/src/resolve.ts +14 -14
- package/src/shared.ts +2 -2
- package/src/sheet.ts +26 -26
- package/src/styled.tsx +145 -100
- package/src/useCSS.ts +4 -4
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
* and inspect the returned VNode + the sheet's CSSOM.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { VNode } from
|
|
12
|
-
import { afterEach, describe, expect, it } from
|
|
13
|
-
import { createGlobalStyle } from
|
|
14
|
-
import { sheet } from
|
|
15
|
-
import { styled } from
|
|
11
|
+
import type { VNode } from '@pyreon/core'
|
|
12
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
13
|
+
import { createGlobalStyle } from '../globalStyle'
|
|
14
|
+
import { sheet } from '../sheet'
|
|
15
|
+
import { styled } from '../styled'
|
|
16
16
|
|
|
17
17
|
/** Helper: collect all CSS rule texts from the shared <style data-pyreon-styler> sheet. */
|
|
18
18
|
const getSheetRules = (): string[] => {
|
|
19
|
-
const el = document.querySelector(
|
|
19
|
+
const el = document.querySelector('style[data-pyreon-styler]') as HTMLStyleElement | null
|
|
20
20
|
if (!el?.sheet) return []
|
|
21
21
|
return Array.from(el.sheet.cssRules).map((r) => r.cssText)
|
|
22
22
|
}
|
|
@@ -25,68 +25,78 @@ const getSheetRules = (): string[] => {
|
|
|
25
25
|
const findRulesFor = (className: string): string[] =>
|
|
26
26
|
getSheetRules().filter((r) => r.includes(`.${className}`))
|
|
27
27
|
|
|
28
|
-
describe(
|
|
28
|
+
describe('hybrid injection — CSS in shared sheet', () => {
|
|
29
29
|
afterEach(() => {
|
|
30
30
|
sheet.clearAll()
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
describe(
|
|
34
|
-
it(
|
|
35
|
-
const Comp = styled(
|
|
33
|
+
describe('static styled components', () => {
|
|
34
|
+
it('injects CSS rules into the shared <style data-pyreon-styler> element', () => {
|
|
35
|
+
const Comp = styled('div')`
|
|
36
|
+
color: red;
|
|
37
|
+
`
|
|
36
38
|
const vnode = Comp({}) as VNode
|
|
37
39
|
const className = vnode.props.class as string
|
|
38
40
|
|
|
39
41
|
const rules = findRulesFor(className)
|
|
40
42
|
expect(rules.length).toBeGreaterThanOrEqual(1)
|
|
41
|
-
expect(rules.some((r) => r.includes(
|
|
43
|
+
expect(rules.some((r) => r.includes('color: red'))).toBe(true)
|
|
42
44
|
})
|
|
43
45
|
|
|
44
|
-
it(
|
|
45
|
-
const A = styled(
|
|
46
|
-
|
|
46
|
+
it('multiple static components share the same <style> element', () => {
|
|
47
|
+
const A = styled('div')`
|
|
48
|
+
color: red;
|
|
49
|
+
`
|
|
50
|
+
const B = styled('span')`
|
|
51
|
+
font-size: 20px;
|
|
52
|
+
`
|
|
47
53
|
|
|
48
54
|
A({})
|
|
49
55
|
B({})
|
|
50
56
|
|
|
51
57
|
// Both should be in the same sheet
|
|
52
|
-
const styleEls = document.querySelectorAll(
|
|
58
|
+
const styleEls = document.querySelectorAll('style[data-pyreon-styler]')
|
|
53
59
|
expect(styleEls.length).toBe(1)
|
|
54
60
|
|
|
55
61
|
const rules = getSheetRules()
|
|
56
|
-
expect(rules.some((r) => r.includes(
|
|
57
|
-
expect(rules.some((r) => r.includes(
|
|
62
|
+
expect(rules.some((r) => r.includes('color: red'))).toBe(true)
|
|
63
|
+
expect(rules.some((r) => r.includes('font-size: 20px'))).toBe(true)
|
|
58
64
|
})
|
|
59
65
|
})
|
|
60
66
|
|
|
61
|
-
describe(
|
|
62
|
-
it(
|
|
63
|
-
const Comp = styled(
|
|
64
|
-
|
|
67
|
+
describe('dynamic styled components', () => {
|
|
68
|
+
it('injects CSS into the shared sheet', () => {
|
|
69
|
+
const Comp = styled('div')`
|
|
70
|
+
color: ${(p: any) => p.$color};
|
|
71
|
+
`
|
|
72
|
+
const vnode = Comp({ $color: 'blue' }) as VNode
|
|
65
73
|
const className = vnode.props.class as string
|
|
66
74
|
|
|
67
75
|
const rules = findRulesFor(className)
|
|
68
76
|
expect(rules.length).toBeGreaterThanOrEqual(1)
|
|
69
|
-
expect(rules.some((r) => r.includes(
|
|
77
|
+
expect(rules.some((r) => r.includes('color: blue'))).toBe(true)
|
|
70
78
|
})
|
|
71
79
|
|
|
72
|
-
it(
|
|
73
|
-
const Comp = styled(
|
|
74
|
-
|
|
80
|
+
it('different prop values inject different CSS rules into the sheet', () => {
|
|
81
|
+
const Comp = styled('div')`
|
|
82
|
+
color: ${(p: any) => p.$color};
|
|
83
|
+
`
|
|
84
|
+
const vnode1 = Comp({ $color: 'red' }) as VNode
|
|
75
85
|
const cls1 = vnode1.props.class as string
|
|
76
86
|
|
|
77
|
-
const vnode2 = Comp({ $color:
|
|
87
|
+
const vnode2 = Comp({ $color: 'green' }) as VNode
|
|
78
88
|
const cls2 = vnode2.props.class as string
|
|
79
89
|
|
|
80
90
|
expect(cls1).not.toBe(cls2)
|
|
81
91
|
|
|
82
92
|
// Both rules should be in the sheet
|
|
83
|
-
expect(findRulesFor(cls1).some((r) => r.includes(
|
|
84
|
-
expect(findRulesFor(cls2).some((r) => r.includes(
|
|
93
|
+
expect(findRulesFor(cls1).some((r) => r.includes('color: red'))).toBe(true)
|
|
94
|
+
expect(findRulesFor(cls2).some((r) => r.includes('color: green'))).toBe(true)
|
|
85
95
|
})
|
|
86
96
|
})
|
|
87
97
|
|
|
88
|
-
describe(
|
|
89
|
-
it(
|
|
98
|
+
describe('createGlobalStyle', () => {
|
|
99
|
+
it('static global styles are injected into the shared sheet', () => {
|
|
90
100
|
const GlobalStyle = createGlobalStyle`
|
|
91
101
|
body { margin: 0; }
|
|
92
102
|
`
|
|
@@ -94,46 +104,54 @@ describe("hybrid injection — CSS in shared sheet", () => {
|
|
|
94
104
|
GlobalStyle({})
|
|
95
105
|
|
|
96
106
|
const rules = getSheetRules()
|
|
97
|
-
expect(rules.some((r) => r.includes(
|
|
107
|
+
expect(rules.some((r) => r.includes('margin') && r.includes('0'))).toBe(true)
|
|
98
108
|
})
|
|
99
109
|
})
|
|
100
110
|
})
|
|
101
111
|
|
|
102
|
-
describe(
|
|
112
|
+
describe('hybrid injection — VNode output (no <style> in tree)', () => {
|
|
103
113
|
afterEach(() => {
|
|
104
114
|
sheet.clearAll()
|
|
105
115
|
})
|
|
106
116
|
|
|
107
|
-
describe(
|
|
108
|
-
it(
|
|
109
|
-
const Comp = styled(
|
|
117
|
+
describe('styled components', () => {
|
|
118
|
+
it('static component returns a VNode of the correct tag', () => {
|
|
119
|
+
const Comp = styled('div')`
|
|
120
|
+
color: red;
|
|
121
|
+
`
|
|
110
122
|
const vnode = Comp({}) as VNode
|
|
111
123
|
|
|
112
124
|
// Should return a VNode for <div>, not a <style>
|
|
113
|
-
expect(vnode.type).toBe(
|
|
125
|
+
expect(vnode.type).toBe('div')
|
|
114
126
|
})
|
|
115
127
|
|
|
116
|
-
it(
|
|
117
|
-
const Comp = styled(
|
|
118
|
-
|
|
128
|
+
it('dynamic component returns a VNode of the correct tag', () => {
|
|
129
|
+
const Comp = styled('div')`
|
|
130
|
+
color: ${(p: any) => p.$color};
|
|
131
|
+
`
|
|
132
|
+
const vnode = Comp({ $color: 'red' }) as VNode
|
|
119
133
|
|
|
120
|
-
expect(vnode.type).toBe(
|
|
134
|
+
expect(vnode.type).toBe('div')
|
|
121
135
|
})
|
|
122
136
|
|
|
123
|
-
it(
|
|
124
|
-
const A = styled(
|
|
125
|
-
|
|
137
|
+
it('multiple styled components produce correct VNode types', () => {
|
|
138
|
+
const A = styled('div')`
|
|
139
|
+
color: red;
|
|
140
|
+
`
|
|
141
|
+
const B = styled('span')`
|
|
142
|
+
color: blue;
|
|
143
|
+
`
|
|
126
144
|
|
|
127
145
|
const vnodeA = A({}) as VNode
|
|
128
146
|
const vnodeB = B({}) as VNode
|
|
129
147
|
|
|
130
|
-
expect(vnodeA.type).toBe(
|
|
131
|
-
expect(vnodeB.type).toBe(
|
|
148
|
+
expect(vnodeA.type).toBe('div')
|
|
149
|
+
expect(vnodeB.type).toBe('span')
|
|
132
150
|
})
|
|
133
151
|
})
|
|
134
152
|
|
|
135
|
-
describe(
|
|
136
|
-
it(
|
|
153
|
+
describe('createGlobalStyle', () => {
|
|
154
|
+
it('static global style returns null', () => {
|
|
137
155
|
const GlobalStyle = createGlobalStyle`body { margin: 0; }`
|
|
138
156
|
const result = GlobalStyle({})
|
|
139
157
|
|
|
@@ -142,13 +160,15 @@ describe("hybrid injection — VNode output (no <style> in tree)", () => {
|
|
|
142
160
|
})
|
|
143
161
|
})
|
|
144
162
|
|
|
145
|
-
describe(
|
|
163
|
+
describe('hybrid injection — boost option at component level', () => {
|
|
146
164
|
afterEach(() => {
|
|
147
165
|
sheet.clearAll()
|
|
148
166
|
})
|
|
149
167
|
|
|
150
|
-
it(
|
|
151
|
-
const Comp = styled(
|
|
168
|
+
it('static boosted component produces doubled selector in CSSOM', () => {
|
|
169
|
+
const Comp = styled('div', { boost: true })`
|
|
170
|
+
color: red;
|
|
171
|
+
`
|
|
152
172
|
const vnode = Comp({}) as VNode
|
|
153
173
|
const className = vnode.props.class as string
|
|
154
174
|
|
|
@@ -158,11 +178,11 @@ describe("hybrid injection — boost option at component level", () => {
|
|
|
158
178
|
expect(rules.some((r) => r.includes(`.${className}.${className}`))).toBe(true)
|
|
159
179
|
})
|
|
160
180
|
|
|
161
|
-
it(
|
|
162
|
-
const Comp = styled(
|
|
181
|
+
it('dynamic boosted component produces doubled selector in CSSOM', () => {
|
|
182
|
+
const Comp = styled('div', { boost: true })`
|
|
163
183
|
color: ${(p: any) => p.$color};
|
|
164
184
|
`
|
|
165
|
-
const vnode = Comp({ $color:
|
|
185
|
+
const vnode = Comp({ $color: 'blue' }) as VNode
|
|
166
186
|
const className = vnode.props.class as string
|
|
167
187
|
|
|
168
188
|
const rules = findRulesFor(className)
|
|
@@ -170,8 +190,10 @@ describe("hybrid injection — boost option at component level", () => {
|
|
|
170
190
|
expect(rules.some((r) => r.includes(`.${className}.${className}`))).toBe(true)
|
|
171
191
|
})
|
|
172
192
|
|
|
173
|
-
it(
|
|
174
|
-
const Comp = styled(
|
|
193
|
+
it('non-boosted component produces single selector', () => {
|
|
194
|
+
const Comp = styled('div')`
|
|
195
|
+
color: green;
|
|
196
|
+
`
|
|
175
197
|
const vnode = Comp({}) as VNode
|
|
176
198
|
const className = vnode.props.class as string
|
|
177
199
|
|
|
@@ -181,15 +203,17 @@ describe("hybrid injection — boost option at component level", () => {
|
|
|
181
203
|
const baseRule = rules[0] as string
|
|
182
204
|
expect(baseRule).toContain(`.${className}`)
|
|
183
205
|
// Count occurrences of the className in the selector portion
|
|
184
|
-
const selectorPart = baseRule.split(
|
|
206
|
+
const selectorPart = baseRule.split('{')[0] as string
|
|
185
207
|
const occurrences = selectorPart.split(`.${className}`).length - 1
|
|
186
208
|
expect(occurrences).toBe(1)
|
|
187
209
|
})
|
|
188
210
|
|
|
189
|
-
it(
|
|
190
|
-
const Comp = styled(
|
|
211
|
+
it('boosted component with @media splits correctly', () => {
|
|
212
|
+
const Comp = styled('div', { boost: true })`
|
|
191
213
|
color: red;
|
|
192
|
-
@media (min-width: 768px) {
|
|
214
|
+
@media (min-width: 768px) {
|
|
215
|
+
font-size: 20px;
|
|
216
|
+
}
|
|
193
217
|
`
|
|
194
218
|
const vnode = Comp({}) as VNode
|
|
195
219
|
const className = vnode.props.class as string
|
package/src/__tests__/index.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Test barrel / helper file.
|
|
3
3
|
* Re-exports commonly used test utilities from the styler source.
|
|
4
4
|
*/
|
|
5
|
-
export { css } from
|
|
6
|
-
export { createGlobalStyle } from
|
|
7
|
-
export { HASH_INIT, hash, hashFinalize, hashUpdate } from
|
|
8
|
-
export { keyframes } from
|
|
9
|
-
export type { CSSResult, Interpolation } from
|
|
10
|
-
export { clearNormCache, normalizeCSS, resolve, resolveValue } from
|
|
11
|
-
export type { StyleSheetOptions } from
|
|
12
|
-
export { createSheet, StyleSheet, sheet } from
|
|
13
|
-
export { styled } from
|
|
14
|
-
export { ThemeContext, ThemeProvider, useTheme } from
|
|
5
|
+
export { css } from '../css'
|
|
6
|
+
export { createGlobalStyle } from '../globalStyle'
|
|
7
|
+
export { HASH_INIT, hash, hashFinalize, hashUpdate } from '../hash'
|
|
8
|
+
export { keyframes } from '../keyframes'
|
|
9
|
+
export type { CSSResult, Interpolation } from '../resolve'
|
|
10
|
+
export { clearNormCache, normalizeCSS, resolve, resolveValue } from '../resolve'
|
|
11
|
+
export type { StyleSheetOptions } from '../sheet'
|
|
12
|
+
export { createSheet, StyleSheet, sheet } from '../sheet'
|
|
13
|
+
export { styled } from '../styled'
|
|
14
|
+
export { ThemeContext, ThemeProvider, useTheme } from '../ThemeProvider'
|
|
@@ -1,36 +1,42 @@
|
|
|
1
|
-
import type { VNode } from
|
|
2
|
-
import { describe, expect, it } from
|
|
3
|
-
import { styled } from
|
|
4
|
-
|
|
5
|
-
describe(
|
|
6
|
-
describe(
|
|
7
|
-
it(
|
|
8
|
-
const Comp = styled(
|
|
1
|
+
import type { VNode } from '@pyreon/core'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { styled } from '../styled'
|
|
4
|
+
|
|
5
|
+
describe('style injection (className generation)', () => {
|
|
6
|
+
describe('dynamic styled components', () => {
|
|
7
|
+
it('generates a proper className for dynamic CSS', () => {
|
|
8
|
+
const Comp = styled('div')`
|
|
9
9
|
color: ${(props: any) => props.$color};
|
|
10
10
|
`
|
|
11
11
|
|
|
12
|
-
const vnode = Comp({ $color:
|
|
12
|
+
const vnode = Comp({ $color: 'red' }) as VNode
|
|
13
13
|
|
|
14
14
|
// Class should be present and properly generated
|
|
15
15
|
expect(vnode.props.class).toMatch(/^pyr-[0-9a-z]+$/)
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
it(
|
|
19
|
-
const Comp1 = styled(
|
|
20
|
-
|
|
18
|
+
it('works with multiple dynamic components', () => {
|
|
19
|
+
const Comp1 = styled('div')`
|
|
20
|
+
color: ${(p: any) => p.$c};
|
|
21
|
+
`
|
|
22
|
+
const Comp2 = styled('span')`
|
|
23
|
+
font-size: ${(p: any) => p.$s};
|
|
24
|
+
`
|
|
21
25
|
|
|
22
|
-
const vnode1 = Comp1({ $c:
|
|
23
|
-
const vnode2 = Comp2({ $s:
|
|
26
|
+
const vnode1 = Comp1({ $c: 'red' }) as VNode
|
|
27
|
+
const vnode2 = Comp2({ $s: '16px' }) as VNode
|
|
24
28
|
|
|
25
29
|
expect(vnode1.props.class).toMatch(/^pyr-/)
|
|
26
30
|
expect(vnode2.props.class).toMatch(/^pyr-/)
|
|
27
31
|
expect(vnode1.props.class).not.toBe(vnode2.props.class)
|
|
28
32
|
})
|
|
29
33
|
|
|
30
|
-
it(
|
|
31
|
-
const Comp = styled(
|
|
34
|
+
it('handles different prop values producing different classNames', () => {
|
|
35
|
+
const Comp = styled('div')`
|
|
36
|
+
color: ${(p: any) => p.$color};
|
|
37
|
+
`
|
|
32
38
|
|
|
33
|
-
const colors = [
|
|
39
|
+
const colors = ['blue', 'green', 'yellow', 'purple', 'orange']
|
|
34
40
|
const classNames = new Set<string>()
|
|
35
41
|
|
|
36
42
|
for (const color of colors) {
|
|
@@ -43,15 +49,17 @@ describe("style injection (className generation)", () => {
|
|
|
43
49
|
expect(classNames.size).toBe(colors.length)
|
|
44
50
|
})
|
|
45
51
|
|
|
46
|
-
it(
|
|
47
|
-
const Comp = styled(
|
|
52
|
+
it('same dynamic CSS produces same className', () => {
|
|
53
|
+
const Comp = styled('div')`
|
|
54
|
+
color: ${(p: any) => p.$color};
|
|
55
|
+
`
|
|
48
56
|
|
|
49
|
-
const vnode1 = Comp({ $color:
|
|
57
|
+
const vnode1 = Comp({ $color: 'red' }) as VNode
|
|
50
58
|
const cls1 = vnode1.props.class as string
|
|
51
59
|
|
|
52
|
-
const _vnode2 = Comp({ $color:
|
|
60
|
+
const _vnode2 = Comp({ $color: 'blue' }) as VNode
|
|
53
61
|
|
|
54
|
-
const vnode3 = Comp({ $color:
|
|
62
|
+
const vnode3 = Comp({ $color: 'red' }) as VNode // back to red
|
|
55
63
|
const cls3 = vnode3.props.class as string
|
|
56
64
|
|
|
57
65
|
// Same resolved CSS -> same className
|
|
@@ -59,18 +67,23 @@ describe("style injection (className generation)", () => {
|
|
|
59
67
|
})
|
|
60
68
|
})
|
|
61
69
|
|
|
62
|
-
describe(
|
|
63
|
-
it(
|
|
70
|
+
describe('static styled components', () => {
|
|
71
|
+
it('static components compute class at creation time', () => {
|
|
64
72
|
// Static components compute class at creation time
|
|
65
|
-
const Comp = styled(
|
|
73
|
+
const Comp = styled('div')`
|
|
74
|
+
display: flex;
|
|
75
|
+
color: red;
|
|
76
|
+
`
|
|
66
77
|
|
|
67
78
|
const vnode = Comp({}) as VNode
|
|
68
79
|
|
|
69
80
|
expect(vnode.props.class).toMatch(/^pyr-[0-9a-z]+$/)
|
|
70
81
|
})
|
|
71
82
|
|
|
72
|
-
it(
|
|
73
|
-
const Comp = styled(
|
|
83
|
+
it('static className is stable across calls', () => {
|
|
84
|
+
const Comp = styled('div')`
|
|
85
|
+
display: flex;
|
|
86
|
+
`
|
|
74
87
|
|
|
75
88
|
const vnode1 = Comp({}) as VNode
|
|
76
89
|
const cls1 = vnode1.props.class as string
|
|
@@ -83,19 +96,19 @@ describe("style injection (className generation)", () => {
|
|
|
83
96
|
})
|
|
84
97
|
})
|
|
85
98
|
|
|
86
|
-
describe(
|
|
87
|
-
it(
|
|
99
|
+
describe('theme-dependent components', () => {
|
|
100
|
+
it('produces different className for different resolved CSS', () => {
|
|
88
101
|
// In Pyreon, theme is accessed via useTheme() inside the component.
|
|
89
102
|
// Without ThemeProvider context, theme is {} (default).
|
|
90
103
|
// We test via direct prop-based dynamic interpolation instead.
|
|
91
|
-
const Comp = styled(
|
|
104
|
+
const Comp = styled('div')`
|
|
92
105
|
background: ${(p: any) => p.$bg};
|
|
93
106
|
`
|
|
94
107
|
|
|
95
|
-
const vnode1 = Comp({ $bg:
|
|
108
|
+
const vnode1 = Comp({ $bg: 'white' }) as VNode
|
|
96
109
|
const cls1 = vnode1.props.class as string
|
|
97
110
|
|
|
98
|
-
const vnode2 = Comp({ $bg:
|
|
111
|
+
const vnode2 = Comp({ $bg: 'black' }) as VNode
|
|
99
112
|
const cls2 = vnode2.props.class as string
|
|
100
113
|
|
|
101
114
|
expect(cls1).not.toBe(cls2)
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import type { VNode } from
|
|
2
|
-
import { describe, expect, it } from
|
|
3
|
-
import { css } from
|
|
4
|
-
import { styled } from
|
|
5
|
-
|
|
6
|
-
describe(
|
|
7
|
-
describe(
|
|
8
|
-
it(
|
|
9
|
-
const flexCSS = css`
|
|
10
|
-
|
|
1
|
+
import type { VNode } from '@pyreon/core'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { css } from '../css'
|
|
4
|
+
import { styled } from '../styled'
|
|
5
|
+
|
|
6
|
+
describe('integration', () => {
|
|
7
|
+
describe('nested css results', () => {
|
|
8
|
+
it('resolves nested css tagged templates', () => {
|
|
9
|
+
const flexCSS = css`
|
|
10
|
+
display: flex;
|
|
11
|
+
`
|
|
12
|
+
const Comp = styled('div')`
|
|
11
13
|
${flexCSS}
|
|
12
14
|
color: red;
|
|
13
15
|
`
|
|
@@ -15,20 +17,26 @@ describe("integration", () => {
|
|
|
15
17
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
16
18
|
})
|
|
17
19
|
|
|
18
|
-
it(
|
|
20
|
+
it('resolves conditional css (logical AND pattern)', () => {
|
|
19
21
|
const isWeb = true
|
|
20
|
-
const Comp = styled(
|
|
21
|
-
${isWeb &&
|
|
22
|
+
const Comp = styled('div')`
|
|
23
|
+
${isWeb &&
|
|
24
|
+
css`
|
|
25
|
+
box-sizing: border-box;
|
|
26
|
+
`};
|
|
22
27
|
display: flex;
|
|
23
28
|
`
|
|
24
29
|
const vnode = Comp({}) as VNode
|
|
25
30
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
26
31
|
})
|
|
27
32
|
|
|
28
|
-
it(
|
|
33
|
+
it('handles false conditional css', () => {
|
|
29
34
|
const isWeb = false
|
|
30
|
-
const Comp = styled(
|
|
31
|
-
${isWeb &&
|
|
35
|
+
const Comp = styled('div')`
|
|
36
|
+
${isWeb &&
|
|
37
|
+
css`
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
`};
|
|
32
40
|
display: flex;
|
|
33
41
|
`
|
|
34
42
|
const vnode = Comp({}) as VNode
|
|
@@ -36,16 +44,26 @@ describe("integration", () => {
|
|
|
36
44
|
})
|
|
37
45
|
})
|
|
38
46
|
|
|
39
|
-
describe(
|
|
40
|
-
it(
|
|
47
|
+
describe('array interpolations (makeItResponsive pattern)', () => {
|
|
48
|
+
it('resolves array of css results (simulating breakpoints)', () => {
|
|
41
49
|
// Simulates what makeItResponsive returns: array of css results per breakpoint
|
|
42
50
|
const breakpointStyles = [
|
|
43
|
-
css`
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
css`
|
|
52
|
+
color: red;
|
|
53
|
+
`,
|
|
54
|
+
css`
|
|
55
|
+
@media (min-width: 48em) {
|
|
56
|
+
color: blue;
|
|
57
|
+
}
|
|
58
|
+
`,
|
|
59
|
+
css`
|
|
60
|
+
@media (min-width: 62em) {
|
|
61
|
+
color: green;
|
|
62
|
+
}
|
|
63
|
+
`,
|
|
46
64
|
]
|
|
47
65
|
|
|
48
|
-
const Comp = styled(
|
|
66
|
+
const Comp = styled('div')`
|
|
49
67
|
display: flex;
|
|
50
68
|
${breakpointStyles};
|
|
51
69
|
`
|
|
@@ -53,41 +71,53 @@ describe("integration", () => {
|
|
|
53
71
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
54
72
|
})
|
|
55
73
|
|
|
56
|
-
it(
|
|
74
|
+
it('resolves function returning array (makeItResponsive full pattern)', () => {
|
|
57
75
|
// makeItResponsive returns a function (props) => CSSResult[]
|
|
58
76
|
const responsiveFn = (props: any) => {
|
|
59
77
|
const theme = props.$element || {}
|
|
60
78
|
return [
|
|
61
|
-
css`
|
|
62
|
-
|
|
79
|
+
css`
|
|
80
|
+
color: ${theme.color || 'black'};
|
|
81
|
+
`,
|
|
82
|
+
theme.breakpoint
|
|
83
|
+
? css`
|
|
84
|
+
@media (min-width: 48em) {
|
|
85
|
+
color: ${theme.breakpoint};
|
|
86
|
+
}
|
|
87
|
+
`
|
|
88
|
+
: '',
|
|
63
89
|
]
|
|
64
90
|
}
|
|
65
91
|
|
|
66
|
-
const Comp = styled(
|
|
92
|
+
const Comp = styled('div')`
|
|
67
93
|
display: flex;
|
|
68
94
|
${responsiveFn};
|
|
69
95
|
`
|
|
70
|
-
const vnode = Comp({ $element: { color:
|
|
96
|
+
const vnode = Comp({ $element: { color: 'red', breakpoint: 'blue' } }) as VNode
|
|
71
97
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
72
98
|
})
|
|
73
99
|
})
|
|
74
100
|
|
|
75
|
-
describe(
|
|
76
|
-
it(
|
|
101
|
+
describe('createMediaQueries pattern', () => {
|
|
102
|
+
it('css called as function (css(...args)) wrapping in @media', () => {
|
|
77
103
|
// Simulates createMediaQueries: builds functions that call css(...args)
|
|
78
104
|
const createMedia = (breakpoint: number, rootSize: number) => {
|
|
79
105
|
const emSize = breakpoint / rootSize
|
|
80
106
|
return (...args: any[]) =>
|
|
81
|
-
css
|
|
82
|
-
|
|
83
|
-
|
|
107
|
+
css`
|
|
108
|
+
@media only screen and (min-width: ${emSize}em) {
|
|
109
|
+
${css(...(args as [TemplateStringsArray, ...any[]]))};
|
|
110
|
+
}
|
|
111
|
+
`
|
|
84
112
|
}
|
|
85
113
|
|
|
86
114
|
const md = createMedia(768, 16)
|
|
87
|
-
const result = md`
|
|
115
|
+
const result = md`
|
|
116
|
+
color: blue;
|
|
117
|
+
`
|
|
88
118
|
|
|
89
119
|
// Wrap in a styled component
|
|
90
|
-
const Comp = styled(
|
|
120
|
+
const Comp = styled('div')`
|
|
91
121
|
color: red;
|
|
92
122
|
${result};
|
|
93
123
|
`
|
|
@@ -95,13 +125,13 @@ describe("integration", () => {
|
|
|
95
125
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
96
126
|
})
|
|
97
127
|
|
|
98
|
-
it(
|
|
128
|
+
it('zero-breakpoint passthrough (css(...args) without @media)', () => {
|
|
99
129
|
// Breakpoint 0 means no @media wrapper
|
|
100
130
|
const passthrough = (...args: any[]) => css(...(args as [TemplateStringsArray, ...any[]]))
|
|
101
131
|
|
|
102
132
|
const result = passthrough`color: red;`
|
|
103
133
|
|
|
104
|
-
const Comp = styled(
|
|
134
|
+
const Comp = styled('div')`
|
|
105
135
|
${result};
|
|
106
136
|
`
|
|
107
137
|
const vnode = Comp({}) as VNode
|
|
@@ -109,21 +139,21 @@ describe("integration", () => {
|
|
|
109
139
|
})
|
|
110
140
|
})
|
|
111
141
|
|
|
112
|
-
describe(
|
|
113
|
-
it(
|
|
114
|
-
const Comp = styled(
|
|
142
|
+
describe('complex styled patterns', () => {
|
|
143
|
+
it('function interpolation with prop-based conditional', () => {
|
|
144
|
+
const Comp = styled('div')`
|
|
115
145
|
display: flex;
|
|
116
|
-
${({ $contentType }: any) => $contentType ===
|
|
146
|
+
${({ $contentType }: any) => $contentType === 'content' && 'flex: 1;'};
|
|
117
147
|
`
|
|
118
|
-
const vnode = Comp({ $contentType:
|
|
148
|
+
const vnode = Comp({ $contentType: 'content' }) as VNode
|
|
119
149
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
120
150
|
})
|
|
121
151
|
|
|
122
|
-
it(
|
|
152
|
+
it('platform-specific CSS (compile-time constant)', () => {
|
|
123
153
|
const __WEB__ = true
|
|
124
|
-
const platformCSS = __WEB__ ?
|
|
154
|
+
const platformCSS = __WEB__ ? 'box-sizing: border-box;' : ''
|
|
125
155
|
|
|
126
|
-
const Comp = styled(
|
|
156
|
+
const Comp = styled('div')`
|
|
127
157
|
${platformCSS};
|
|
128
158
|
display: flex;
|
|
129
159
|
`
|
|
@@ -131,17 +161,17 @@ describe("integration", () => {
|
|
|
131
161
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
132
162
|
})
|
|
133
163
|
|
|
134
|
-
it(
|
|
135
|
-
const Comp = styled(
|
|
136
|
-
color: ${(p: any) => p.$color ||
|
|
137
|
-
font-size: ${(p: any) => p.$size ||
|
|
164
|
+
it('multiple function interpolations', () => {
|
|
165
|
+
const Comp = styled('div')`
|
|
166
|
+
color: ${(p: any) => p.$color || 'black'};
|
|
167
|
+
font-size: ${(p: any) => p.$size || '16px'};
|
|
138
168
|
`
|
|
139
|
-
const vnode = Comp({ $color:
|
|
169
|
+
const vnode = Comp({ $color: 'red', $size: '20px' }) as VNode
|
|
140
170
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
141
171
|
})
|
|
142
172
|
|
|
143
|
-
it(
|
|
144
|
-
const Comp = styled(
|
|
173
|
+
it('empty template produces no className', () => {
|
|
174
|
+
const Comp = styled('div')``
|
|
145
175
|
const vnode = Comp({}) as VNode
|
|
146
176
|
expect(vnode.props.class).toBeFalsy()
|
|
147
177
|
})
|