@pyreon/styler 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 +5 -7
- package/src/ThemeProvider.ts +0 -65
- package/src/__tests__/ThemeProvider.test.ts +0 -67
- package/src/__tests__/benchmark.bench.ts +0 -200
- package/src/__tests__/composition-chain.test.ts +0 -537
- package/src/__tests__/css.test.ts +0 -70
- package/src/__tests__/dev-gate-treeshake.test.ts +0 -85
- package/src/__tests__/forward.test.ts +0 -282
- package/src/__tests__/globalStyle.test.ts +0 -72
- package/src/__tests__/hash.test.ts +0 -70
- package/src/__tests__/hybrid-injection.test.ts +0 -225
- package/src/__tests__/index.ts +0 -14
- package/src/__tests__/inject-rules.browser.test.ts +0 -40
- package/src/__tests__/insertion-effect.test.ts +0 -119
- package/src/__tests__/integration-dom.test.ts +0 -58
- package/src/__tests__/integration.test.ts +0 -179
- package/src/__tests__/keyframes.test.ts +0 -68
- package/src/__tests__/memory-growth.test.ts +0 -220
- package/src/__tests__/native-marker.test.ts +0 -9
- package/src/__tests__/p3-features.test.ts +0 -316
- package/src/__tests__/resolve-cache.test.ts +0 -94
- package/src/__tests__/resolve.test.ts +0 -308
- package/src/__tests__/shared.test.ts +0 -133
- package/src/__tests__/sheet-advanced.test.ts +0 -659
- package/src/__tests__/sheet-split-atrules.test.ts +0 -410
- package/src/__tests__/sheet.test.ts +0 -250
- package/src/__tests__/static-styler-resolve-cost.test.ts +0 -160
- package/src/__tests__/styled-reactive.test.ts +0 -74
- package/src/__tests__/styled-ssr.test.ts +0 -75
- package/src/__tests__/styled.test.ts +0 -511
- package/src/__tests__/styler.browser.test.tsx +0 -194
- package/src/__tests__/theme.test.ts +0 -33
- package/src/__tests__/useCSS.test.ts +0 -172
- package/src/css.ts +0 -13
- package/src/env.d.ts +0 -6
- package/src/forward.ts +0 -308
- package/src/globalStyle.ts +0 -53
- package/src/hash.ts +0 -28
- package/src/index.ts +0 -15
- package/src/keyframes.ts +0 -36
- package/src/manifest.ts +0 -332
- package/src/resolve.ts +0 -225
- package/src/shared.ts +0 -22
- package/src/sheet.ts +0 -635
- package/src/styled.tsx +0 -503
- package/src/tests/manifest-snapshot.test.ts +0 -51
- package/src/useCSS.ts +0 -20
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { buildProps, filterProps } from '../forward'
|
|
3
|
-
|
|
4
|
-
describe('filterProps', () => {
|
|
5
|
-
describe('keeps standard HTML props', () => {
|
|
6
|
-
it('keeps id', () => {
|
|
7
|
-
const result = filterProps({ id: 'test' })
|
|
8
|
-
expect(result.id).toBe('test')
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it('keeps className', () => {
|
|
12
|
-
const result = filterProps({ className: 'foo' })
|
|
13
|
-
expect(result.className).toBe('foo')
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('keeps style', () => {
|
|
17
|
-
const style = { color: 'red' }
|
|
18
|
-
const result = filterProps({ style })
|
|
19
|
-
expect(result.style).toBe(style)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('keeps href', () => {
|
|
23
|
-
const result = filterProps({ href: '/path' })
|
|
24
|
-
expect(result.href).toBe('/path')
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('keeps disabled', () => {
|
|
28
|
-
const result = filterProps({ disabled: true })
|
|
29
|
-
expect(result.disabled).toBe(true)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('keeps multiple standard props at once', () => {
|
|
33
|
-
const result = filterProps({
|
|
34
|
-
id: 'main',
|
|
35
|
-
tabIndex: 0,
|
|
36
|
-
role: 'button',
|
|
37
|
-
title: 'Click me',
|
|
38
|
-
})
|
|
39
|
-
expect(result).toEqual({
|
|
40
|
-
id: 'main',
|
|
41
|
-
tabIndex: 0,
|
|
42
|
-
role: 'button',
|
|
43
|
-
title: 'Click me',
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
describe('keeps data-* attributes', () => {
|
|
49
|
-
it('keeps data-testid', () => {
|
|
50
|
-
const result = filterProps({ 'data-testid': 'hello' })
|
|
51
|
-
expect(result['data-testid']).toBe('hello')
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('keeps data-custom', () => {
|
|
55
|
-
const result = filterProps({ 'data-custom': 'value' })
|
|
56
|
-
expect(result['data-custom']).toBe('value')
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
describe('keeps aria-* attributes', () => {
|
|
61
|
-
it('keeps aria-label', () => {
|
|
62
|
-
const result = filterProps({ 'aria-label': 'Close' })
|
|
63
|
-
expect(result['aria-label']).toBe('Close')
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('keeps aria-hidden', () => {
|
|
67
|
-
const result = filterProps({ 'aria-hidden': true })
|
|
68
|
-
expect(result['aria-hidden']).toBe(true)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('keeps aria-describedby', () => {
|
|
72
|
-
const result = filterProps({ 'aria-describedby': 'desc' })
|
|
73
|
-
expect(result['aria-describedby']).toBe('desc')
|
|
74
|
-
})
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
describe('keeps event handlers', () => {
|
|
78
|
-
it('keeps onClick', () => {
|
|
79
|
-
const fn = () => undefined
|
|
80
|
-
const result = filterProps({ onClick: fn })
|
|
81
|
-
expect(result.onClick).toBe(fn)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('keeps onMouseEnter', () => {
|
|
85
|
-
const fn = () => undefined
|
|
86
|
-
const result = filterProps({ onMouseEnter: fn })
|
|
87
|
-
expect(result.onMouseEnter).toBe(fn)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('keeps onKeyDown', () => {
|
|
91
|
-
const fn = () => undefined
|
|
92
|
-
const result = filterProps({ onKeyDown: fn })
|
|
93
|
-
expect(result.onKeyDown).toBe(fn)
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
describe('strips $-prefixed transient props', () => {
|
|
98
|
-
it('strips $rocketstyle', () => {
|
|
99
|
-
const result = filterProps({ $rocketstyle: { color: 'red' } })
|
|
100
|
-
expect(result).toEqual({})
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('strips $element', () => {
|
|
104
|
-
const result = filterProps({ $element: 'button' })
|
|
105
|
-
expect(result).toEqual({})
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('strips $active', () => {
|
|
109
|
-
const result = filterProps({ $active: true })
|
|
110
|
-
expect(result).toEqual({})
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('strips multiple $-prefixed props while keeping valid ones', () => {
|
|
114
|
-
const result = filterProps({
|
|
115
|
-
$rocketstyle: {},
|
|
116
|
-
$element: 'div',
|
|
117
|
-
id: 'test',
|
|
118
|
-
'data-x': 'y',
|
|
119
|
-
})
|
|
120
|
-
expect(result).toEqual({ id: 'test', 'data-x': 'y' })
|
|
121
|
-
})
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
describe('strips as prop', () => {
|
|
125
|
-
it('strips the as prop', () => {
|
|
126
|
-
const result = filterProps({ as: 'button' })
|
|
127
|
-
expect(result).toEqual({})
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('strips as while keeping other props', () => {
|
|
131
|
-
const result = filterProps({ as: 'section', id: 'main' })
|
|
132
|
-
expect(result).toEqual({ id: 'main' })
|
|
133
|
-
})
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
describe('strips unknown props', () => {
|
|
137
|
-
it('strips customProp', () => {
|
|
138
|
-
const result = filterProps({ customProp: 'value' })
|
|
139
|
-
expect(result).toEqual({})
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('strips myThing', () => {
|
|
143
|
-
const result = filterProps({ myThing: 42 })
|
|
144
|
-
expect(result).toEqual({})
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('strips camelCase unknown props', () => {
|
|
148
|
-
const result = filterProps({ isActive: true, backgroundColor: 'red' })
|
|
149
|
-
expect(result).toEqual({})
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
it('returns empty object for all-unknown props', () => {
|
|
153
|
-
const result = filterProps({
|
|
154
|
-
foo: 1,
|
|
155
|
-
bar: 2,
|
|
156
|
-
baz: 3,
|
|
157
|
-
customThing: 'x',
|
|
158
|
-
})
|
|
159
|
-
expect(result).toEqual({})
|
|
160
|
-
})
|
|
161
|
-
})
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
describe('buildProps', () => {
|
|
165
|
-
describe('className merging', () => {
|
|
166
|
-
it('merges generatedCls with user className', () => {
|
|
167
|
-
const result = buildProps({ className: 'custom' }, 'pyr-abc', true)
|
|
168
|
-
expect(result.class).toBe('pyr-abc custom')
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('merges generatedCls with user class', () => {
|
|
172
|
-
const result = buildProps({ class: 'custom' }, 'pyr-abc', true)
|
|
173
|
-
expect(result.class).toBe('pyr-abc custom')
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('uses only generatedCls when no user className', () => {
|
|
177
|
-
const result = buildProps({}, 'pyr-abc', true)
|
|
178
|
-
expect(result.class).toBe('pyr-abc')
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('uses only user className when generatedCls is empty', () => {
|
|
182
|
-
const result = buildProps({ className: 'custom' }, '', true)
|
|
183
|
-
expect(result.class).toBe('custom')
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('sets no class when both are empty/missing', () => {
|
|
187
|
-
const result = buildProps({}, '', true)
|
|
188
|
-
expect(result.class).toBeUndefined()
|
|
189
|
-
})
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
describe('isDOM=false (component target)', () => {
|
|
193
|
-
it('forwards all props except as, className, class, and $-prefixed', () => {
|
|
194
|
-
const result = buildProps(
|
|
195
|
-
{
|
|
196
|
-
as: 'button',
|
|
197
|
-
className: 'user',
|
|
198
|
-
customProp: 'hello',
|
|
199
|
-
$rocketstyle: {},
|
|
200
|
-
$rocketstate: { hover: true },
|
|
201
|
-
'data-x': 'y',
|
|
202
|
-
onClick: () => undefined,
|
|
203
|
-
},
|
|
204
|
-
'pyr-abc',
|
|
205
|
-
false,
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
expect(result.customProp).toBe('hello')
|
|
209
|
-
expect(result.$rocketstyle).toBeUndefined()
|
|
210
|
-
expect(result.$rocketstate).toBeUndefined()
|
|
211
|
-
expect(result['data-x']).toBe('y')
|
|
212
|
-
expect(result.onClick).toBeDefined()
|
|
213
|
-
// as and className are not forwarded from rawProps (class is merged separately)
|
|
214
|
-
expect(result.as).toBeUndefined()
|
|
215
|
-
expect(result.class).toBe('pyr-abc user')
|
|
216
|
-
})
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
describe('isDOM=true (default filtering)', () => {
|
|
220
|
-
it('filters unknown DOM props', () => {
|
|
221
|
-
const result = buildProps({ customProp: 'hello', unknownThing: 42 }, 'pyr-abc', true)
|
|
222
|
-
expect(result.customProp).toBeUndefined()
|
|
223
|
-
expect(result.unknownThing).toBeUndefined()
|
|
224
|
-
expect(result.class).toBe('pyr-abc')
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
it('strips $-prefixed props', () => {
|
|
228
|
-
const result = buildProps({ $rocketstyle: {}, $active: true, id: 'test' }, 'pyr-abc', true)
|
|
229
|
-
expect(result.$rocketstyle).toBeUndefined()
|
|
230
|
-
expect(result.$active).toBeUndefined()
|
|
231
|
-
expect(result.id).toBe('test')
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
it('keeps data-* and aria-* attributes', () => {
|
|
235
|
-
const result = buildProps({ 'data-testid': 'btn', 'aria-label': 'Close' }, 'pyr-abc', true)
|
|
236
|
-
expect(result['data-testid']).toBe('btn')
|
|
237
|
-
expect(result['aria-label']).toBe('Close')
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
it('keeps standard HTML attributes', () => {
|
|
241
|
-
const result = buildProps({ id: 'main', disabled: true, tabIndex: 0 }, 'pyr-abc', true)
|
|
242
|
-
expect(result.id).toBe('main')
|
|
243
|
-
expect(result.disabled).toBe(true)
|
|
244
|
-
expect(result.tabIndex).toBe(0)
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
it('strips as prop', () => {
|
|
248
|
-
const result = buildProps({ as: 'section' }, 'pyr-abc', true)
|
|
249
|
-
expect(result.as).toBeUndefined()
|
|
250
|
-
})
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
describe('isDOM=true with customFilter', () => {
|
|
254
|
-
it('uses customFilter to decide which props to forward', () => {
|
|
255
|
-
const customFilter = (prop: string) => prop.startsWith('my')
|
|
256
|
-
const result = buildProps(
|
|
257
|
-
{ myProp: 'yes', otherProp: 'no', id: 'skip' },
|
|
258
|
-
'pyr-abc',
|
|
259
|
-
true,
|
|
260
|
-
customFilter,
|
|
261
|
-
)
|
|
262
|
-
expect(result.myProp).toBe('yes')
|
|
263
|
-
expect(result.otherProp).toBeUndefined()
|
|
264
|
-
expect(result.id).toBeUndefined()
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
it('customFilter still skips as and className from rawProps', () => {
|
|
268
|
-
const customFilter = () => true
|
|
269
|
-
const result = buildProps(
|
|
270
|
-
{ as: 'button', className: 'user', id: 'test' },
|
|
271
|
-
'pyr-abc',
|
|
272
|
-
true,
|
|
273
|
-
customFilter,
|
|
274
|
-
)
|
|
275
|
-
// as is always skipped
|
|
276
|
-
expect(result.as).toBeUndefined()
|
|
277
|
-
// class is merged, not forwarded from rawProps
|
|
278
|
-
expect(result.class).toBe('pyr-abc user')
|
|
279
|
-
expect(result.id).toBe('test')
|
|
280
|
-
})
|
|
281
|
-
})
|
|
282
|
-
})
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
-
import { createGlobalStyle } from '../globalStyle'
|
|
3
|
-
import { sheet } from '../sheet'
|
|
4
|
-
|
|
5
|
-
describe('createGlobalStyle -- empty CSS paths', () => {
|
|
6
|
-
afterEach(() => {
|
|
7
|
-
sheet.clearAll()
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it('static: returns null for empty template', () => {
|
|
11
|
-
const GlobalStyle = createGlobalStyle``
|
|
12
|
-
const result = GlobalStyle({})
|
|
13
|
-
expect(result).toBeNull()
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('static: returns null for whitespace-only template', () => {
|
|
17
|
-
const GlobalStyle = createGlobalStyle` `
|
|
18
|
-
const result = GlobalStyle({})
|
|
19
|
-
expect(result).toBeNull()
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('dynamic: returns null when interpolation resolves to empty CSS', () => {
|
|
23
|
-
const GlobalStyle = createGlobalStyle`${({ theme }: any) => (theme.empty ? '' : '')}`
|
|
24
|
-
const result = GlobalStyle({})
|
|
25
|
-
expect(result).toBeNull()
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('dynamic: returns null when interpolation resolves to whitespace', () => {
|
|
29
|
-
const GlobalStyle = createGlobalStyle`${() => ' '}`
|
|
30
|
-
const result = GlobalStyle({})
|
|
31
|
-
expect(result).toBeNull()
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
describe('createGlobalStyle', () => {
|
|
36
|
-
afterEach(() => {
|
|
37
|
-
sheet.clearAll()
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('returns a component function', () => {
|
|
41
|
-
const GlobalStyle = createGlobalStyle`
|
|
42
|
-
body { margin: 0; }
|
|
43
|
-
`
|
|
44
|
-
expect(typeof GlobalStyle).toBe('function')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('renders nothing (returns null)', () => {
|
|
48
|
-
const GlobalStyle = createGlobalStyle`
|
|
49
|
-
body { margin: 0; padding: 0; }
|
|
50
|
-
`
|
|
51
|
-
const result = GlobalStyle({})
|
|
52
|
-
expect(result).toBeNull()
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('handles dynamic interpolations with theme', () => {
|
|
56
|
-
// Dynamic path: function interpolation causes per-render resolution
|
|
57
|
-
const GlobalStyle = createGlobalStyle`
|
|
58
|
-
body { font-family: ${({ theme }: any) => theme?.font ?? 'sans-serif'}; }
|
|
59
|
-
`
|
|
60
|
-
const result = GlobalStyle({})
|
|
61
|
-
expect(result).toBeNull()
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('handles static interpolations', () => {
|
|
65
|
-
const color = 'red'
|
|
66
|
-
const GlobalStyle = createGlobalStyle`
|
|
67
|
-
body { color: ${color}; }
|
|
68
|
-
`
|
|
69
|
-
const result = GlobalStyle({})
|
|
70
|
-
expect(result).toBeNull()
|
|
71
|
-
})
|
|
72
|
-
})
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { hash } from '../hash'
|
|
3
|
-
|
|
4
|
-
describe('hash', () => {
|
|
5
|
-
it('returns a string', () => {
|
|
6
|
-
expect(typeof hash('test')).toBe('string')
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
it('is deterministic — same input always produces same output', () => {
|
|
10
|
-
const input = 'display: flex; color: red;'
|
|
11
|
-
expect(hash(input)).toBe(hash(input))
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('produces different hashes for different inputs', () => {
|
|
15
|
-
expect(hash('color: red')).not.toBe(hash('color: blue'))
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('returns base-36 string (compact)', () => {
|
|
19
|
-
const result = hash('some css')
|
|
20
|
-
expect(result).toMatch(/^[0-9a-z]+$/)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('handles empty string', () => {
|
|
24
|
-
const result = hash('')
|
|
25
|
-
expect(typeof result).toBe('string')
|
|
26
|
-
expect(result.length).toBeGreaterThan(0)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('handles long CSS strings', () => {
|
|
30
|
-
const longCSS = 'display: flex; '.repeat(100)
|
|
31
|
-
const result = hash(longCSS)
|
|
32
|
-
expect(typeof result).toBe('string')
|
|
33
|
-
// base-36 uint32 is at most 7 chars
|
|
34
|
-
expect(result.length).toBeLessThan(10)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('handles special characters in CSS', () => {
|
|
38
|
-
const css = `@media (min-width: 48em) { .foo { content: "hello"; } }`
|
|
39
|
-
const result = hash(css)
|
|
40
|
-
expect(typeof result).toBe('string')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('produces consistent hash for FNV-1a offset basis on empty string', () => {
|
|
44
|
-
// Empty string: h stays at FNV_OFFSET = 2166136261, base36 = "zzzzzz" range
|
|
45
|
-
const result = hash('')
|
|
46
|
-
// Just verify it is stable
|
|
47
|
-
expect(result).toBe(hash(''))
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('handles unicode characters', () => {
|
|
51
|
-
const result = hash('content: "🎉";')
|
|
52
|
-
expect(typeof result).toBe('string')
|
|
53
|
-
expect(result.length).toBeGreaterThan(0)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('single character inputs produce distinct hashes', () => {
|
|
57
|
-
const hashes = new Set<string>()
|
|
58
|
-
for (let i = 0; i < 26; i++) {
|
|
59
|
-
hashes.add(hash(String.fromCharCode(97 + i)))
|
|
60
|
-
}
|
|
61
|
-
// All 26 lowercase letters should hash to unique values
|
|
62
|
-
expect(hashes.size).toBe(26)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('hash is unsigned 32-bit (no negative values)', () => {
|
|
66
|
-
// base-36 of a uint32 is always positive
|
|
67
|
-
const result = hash('test negative')
|
|
68
|
-
expect(Number.parseInt(result, 36)).toBeGreaterThanOrEqual(0)
|
|
69
|
-
})
|
|
70
|
-
})
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the hybrid injection approach:
|
|
3
|
-
* - Client (jsdom): shared <style data-pyreon-styler> sheet
|
|
4
|
-
* - CSS rules present in the CSSOM sheet after insertion
|
|
5
|
-
* - `layer` option threaded from styled() through to the sheet
|
|
6
|
-
*
|
|
7
|
-
* Ported to VNode-level testing: we call the component function directly
|
|
8
|
-
* and inspect the returned VNode + the sheet's CSSOM.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { VNode } from '@pyreon/core'
|
|
12
|
-
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
13
|
-
import { createGlobalStyle } from '../globalStyle'
|
|
14
|
-
import { sheet } from '../sheet'
|
|
15
|
-
import { styled } from '../styled'
|
|
16
|
-
|
|
17
|
-
/** Helper: collect all CSS rule texts from the shared <style data-pyreon-styler> sheet. */
|
|
18
|
-
const getSheetRules = (): string[] => {
|
|
19
|
-
const el = document.querySelector('style[data-pyreon-styler]') as HTMLStyleElement | null
|
|
20
|
-
if (!el?.sheet) return []
|
|
21
|
-
return Array.from(el.sheet.cssRules).map((r) => r.cssText)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Helper: find rules matching a className in the CSSOM. */
|
|
25
|
-
const findRulesFor = (className: string): string[] =>
|
|
26
|
-
getSheetRules().filter((r) => r.includes(`.${className}`))
|
|
27
|
-
|
|
28
|
-
describe('hybrid injection — CSS in shared sheet', () => {
|
|
29
|
-
afterEach(() => {
|
|
30
|
-
sheet.clearAll()
|
|
31
|
-
})
|
|
32
|
-
|
|
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
|
-
`
|
|
38
|
-
const vnode = Comp({}) as VNode
|
|
39
|
-
const className = vnode.props.class as string
|
|
40
|
-
|
|
41
|
-
const rules = findRulesFor(className)
|
|
42
|
-
expect(rules.length).toBeGreaterThanOrEqual(1)
|
|
43
|
-
expect(rules.some((r) => r.includes('color: red'))).toBe(true)
|
|
44
|
-
})
|
|
45
|
-
|
|
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
|
-
`
|
|
53
|
-
|
|
54
|
-
A({})
|
|
55
|
-
B({})
|
|
56
|
-
|
|
57
|
-
// Both should be in the same sheet
|
|
58
|
-
const styleEls = document.querySelectorAll('style[data-pyreon-styler]')
|
|
59
|
-
expect(styleEls.length).toBe(1)
|
|
60
|
-
|
|
61
|
-
const rules = getSheetRules()
|
|
62
|
-
expect(rules.some((r) => r.includes('color: red'))).toBe(true)
|
|
63
|
-
expect(rules.some((r) => r.includes('font-size: 20px'))).toBe(true)
|
|
64
|
-
})
|
|
65
|
-
})
|
|
66
|
-
|
|
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
|
|
73
|
-
const className = vnode.props.class as string
|
|
74
|
-
|
|
75
|
-
const rules = findRulesFor(className)
|
|
76
|
-
expect(rules.length).toBeGreaterThanOrEqual(1)
|
|
77
|
-
expect(rules.some((r) => r.includes('color: blue'))).toBe(true)
|
|
78
|
-
})
|
|
79
|
-
|
|
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
|
|
85
|
-
const cls1 = vnode1.props.class as string
|
|
86
|
-
|
|
87
|
-
const vnode2 = Comp({ $color: 'green' }) as VNode
|
|
88
|
-
const cls2 = vnode2.props.class as string
|
|
89
|
-
|
|
90
|
-
expect(cls1).not.toBe(cls2)
|
|
91
|
-
|
|
92
|
-
// Both rules should be in the sheet
|
|
93
|
-
expect(findRulesFor(cls1).some((r) => r.includes('color: red'))).toBe(true)
|
|
94
|
-
expect(findRulesFor(cls2).some((r) => r.includes('color: green'))).toBe(true)
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
describe('createGlobalStyle', () => {
|
|
99
|
-
it('static global styles are injected into the shared sheet', () => {
|
|
100
|
-
const GlobalStyle = createGlobalStyle`
|
|
101
|
-
body { margin: 0; }
|
|
102
|
-
`
|
|
103
|
-
// Static global styles are injected at creation time
|
|
104
|
-
GlobalStyle({})
|
|
105
|
-
|
|
106
|
-
const rules = getSheetRules()
|
|
107
|
-
expect(rules.some((r) => r.includes('margin') && r.includes('0'))).toBe(true)
|
|
108
|
-
})
|
|
109
|
-
})
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
describe('hybrid injection — VNode output (no <style> in tree)', () => {
|
|
113
|
-
afterEach(() => {
|
|
114
|
-
sheet.clearAll()
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
describe('styled components', () => {
|
|
118
|
-
it('static component returns a VNode of the correct tag', () => {
|
|
119
|
-
const Comp = styled('div')`
|
|
120
|
-
color: red;
|
|
121
|
-
`
|
|
122
|
-
const vnode = Comp({}) as VNode
|
|
123
|
-
|
|
124
|
-
// Should return a VNode for <div>, not a <style>
|
|
125
|
-
expect(vnode.type).toBe('div')
|
|
126
|
-
})
|
|
127
|
-
|
|
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
|
|
133
|
-
|
|
134
|
-
expect(vnode.type).toBe('div')
|
|
135
|
-
})
|
|
136
|
-
|
|
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
|
-
`
|
|
144
|
-
|
|
145
|
-
const vnodeA = A({}) as VNode
|
|
146
|
-
const vnodeB = B({}) as VNode
|
|
147
|
-
|
|
148
|
-
expect(vnodeA.type).toBe('div')
|
|
149
|
-
expect(vnodeB.type).toBe('span')
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
describe('createGlobalStyle', () => {
|
|
154
|
-
it('static global style returns null', () => {
|
|
155
|
-
const GlobalStyle = createGlobalStyle`body { margin: 0; }`
|
|
156
|
-
const result = GlobalStyle({})
|
|
157
|
-
|
|
158
|
-
expect(result).toBeNull()
|
|
159
|
-
})
|
|
160
|
-
})
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
describe('hybrid injection — layer option at component level', () => {
|
|
164
|
-
afterEach(() => {
|
|
165
|
-
sheet.clearAll()
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it('static layered component generates className', () => {
|
|
169
|
-
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
|
170
|
-
const Comp = styled('div', { layer: 'rocketstyle' })`
|
|
171
|
-
color: red;
|
|
172
|
-
`
|
|
173
|
-
const vnode = Comp({}) as VNode
|
|
174
|
-
const className = vnode.props.class as string
|
|
175
|
-
|
|
176
|
-
// className is generated regardless of CSSOM @layer support
|
|
177
|
-
expect(className).toMatch(/^pyr-/)
|
|
178
|
-
warnSpy.mockRestore()
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('dynamic layered component generates className', () => {
|
|
182
|
-
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
|
183
|
-
const Comp = styled('div', { layer: 'rocketstyle' })`
|
|
184
|
-
color: ${(p: any) => p.$color};
|
|
185
|
-
`
|
|
186
|
-
const vnode = Comp({ $color: 'blue' }) as VNode
|
|
187
|
-
const className = vnode.props.class as string
|
|
188
|
-
|
|
189
|
-
// className is generated regardless of CSSOM @layer support
|
|
190
|
-
expect(className).toMatch(/^pyr-/)
|
|
191
|
-
warnSpy.mockRestore()
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('default sheet component produces valid className and injects rule', () => {
|
|
195
|
-
const Comp = styled('div')`
|
|
196
|
-
color: green;
|
|
197
|
-
`
|
|
198
|
-
const vnode = Comp({}) as VNode
|
|
199
|
-
const className = vnode.props.class as string
|
|
200
|
-
|
|
201
|
-
expect(className).toMatch(/^pyr-/)
|
|
202
|
-
// In environments without @layer support (happy-dom), rules are inserted
|
|
203
|
-
// without layer wrapping. In real browsers, they'd be in @layer pyreon.
|
|
204
|
-
const rules = findRulesFor(className)
|
|
205
|
-
expect(rules.length).toBeGreaterThanOrEqual(1)
|
|
206
|
-
const baseRule = rules[0] as string
|
|
207
|
-
expect(baseRule).toContain(`.${className}`)
|
|
208
|
-
expect(baseRule).toContain('color: green')
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('layered component with @media generates className', () => {
|
|
212
|
-
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
|
213
|
-
const Comp = styled('div', { layer: 'rocketstyle' })`
|
|
214
|
-
color: red;
|
|
215
|
-
@media (min-width: 768px) {
|
|
216
|
-
font-size: 20px;
|
|
217
|
-
}
|
|
218
|
-
`
|
|
219
|
-
const vnode = Comp({}) as VNode
|
|
220
|
-
const className = vnode.props.class as string
|
|
221
|
-
|
|
222
|
-
expect(className).toMatch(/^pyr-/)
|
|
223
|
-
warnSpy.mockRestore()
|
|
224
|
-
})
|
|
225
|
-
})
|
package/src/__tests__/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test barrel / helper file.
|
|
3
|
-
* Re-exports commonly used test utilities from the styler source.
|
|
4
|
-
*/
|
|
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'
|