@pyreon/styler 0.11.5 → 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
|
@@ -1,282 +1,282 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { buildProps, filterProps } from
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { buildProps, filterProps } from '../forward'
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
describe(
|
|
6
|
-
it(
|
|
7
|
-
const result = filterProps({ id:
|
|
8
|
-
expect(result.id).toBe(
|
|
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
9
|
})
|
|
10
10
|
|
|
11
|
-
it(
|
|
12
|
-
const result = filterProps({ className:
|
|
13
|
-
expect(result.className).toBe(
|
|
11
|
+
it('keeps className', () => {
|
|
12
|
+
const result = filterProps({ className: 'foo' })
|
|
13
|
+
expect(result.className).toBe('foo')
|
|
14
14
|
})
|
|
15
15
|
|
|
16
|
-
it(
|
|
17
|
-
const style = { color:
|
|
16
|
+
it('keeps style', () => {
|
|
17
|
+
const style = { color: 'red' }
|
|
18
18
|
const result = filterProps({ style })
|
|
19
19
|
expect(result.style).toBe(style)
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
it(
|
|
23
|
-
const result = filterProps({ href:
|
|
24
|
-
expect(result.href).toBe(
|
|
22
|
+
it('keeps href', () => {
|
|
23
|
+
const result = filterProps({ href: '/path' })
|
|
24
|
+
expect(result.href).toBe('/path')
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
-
it(
|
|
27
|
+
it('keeps disabled', () => {
|
|
28
28
|
const result = filterProps({ disabled: true })
|
|
29
29
|
expect(result.disabled).toBe(true)
|
|
30
30
|
})
|
|
31
31
|
|
|
32
|
-
it(
|
|
32
|
+
it('keeps multiple standard props at once', () => {
|
|
33
33
|
const result = filterProps({
|
|
34
|
-
id:
|
|
34
|
+
id: 'main',
|
|
35
35
|
tabIndex: 0,
|
|
36
|
-
role:
|
|
37
|
-
title:
|
|
36
|
+
role: 'button',
|
|
37
|
+
title: 'Click me',
|
|
38
38
|
})
|
|
39
39
|
expect(result).toEqual({
|
|
40
|
-
id:
|
|
40
|
+
id: 'main',
|
|
41
41
|
tabIndex: 0,
|
|
42
|
-
role:
|
|
43
|
-
title:
|
|
42
|
+
role: 'button',
|
|
43
|
+
title: 'Click me',
|
|
44
44
|
})
|
|
45
45
|
})
|
|
46
46
|
})
|
|
47
47
|
|
|
48
|
-
describe(
|
|
49
|
-
it(
|
|
50
|
-
const result = filterProps({
|
|
51
|
-
expect(result[
|
|
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
52
|
})
|
|
53
53
|
|
|
54
|
-
it(
|
|
55
|
-
const result = filterProps({
|
|
56
|
-
expect(result[
|
|
54
|
+
it('keeps data-custom', () => {
|
|
55
|
+
const result = filterProps({ 'data-custom': 'value' })
|
|
56
|
+
expect(result['data-custom']).toBe('value')
|
|
57
57
|
})
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
-
describe(
|
|
61
|
-
it(
|
|
62
|
-
const result = filterProps({
|
|
63
|
-
expect(result[
|
|
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
64
|
})
|
|
65
65
|
|
|
66
|
-
it(
|
|
67
|
-
const result = filterProps({
|
|
68
|
-
expect(result[
|
|
66
|
+
it('keeps aria-hidden', () => {
|
|
67
|
+
const result = filterProps({ 'aria-hidden': true })
|
|
68
|
+
expect(result['aria-hidden']).toBe(true)
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
-
it(
|
|
72
|
-
const result = filterProps({
|
|
73
|
-
expect(result[
|
|
71
|
+
it('keeps aria-describedby', () => {
|
|
72
|
+
const result = filterProps({ 'aria-describedby': 'desc' })
|
|
73
|
+
expect(result['aria-describedby']).toBe('desc')
|
|
74
74
|
})
|
|
75
75
|
})
|
|
76
76
|
|
|
77
|
-
describe(
|
|
78
|
-
it(
|
|
77
|
+
describe('keeps event handlers', () => {
|
|
78
|
+
it('keeps onClick', () => {
|
|
79
79
|
const fn = () => undefined
|
|
80
80
|
const result = filterProps({ onClick: fn })
|
|
81
81
|
expect(result.onClick).toBe(fn)
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
-
it(
|
|
84
|
+
it('keeps onMouseEnter', () => {
|
|
85
85
|
const fn = () => undefined
|
|
86
86
|
const result = filterProps({ onMouseEnter: fn })
|
|
87
87
|
expect(result.onMouseEnter).toBe(fn)
|
|
88
88
|
})
|
|
89
89
|
|
|
90
|
-
it(
|
|
90
|
+
it('keeps onKeyDown', () => {
|
|
91
91
|
const fn = () => undefined
|
|
92
92
|
const result = filterProps({ onKeyDown: fn })
|
|
93
93
|
expect(result.onKeyDown).toBe(fn)
|
|
94
94
|
})
|
|
95
95
|
})
|
|
96
96
|
|
|
97
|
-
describe(
|
|
98
|
-
it(
|
|
99
|
-
const result = filterProps({ $rocketstyle: { color:
|
|
97
|
+
describe('strips $-prefixed transient props', () => {
|
|
98
|
+
it('strips $rocketstyle', () => {
|
|
99
|
+
const result = filterProps({ $rocketstyle: { color: 'red' } })
|
|
100
100
|
expect(result).toEqual({})
|
|
101
101
|
})
|
|
102
102
|
|
|
103
|
-
it(
|
|
104
|
-
const result = filterProps({ $element:
|
|
103
|
+
it('strips $element', () => {
|
|
104
|
+
const result = filterProps({ $element: 'button' })
|
|
105
105
|
expect(result).toEqual({})
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
-
it(
|
|
108
|
+
it('strips $active', () => {
|
|
109
109
|
const result = filterProps({ $active: true })
|
|
110
110
|
expect(result).toEqual({})
|
|
111
111
|
})
|
|
112
112
|
|
|
113
|
-
it(
|
|
113
|
+
it('strips multiple $-prefixed props while keeping valid ones', () => {
|
|
114
114
|
const result = filterProps({
|
|
115
115
|
$rocketstyle: {},
|
|
116
|
-
$element:
|
|
117
|
-
id:
|
|
118
|
-
|
|
116
|
+
$element: 'div',
|
|
117
|
+
id: 'test',
|
|
118
|
+
'data-x': 'y',
|
|
119
119
|
})
|
|
120
|
-
expect(result).toEqual({ id:
|
|
120
|
+
expect(result).toEqual({ id: 'test', 'data-x': 'y' })
|
|
121
121
|
})
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
-
describe(
|
|
125
|
-
it(
|
|
126
|
-
const result = filterProps({ as:
|
|
124
|
+
describe('strips as prop', () => {
|
|
125
|
+
it('strips the as prop', () => {
|
|
126
|
+
const result = filterProps({ as: 'button' })
|
|
127
127
|
expect(result).toEqual({})
|
|
128
128
|
})
|
|
129
129
|
|
|
130
|
-
it(
|
|
131
|
-
const result = filterProps({ as:
|
|
132
|
-
expect(result).toEqual({ id:
|
|
130
|
+
it('strips as while keeping other props', () => {
|
|
131
|
+
const result = filterProps({ as: 'section', id: 'main' })
|
|
132
|
+
expect(result).toEqual({ id: 'main' })
|
|
133
133
|
})
|
|
134
134
|
})
|
|
135
135
|
|
|
136
|
-
describe(
|
|
137
|
-
it(
|
|
138
|
-
const result = filterProps({ customProp:
|
|
136
|
+
describe('strips unknown props', () => {
|
|
137
|
+
it('strips customProp', () => {
|
|
138
|
+
const result = filterProps({ customProp: 'value' })
|
|
139
139
|
expect(result).toEqual({})
|
|
140
140
|
})
|
|
141
141
|
|
|
142
|
-
it(
|
|
142
|
+
it('strips myThing', () => {
|
|
143
143
|
const result = filterProps({ myThing: 42 })
|
|
144
144
|
expect(result).toEqual({})
|
|
145
145
|
})
|
|
146
146
|
|
|
147
|
-
it(
|
|
148
|
-
const result = filterProps({ isActive: true, backgroundColor:
|
|
147
|
+
it('strips camelCase unknown props', () => {
|
|
148
|
+
const result = filterProps({ isActive: true, backgroundColor: 'red' })
|
|
149
149
|
expect(result).toEqual({})
|
|
150
150
|
})
|
|
151
151
|
|
|
152
|
-
it(
|
|
152
|
+
it('returns empty object for all-unknown props', () => {
|
|
153
153
|
const result = filterProps({
|
|
154
154
|
foo: 1,
|
|
155
155
|
bar: 2,
|
|
156
156
|
baz: 3,
|
|
157
|
-
customThing:
|
|
157
|
+
customThing: 'x',
|
|
158
158
|
})
|
|
159
159
|
expect(result).toEqual({})
|
|
160
160
|
})
|
|
161
161
|
})
|
|
162
162
|
})
|
|
163
163
|
|
|
164
|
-
describe(
|
|
165
|
-
describe(
|
|
166
|
-
it(
|
|
167
|
-
const result = buildProps({ className:
|
|
168
|
-
expect(result.class).toBe(
|
|
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
169
|
})
|
|
170
170
|
|
|
171
|
-
it(
|
|
172
|
-
const result = buildProps({ class:
|
|
173
|
-
expect(result.class).toBe(
|
|
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
174
|
})
|
|
175
175
|
|
|
176
|
-
it(
|
|
177
|
-
const result = buildProps({},
|
|
178
|
-
expect(result.class).toBe(
|
|
176
|
+
it('uses only generatedCls when no user className', () => {
|
|
177
|
+
const result = buildProps({}, 'pyr-abc', true)
|
|
178
|
+
expect(result.class).toBe('pyr-abc')
|
|
179
179
|
})
|
|
180
180
|
|
|
181
|
-
it(
|
|
182
|
-
const result = buildProps({ className:
|
|
183
|
-
expect(result.class).toBe(
|
|
181
|
+
it('uses only user className when generatedCls is empty', () => {
|
|
182
|
+
const result = buildProps({ className: 'custom' }, '', true)
|
|
183
|
+
expect(result.class).toBe('custom')
|
|
184
184
|
})
|
|
185
185
|
|
|
186
|
-
it(
|
|
187
|
-
const result = buildProps({},
|
|
186
|
+
it('sets no class when both are empty/missing', () => {
|
|
187
|
+
const result = buildProps({}, '', true)
|
|
188
188
|
expect(result.class).toBeUndefined()
|
|
189
189
|
})
|
|
190
190
|
})
|
|
191
191
|
|
|
192
|
-
describe(
|
|
193
|
-
it(
|
|
192
|
+
describe('isDOM=false (component target)', () => {
|
|
193
|
+
it('forwards all props except as, className, class, and $-prefixed', () => {
|
|
194
194
|
const result = buildProps(
|
|
195
195
|
{
|
|
196
|
-
as:
|
|
197
|
-
className:
|
|
198
|
-
customProp:
|
|
196
|
+
as: 'button',
|
|
197
|
+
className: 'user',
|
|
198
|
+
customProp: 'hello',
|
|
199
199
|
$rocketstyle: {},
|
|
200
200
|
$rocketstate: { hover: true },
|
|
201
|
-
|
|
201
|
+
'data-x': 'y',
|
|
202
202
|
onClick: () => undefined,
|
|
203
203
|
},
|
|
204
|
-
|
|
204
|
+
'pyr-abc',
|
|
205
205
|
false,
|
|
206
206
|
)
|
|
207
207
|
|
|
208
|
-
expect(result.customProp).toBe(
|
|
208
|
+
expect(result.customProp).toBe('hello')
|
|
209
209
|
expect(result.$rocketstyle).toBeUndefined()
|
|
210
210
|
expect(result.$rocketstate).toBeUndefined()
|
|
211
|
-
expect(result[
|
|
211
|
+
expect(result['data-x']).toBe('y')
|
|
212
212
|
expect(result.onClick).toBeDefined()
|
|
213
213
|
// as and className are not forwarded from rawProps (class is merged separately)
|
|
214
214
|
expect(result.as).toBeUndefined()
|
|
215
|
-
expect(result.class).toBe(
|
|
215
|
+
expect(result.class).toBe('pyr-abc user')
|
|
216
216
|
})
|
|
217
217
|
})
|
|
218
218
|
|
|
219
|
-
describe(
|
|
220
|
-
it(
|
|
221
|
-
const result = buildProps({ customProp:
|
|
219
|
+
describe('isDOM=true (default filtering)', () => {
|
|
220
|
+
it('filters unknown DOM props', () => {
|
|
221
|
+
const result = buildProps({ customProp: 'hello', unknownThing: 42 }, 'pyr-abc', true)
|
|
222
222
|
expect(result.customProp).toBeUndefined()
|
|
223
223
|
expect(result.unknownThing).toBeUndefined()
|
|
224
|
-
expect(result.class).toBe(
|
|
224
|
+
expect(result.class).toBe('pyr-abc')
|
|
225
225
|
})
|
|
226
226
|
|
|
227
|
-
it(
|
|
228
|
-
const result = buildProps({ $rocketstyle: {}, $active: true, id:
|
|
227
|
+
it('strips $-prefixed props', () => {
|
|
228
|
+
const result = buildProps({ $rocketstyle: {}, $active: true, id: 'test' }, 'pyr-abc', true)
|
|
229
229
|
expect(result.$rocketstyle).toBeUndefined()
|
|
230
230
|
expect(result.$active).toBeUndefined()
|
|
231
|
-
expect(result.id).toBe(
|
|
231
|
+
expect(result.id).toBe('test')
|
|
232
232
|
})
|
|
233
233
|
|
|
234
|
-
it(
|
|
235
|
-
const result = buildProps({
|
|
236
|
-
expect(result[
|
|
237
|
-
expect(result[
|
|
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
238
|
})
|
|
239
239
|
|
|
240
|
-
it(
|
|
241
|
-
const result = buildProps({ id:
|
|
242
|
-
expect(result.id).toBe(
|
|
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
243
|
expect(result.disabled).toBe(true)
|
|
244
244
|
expect(result.tabIndex).toBe(0)
|
|
245
245
|
})
|
|
246
246
|
|
|
247
|
-
it(
|
|
248
|
-
const result = buildProps({ as:
|
|
247
|
+
it('strips as prop', () => {
|
|
248
|
+
const result = buildProps({ as: 'section' }, 'pyr-abc', true)
|
|
249
249
|
expect(result.as).toBeUndefined()
|
|
250
250
|
})
|
|
251
251
|
})
|
|
252
252
|
|
|
253
|
-
describe(
|
|
254
|
-
it(
|
|
255
|
-
const customFilter = (prop: string) => prop.startsWith(
|
|
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
256
|
const result = buildProps(
|
|
257
|
-
{ myProp:
|
|
258
|
-
|
|
257
|
+
{ myProp: 'yes', otherProp: 'no', id: 'skip' },
|
|
258
|
+
'pyr-abc',
|
|
259
259
|
true,
|
|
260
260
|
customFilter,
|
|
261
261
|
)
|
|
262
|
-
expect(result.myProp).toBe(
|
|
262
|
+
expect(result.myProp).toBe('yes')
|
|
263
263
|
expect(result.otherProp).toBeUndefined()
|
|
264
264
|
expect(result.id).toBeUndefined()
|
|
265
265
|
})
|
|
266
266
|
|
|
267
|
-
it(
|
|
267
|
+
it('customFilter still skips as and className from rawProps', () => {
|
|
268
268
|
const customFilter = () => true
|
|
269
269
|
const result = buildProps(
|
|
270
|
-
{ as:
|
|
271
|
-
|
|
270
|
+
{ as: 'button', className: 'user', id: 'test' },
|
|
271
|
+
'pyr-abc',
|
|
272
272
|
true,
|
|
273
273
|
customFilter,
|
|
274
274
|
)
|
|
275
275
|
// as is always skipped
|
|
276
276
|
expect(result.as).toBeUndefined()
|
|
277
277
|
// class is merged, not forwarded from rawProps
|
|
278
|
-
expect(result.class).toBe(
|
|
279
|
-
expect(result.id).toBe(
|
|
278
|
+
expect(result.class).toBe('pyr-abc user')
|
|
279
|
+
expect(result.id).toBe('test')
|
|
280
280
|
})
|
|
281
281
|
})
|
|
282
282
|
})
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from
|
|
2
|
-
import { createGlobalStyle } from
|
|
3
|
-
import { sheet } from
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { createGlobalStyle } from '../globalStyle'
|
|
3
|
+
import { sheet } from '../sheet'
|
|
4
4
|
|
|
5
|
-
describe(
|
|
5
|
+
describe('createGlobalStyle -- empty CSS paths', () => {
|
|
6
6
|
afterEach(() => {
|
|
7
7
|
sheet.clearAll()
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
-
it(
|
|
10
|
+
it('static: returns null for empty template', () => {
|
|
11
11
|
const GlobalStyle = createGlobalStyle``
|
|
12
12
|
const result = GlobalStyle({})
|
|
13
13
|
expect(result).toBeNull()
|
|
14
14
|
})
|
|
15
15
|
|
|
16
|
-
it(
|
|
16
|
+
it('static: returns null for whitespace-only template', () => {
|
|
17
17
|
const GlobalStyle = createGlobalStyle` `
|
|
18
18
|
const result = GlobalStyle({})
|
|
19
19
|
expect(result).toBeNull()
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
it(
|
|
23
|
-
const GlobalStyle = createGlobalStyle`${({ theme }: any) => (theme.empty ?
|
|
22
|
+
it('dynamic: returns null when interpolation resolves to empty CSS', () => {
|
|
23
|
+
const GlobalStyle = createGlobalStyle`${({ theme }: any) => (theme.empty ? '' : '')}`
|
|
24
24
|
const result = GlobalStyle({})
|
|
25
25
|
expect(result).toBeNull()
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
it(
|
|
29
|
-
const GlobalStyle = createGlobalStyle`${() =>
|
|
28
|
+
it('dynamic: returns null when interpolation resolves to whitespace', () => {
|
|
29
|
+
const GlobalStyle = createGlobalStyle`${() => ' '}`
|
|
30
30
|
const result = GlobalStyle({})
|
|
31
31
|
expect(result).toBeNull()
|
|
32
32
|
})
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
-
describe(
|
|
35
|
+
describe('createGlobalStyle', () => {
|
|
36
36
|
afterEach(() => {
|
|
37
37
|
sheet.clearAll()
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
it(
|
|
40
|
+
it('returns a component function', () => {
|
|
41
41
|
const GlobalStyle = createGlobalStyle`
|
|
42
42
|
body { margin: 0; }
|
|
43
43
|
`
|
|
44
|
-
expect(typeof GlobalStyle).toBe(
|
|
44
|
+
expect(typeof GlobalStyle).toBe('function')
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
-
it(
|
|
47
|
+
it('renders nothing (returns null)', () => {
|
|
48
48
|
const GlobalStyle = createGlobalStyle`
|
|
49
49
|
body { margin: 0; padding: 0; }
|
|
50
50
|
`
|
|
@@ -52,17 +52,17 @@ describe("createGlobalStyle", () => {
|
|
|
52
52
|
expect(result).toBeNull()
|
|
53
53
|
})
|
|
54
54
|
|
|
55
|
-
it(
|
|
55
|
+
it('handles dynamic interpolations with theme', () => {
|
|
56
56
|
// Dynamic path: function interpolation causes per-render resolution
|
|
57
57
|
const GlobalStyle = createGlobalStyle`
|
|
58
|
-
body { font-family: ${({ theme }: any) => theme?.font ??
|
|
58
|
+
body { font-family: ${({ theme }: any) => theme?.font ?? 'sans-serif'}; }
|
|
59
59
|
`
|
|
60
60
|
const result = GlobalStyle({})
|
|
61
61
|
expect(result).toBeNull()
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
-
it(
|
|
65
|
-
const color =
|
|
64
|
+
it('handles static interpolations', () => {
|
|
65
|
+
const color = 'red'
|
|
66
66
|
const GlobalStyle = createGlobalStyle`
|
|
67
67
|
body { color: ${color}; }
|
|
68
68
|
`
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { hash } from
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { hash } from '../hash'
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
6
|
-
expect(typeof hash(
|
|
4
|
+
describe('hash', () => {
|
|
5
|
+
it('returns a string', () => {
|
|
6
|
+
expect(typeof hash('test')).toBe('string')
|
|
7
7
|
})
|
|
8
8
|
|
|
9
|
-
it(
|
|
10
|
-
const input =
|
|
9
|
+
it('is deterministic — same input always produces same output', () => {
|
|
10
|
+
const input = 'display: flex; color: red;'
|
|
11
11
|
expect(hash(input)).toBe(hash(input))
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
-
it(
|
|
15
|
-
expect(hash(
|
|
14
|
+
it('produces different hashes for different inputs', () => {
|
|
15
|
+
expect(hash('color: red')).not.toBe(hash('color: blue'))
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
it(
|
|
19
|
-
const result = hash(
|
|
18
|
+
it('returns base-36 string (compact)', () => {
|
|
19
|
+
const result = hash('some css')
|
|
20
20
|
expect(result).toMatch(/^[0-9a-z]+$/)
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
-
it(
|
|
24
|
-
const result = hash(
|
|
25
|
-
expect(typeof result).toBe(
|
|
23
|
+
it('handles empty string', () => {
|
|
24
|
+
const result = hash('')
|
|
25
|
+
expect(typeof result).toBe('string')
|
|
26
26
|
expect(result.length).toBeGreaterThan(0)
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
it(
|
|
30
|
-
const longCSS =
|
|
29
|
+
it('handles long CSS strings', () => {
|
|
30
|
+
const longCSS = 'display: flex; '.repeat(100)
|
|
31
31
|
const result = hash(longCSS)
|
|
32
|
-
expect(typeof result).toBe(
|
|
32
|
+
expect(typeof result).toBe('string')
|
|
33
33
|
// base-36 uint32 is at most 7 chars
|
|
34
34
|
expect(result.length).toBeLessThan(10)
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
it(
|
|
37
|
+
it('handles special characters in CSS', () => {
|
|
38
38
|
const css = `@media (min-width: 48em) { .foo { content: "hello"; } }`
|
|
39
39
|
const result = hash(css)
|
|
40
|
-
expect(typeof result).toBe(
|
|
40
|
+
expect(typeof result).toBe('string')
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
it(
|
|
43
|
+
it('produces consistent hash for FNV-1a offset basis on empty string', () => {
|
|
44
44
|
// Empty string: h stays at FNV_OFFSET = 2166136261, base36 = "zzzzzz" range
|
|
45
|
-
const result = hash(
|
|
45
|
+
const result = hash('')
|
|
46
46
|
// Just verify it is stable
|
|
47
|
-
expect(result).toBe(hash(
|
|
47
|
+
expect(result).toBe(hash(''))
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
-
it(
|
|
50
|
+
it('handles unicode characters', () => {
|
|
51
51
|
const result = hash('content: "🎉";')
|
|
52
|
-
expect(typeof result).toBe(
|
|
52
|
+
expect(typeof result).toBe('string')
|
|
53
53
|
expect(result.length).toBeGreaterThan(0)
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
it(
|
|
56
|
+
it('single character inputs produce distinct hashes', () => {
|
|
57
57
|
const hashes = new Set<string>()
|
|
58
58
|
for (let i = 0; i < 26; i++) {
|
|
59
59
|
hashes.add(hash(String.fromCharCode(97 + i)))
|
|
@@ -62,9 +62,9 @@ describe("hash", () => {
|
|
|
62
62
|
expect(hashes.size).toBe(26)
|
|
63
63
|
})
|
|
64
64
|
|
|
65
|
-
it(
|
|
65
|
+
it('hash is unsigned 32-bit (no negative values)', () => {
|
|
66
66
|
// base-36 of a uint32 is always positive
|
|
67
|
-
const result = hash(
|
|
67
|
+
const result = hash('test negative')
|
|
68
68
|
expect(Number.parseInt(result, 36)).toBeGreaterThanOrEqual(0)
|
|
69
69
|
})
|
|
70
70
|
})
|