@pyreon/unistyle 0.24.5 → 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.
Files changed (50) hide show
  1. package/package.json +7 -9
  2. package/src/__tests__/alignContent.test.ts +0 -121
  3. package/src/__tests__/borderRadius.test.ts +0 -125
  4. package/src/__tests__/camelToKebab.test.ts +0 -44
  5. package/src/__tests__/context.test.ts +0 -147
  6. package/src/__tests__/createMediaQueries.test.ts +0 -98
  7. package/src/__tests__/edge.test.ts +0 -164
  8. package/src/__tests__/enrichTheme.test.ts +0 -56
  9. package/src/__tests__/extendCss.test.ts +0 -45
  10. package/src/__tests__/index.test.ts +0 -79
  11. package/src/__tests__/makeItResponsive.test.ts +0 -431
  12. package/src/__tests__/manifest-snapshot.test.ts +0 -34
  13. package/src/__tests__/native-marker.test.ts +0 -9
  14. package/src/__tests__/optimizeBreakpointDeltas.test.ts +0 -124
  15. package/src/__tests__/processDescriptor.test.ts +0 -322
  16. package/src/__tests__/responsive.test.ts +0 -221
  17. package/src/__tests__/special-keys.test.ts +0 -120
  18. package/src/__tests__/styles.test.ts +0 -273
  19. package/src/__tests__/unistyle.browser.test.tsx +0 -169
  20. package/src/__tests__/units.test.ts +0 -134
  21. package/src/context.tsx +0 -44
  22. package/src/enrichTheme.ts +0 -42
  23. package/src/env.d.ts +0 -6
  24. package/src/index.ts +0 -91
  25. package/src/manifest.ts +0 -197
  26. package/src/responsive/breakpoints.ts +0 -15
  27. package/src/responsive/createMediaQueries.ts +0 -43
  28. package/src/responsive/index.ts +0 -15
  29. package/src/responsive/makeItResponsive.ts +0 -223
  30. package/src/responsive/normalizeTheme.ts +0 -79
  31. package/src/responsive/optimizeBreakpointDeltas.ts +0 -190
  32. package/src/responsive/optimizeTheme.ts +0 -60
  33. package/src/responsive/sortBreakpoints.ts +0 -10
  34. package/src/responsive/transformTheme.ts +0 -54
  35. package/src/styles/alignContent.ts +0 -62
  36. package/src/styles/extendCss.ts +0 -26
  37. package/src/styles/index.ts +0 -16
  38. package/src/styles/shorthands/borderRadius.ts +0 -89
  39. package/src/styles/shorthands/edge.ts +0 -108
  40. package/src/styles/shorthands/index.ts +0 -4
  41. package/src/styles/styles/camelToKebab.ts +0 -3
  42. package/src/styles/styles/index.ts +0 -132
  43. package/src/styles/styles/processDescriptor.ts +0 -136
  44. package/src/styles/styles/propertyMap.ts +0 -438
  45. package/src/styles/styles/types.ts +0 -368
  46. package/src/types.ts +0 -175
  47. package/src/units/index.ts +0 -6
  48. package/src/units/stripUnit.ts +0 -25
  49. package/src/units/value.ts +0 -47
  50. package/src/units/values.ts +0 -40
@@ -1,322 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import processDescriptor from '../styles/styles/processDescriptor'
3
- import type { PropertyDescriptor } from '../styles/styles/propertyMap'
4
- import type { InnerTheme } from '../styles/styles/types'
5
-
6
- // Minimal helpers matching the signature expected by processDescriptor
7
- const mockCss = (strings: TemplateStringsArray, ...vals: any[]) => {
8
- let result = ''
9
- for (let i = 0; i < strings.length; i++) {
10
- result += strings[i]
11
- if (i < vals.length) result += String(vals[i] ?? '')
12
- }
13
- return result
14
- }
15
-
16
- const mockCalc = (...params: any[]) => {
17
- const val = params.find((p) => p != null)
18
- if (val == null) return null
19
- if (typeof val === 'string') return val
20
- return `${val / 16}rem`
21
- }
22
-
23
- const mockEdge = (_property: string, _values: any) => null
24
- const mockBorderRadius = (_props: any) => null
25
-
26
- const t = (overrides: Partial<InnerTheme> = {}): InnerTheme => overrides as InnerTheme
27
-
28
- describe('processDescriptor', () => {
29
- describe('simple kind', () => {
30
- const d: PropertyDescriptor = { kind: 'simple', css: 'display', key: 'display' }
31
-
32
- it('returns CSS declaration when key has a value', () => {
33
- const result = processDescriptor(
34
- d,
35
- t({ display: 'flex' }),
36
- mockCss,
37
- mockCalc,
38
- mockEdge as any,
39
- mockBorderRadius as any,
40
- )
41
- expect(result).toBe('display: flex;')
42
- })
43
-
44
- it('returns empty string when key is null', () => {
45
- const result = processDescriptor(
46
- d,
47
- t({ display: null as any }),
48
- mockCss,
49
- mockCalc,
50
- mockEdge as any,
51
- mockBorderRadius as any,
52
- )
53
- expect(result).toBe('')
54
- })
55
-
56
- it('returns empty string when key is undefined', () => {
57
- const result = processDescriptor(
58
- d,
59
- t({}),
60
- mockCss,
61
- mockCalc,
62
- mockEdge as any,
63
- mockBorderRadius as any,
64
- )
65
- expect(result).toBe('')
66
- })
67
- })
68
-
69
- describe('convert kind', () => {
70
- const d: PropertyDescriptor = { kind: 'convert', css: 'width', key: 'width' }
71
-
72
- it('returns converted value through calc function', () => {
73
- const result = processDescriptor(
74
- d,
75
- t({ width: 32 } as any),
76
- mockCss,
77
- mockCalc,
78
- mockEdge as any,
79
- mockBorderRadius as any,
80
- )
81
- expect(result).toBe('width: 2rem;')
82
- })
83
-
84
- it('passes through string values', () => {
85
- const result = processDescriptor(
86
- d,
87
- t({ width: '50%' } as any),
88
- mockCss,
89
- mockCalc,
90
- mockEdge as any,
91
- mockBorderRadius as any,
92
- )
93
- expect(result).toBe('width: 50%;')
94
- })
95
- })
96
-
97
- describe('convert_fallback kind', () => {
98
- const d: PropertyDescriptor = {
99
- kind: 'convert_fallback',
100
- css: 'width',
101
- keys: ['width', 'size'] as (keyof InnerTheme)[],
102
- }
103
-
104
- it('uses first defined key value', () => {
105
- const result = processDescriptor(
106
- d,
107
- t({ width: 16 } as any),
108
- mockCss,
109
- mockCalc,
110
- mockEdge as any,
111
- mockBorderRadius as any,
112
- )
113
- expect(result).toBe('width: 1rem;')
114
- })
115
- })
116
-
117
- describe('special kind', () => {
118
- it('returns fullScreen CSS when fullScreen is truthy', () => {
119
- const d: PropertyDescriptor = { kind: 'special', id: 'fullScreen' }
120
- const result = processDescriptor(
121
- d,
122
- t({ fullScreen: true } as any),
123
- mockCss,
124
- mockCalc,
125
- mockEdge as any,
126
- mockBorderRadius as any,
127
- )
128
- expect(result).toContain('position: fixed')
129
- expect(result).toContain('top: 0')
130
- })
131
-
132
- it('returns empty string when fullScreen is falsy', () => {
133
- const d: PropertyDescriptor = { kind: 'special', id: 'fullScreen' }
134
- const result = processDescriptor(
135
- d,
136
- t({}),
137
- mockCss,
138
- mockCalc,
139
- mockEdge as any,
140
- mockBorderRadius as any,
141
- )
142
- expect(result).toBe('')
143
- })
144
-
145
- it('returns backgroundImage CSS when set', () => {
146
- const d: PropertyDescriptor = { kind: 'special', id: 'backgroundImage' }
147
- const result = processDescriptor(
148
- d,
149
- t({ backgroundImage: 'url.png' } as any),
150
- mockCss,
151
- mockCalc,
152
- mockEdge as any,
153
- mockBorderRadius as any,
154
- )
155
- // Result is a css`` template result — normalize whitespace
156
- const normalized = String(result).replace(/\s+/g, ' ').trim()
157
- expect(normalized).toContain('background-image: url(url.png);')
158
- })
159
-
160
- it('returns animation CSS when keyframe is set', () => {
161
- const d: PropertyDescriptor = { kind: 'special', id: 'animation' }
162
- const result = processDescriptor(
163
- d,
164
- t({ keyframe: 'fadeIn', animation: '0.3s ease' } as any),
165
- mockCss,
166
- mockCalc,
167
- mockEdge as any,
168
- mockBorderRadius as any,
169
- )
170
- expect(result).toBe('animation: fadeIn 0.3s ease;')
171
- })
172
-
173
- it('returns hideEmpty pseudo-selector when hideEmpty is true', () => {
174
- const d: PropertyDescriptor = { kind: 'special', id: 'hideEmpty' }
175
- const result = processDescriptor(
176
- d,
177
- t({ hideEmpty: true } as any),
178
- mockCss,
179
- mockCalc,
180
- mockEdge as any,
181
- mockBorderRadius as any,
182
- )
183
- expect(result).toContain('&:empty')
184
- expect(result).toContain('display: none')
185
- })
186
-
187
- it('returns clearFix pseudo-element when clearFix is true', () => {
188
- const d: PropertyDescriptor = { kind: 'special', id: 'clearFix' }
189
- const result = processDescriptor(
190
- d,
191
- t({ clearFix: true } as any),
192
- mockCss,
193
- mockCalc,
194
- mockEdge as any,
195
- mockBorderRadius as any,
196
- )
197
- expect(result).toContain('&::after')
198
- expect(result).toContain('clear: both')
199
- })
200
-
201
- it('returns empty string for unknown special id', () => {
202
- const d: PropertyDescriptor = { kind: 'special', id: 'unknown' }
203
- const result = processDescriptor(
204
- d,
205
- t({}),
206
- mockCss,
207
- mockCalc,
208
- mockEdge as any,
209
- mockBorderRadius as any,
210
- )
211
- expect(result).toBe('')
212
- })
213
- })
214
-
215
- describe('edge kind', () => {
216
- it('delegates to shorthand function', () => {
217
- const d: PropertyDescriptor = {
218
- kind: 'edge',
219
- property: 'margin',
220
- keys: {
221
- full: 'margin' as keyof InnerTheme,
222
- x: 'marginX' as keyof InnerTheme,
223
- y: 'marginY' as keyof InnerTheme,
224
- top: 'marginTop' as keyof InnerTheme,
225
- left: 'marginLeft' as keyof InnerTheme,
226
- bottom: 'marginBottom' as keyof InnerTheme,
227
- right: 'marginRight' as keyof InnerTheme,
228
- },
229
- }
230
- const customEdge = () => 'margin: 1rem;'
231
- const result = processDescriptor(
232
- d,
233
- t({ margin: 16 } as any),
234
- mockCss,
235
- mockCalc,
236
- customEdge as any,
237
- mockBorderRadius as any,
238
- )
239
- expect(result).toBe('margin: 1rem;')
240
- })
241
-
242
- it('returns empty string when shorthand returns null', () => {
243
- const d: PropertyDescriptor = {
244
- kind: 'edge',
245
- property: 'padding',
246
- keys: {
247
- full: 'padding' as keyof InnerTheme,
248
- x: 'paddingX' as keyof InnerTheme,
249
- y: 'paddingY' as keyof InnerTheme,
250
- top: 'paddingTop' as keyof InnerTheme,
251
- left: 'paddingLeft' as keyof InnerTheme,
252
- bottom: 'paddingBottom' as keyof InnerTheme,
253
- right: 'paddingRight' as keyof InnerTheme,
254
- },
255
- }
256
- const result = processDescriptor(
257
- d,
258
- t({}),
259
- mockCss,
260
- mockCalc,
261
- mockEdge as any,
262
- mockBorderRadius as any,
263
- )
264
- expect(result).toBe('')
265
- })
266
- })
267
-
268
- describe('border_radius kind', () => {
269
- it('delegates to borderRadius function', () => {
270
- const d: PropertyDescriptor = {
271
- kind: 'border_radius',
272
- keys: {
273
- full: 'borderRadius' as keyof InnerTheme,
274
- top: 'borderRadiusTop' as keyof InnerTheme,
275
- bottom: 'borderRadiusBottom' as keyof InnerTheme,
276
- left: 'borderRadiusLeft' as keyof InnerTheme,
277
- right: 'borderRadiusRight' as keyof InnerTheme,
278
- topLeft: 'borderRadiusTopLeft' as keyof InnerTheme,
279
- topRight: 'borderRadiusTopRight' as keyof InnerTheme,
280
- bottomLeft: 'borderRadiusBottomLeft' as keyof InnerTheme,
281
- bottomRight: 'borderRadiusBottomRight' as keyof InnerTheme,
282
- },
283
- }
284
- const customBR = () => 'border-radius: 4px;'
285
- const result = processDescriptor(
286
- d,
287
- t({ borderRadius: 4 } as any),
288
- mockCss,
289
- mockCalc,
290
- mockEdge as any,
291
- customBR as any,
292
- )
293
- expect(result).toBe('border-radius: 4px;')
294
- })
295
-
296
- it('returns empty string when borderRadius returns null', () => {
297
- const d: PropertyDescriptor = {
298
- kind: 'border_radius',
299
- keys: {
300
- full: 'borderRadius' as keyof InnerTheme,
301
- top: 'borderRadiusTop' as keyof InnerTheme,
302
- bottom: 'borderRadiusBottom' as keyof InnerTheme,
303
- left: 'borderRadiusLeft' as keyof InnerTheme,
304
- right: 'borderRadiusRight' as keyof InnerTheme,
305
- topLeft: 'borderRadiusTopLeft' as keyof InnerTheme,
306
- topRight: 'borderRadiusTopRight' as keyof InnerTheme,
307
- bottomLeft: 'borderRadiusBottomLeft' as keyof InnerTheme,
308
- bottomRight: 'borderRadiusBottomRight' as keyof InnerTheme,
309
- },
310
- }
311
- const result = processDescriptor(
312
- d,
313
- t({}),
314
- mockCss,
315
- mockCalc,
316
- mockEdge as any,
317
- mockBorderRadius as any,
318
- )
319
- expect(result).toBe('')
320
- })
321
- })
322
- })
@@ -1,221 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import breakpoints from '../responsive/breakpoints'
3
- import normalizeTheme from '../responsive/normalizeTheme'
4
- import optimizeTheme from '../responsive/optimizeTheme'
5
- import sortBreakpoints from '../responsive/sortBreakpoints'
6
- import transformTheme from '../responsive/transformTheme'
7
-
8
- describe('breakpoints', () => {
9
- it('has expected default config', () => {
10
- expect(breakpoints.rootSize).toBe(16)
11
- expect(breakpoints.breakpoints).toHaveProperty('xs')
12
- expect(breakpoints.breakpoints).toHaveProperty('sm')
13
- expect(breakpoints.breakpoints).toHaveProperty('md')
14
- expect(breakpoints.breakpoints).toHaveProperty('lg')
15
- expect(breakpoints.breakpoints).toHaveProperty('xl')
16
- expect(breakpoints.breakpoints).toHaveProperty('xxl')
17
- })
18
-
19
- it('has correct pixel values', () => {
20
- expect(breakpoints.breakpoints.xs).toBe(0)
21
- expect(breakpoints.breakpoints.sm).toBe(576)
22
- expect(breakpoints.breakpoints.md).toBe(768)
23
- expect(breakpoints.breakpoints.lg).toBe(992)
24
- expect(breakpoints.breakpoints.xl).toBe(1200)
25
- expect(breakpoints.breakpoints.xxl).toBe(1440)
26
- })
27
- })
28
-
29
- describe('sortBreakpoints', () => {
30
- it('sorts breakpoints by value ascending, returns keys', () => {
31
- const bps = { md: 768, xs: 0, xl: 1200, sm: 576 }
32
- const sorted = sortBreakpoints(bps)
33
- expect(sorted).toEqual(['xs', 'sm', 'md', 'xl'])
34
- })
35
-
36
- it('handles already sorted breakpoints', () => {
37
- const sorted = sortBreakpoints({ xs: 0, sm: 576, md: 768 })
38
- expect(sorted).toEqual(['xs', 'sm', 'md'])
39
- })
40
-
41
- it('handles single breakpoint', () => {
42
- expect(sortBreakpoints({ xs: 0 })).toEqual(['xs'])
43
- })
44
-
45
- it('handles empty object', () => {
46
- expect(sortBreakpoints({})).toEqual([])
47
- })
48
-
49
- it('sorts full default breakpoint set', () => {
50
- const sorted = sortBreakpoints(breakpoints.breakpoints)
51
- expect(sorted).toEqual(['xs', 'sm', 'md', 'lg', 'xl', 'xxl'])
52
- })
53
- })
54
-
55
- describe('normalizeTheme', () => {
56
- const bpKeys = ['xs', 'sm', 'md', 'lg', 'xl']
57
-
58
- it('returns theme as-is when no nested objects/arrays', () => {
59
- const theme = { color: 'red', fontSize: 16 }
60
- const result = normalizeTheme({ theme, breakpoints: bpKeys })
61
- expect(result).toEqual(theme)
62
- })
63
-
64
- it('expands array values across breakpoints', () => {
65
- const theme = { fontSize: [12, 14, 16, 18, 20] }
66
- const result = normalizeTheme({ theme, breakpoints: bpKeys })
67
- expect(result.fontSize).toEqual({
68
- xs: 12,
69
- sm: 14,
70
- md: 16,
71
- lg: 18,
72
- xl: 20,
73
- })
74
- })
75
-
76
- it('array values use last value for extra breakpoints', () => {
77
- const theme = { fontSize: [12, 14] }
78
- const result = normalizeTheme({ theme, breakpoints: bpKeys })
79
- expect((result.fontSize as Record<string, unknown>).xs).toBe(12)
80
- expect((result.fontSize as Record<string, unknown>).sm).toBe(14)
81
- expect((result.fontSize as Record<string, unknown>).md).toBe(14)
82
- })
83
-
84
- it('expands object values with carry-forward', () => {
85
- const theme = { fontSize: { xs: 12, md: 16 } }
86
- const result = normalizeTheme({ theme, breakpoints: bpKeys })
87
- const fs = result.fontSize as Record<string, unknown>
88
- expect(fs.xs).toBe(12)
89
- expect(fs.sm).toBe(12) // carried from xs
90
- expect(fs.md).toBe(16)
91
- expect(fs.lg).toBe(16) // carried from md
92
- })
93
-
94
- it('skips null values', () => {
95
- const theme = { color: null, fontSize: 16 }
96
- const result = normalizeTheme({ theme, breakpoints: bpKeys })
97
- expect(result.color).toBeUndefined()
98
- })
99
- })
100
-
101
- describe('transformTheme', () => {
102
- const bpKeys = ['xs', 'sm', 'md']
103
-
104
- it('pivots scalar values to first breakpoint', () => {
105
- const theme = { color: 'red' }
106
- const result = transformTheme({ theme, breakpoints: bpKeys })
107
- expect(result.xs).toEqual({ color: 'red' })
108
- })
109
-
110
- it('pivots object values to breakpoints', () => {
111
- const theme = { color: { xs: 'red', md: 'blue' } }
112
- const result = transformTheme({ theme, breakpoints: bpKeys })
113
- expect(result.xs).toEqual({ color: 'red' })
114
- expect(result.md).toEqual({ color: 'blue' })
115
- })
116
-
117
- it('pivots array values by index', () => {
118
- const theme = { fontSize: [12, 14, 16] }
119
- const result = transformTheme({ theme, breakpoints: bpKeys })
120
- expect(result.xs).toEqual({ fontSize: 12 })
121
- expect(result.sm).toEqual({ fontSize: 14 })
122
- expect(result.md).toEqual({ fontSize: 16 })
123
- })
124
-
125
- it('returns empty object for empty theme', () => {
126
- expect(transformTheme({ theme: {}, breakpoints: bpKeys })).toEqual({})
127
- })
128
-
129
- it('returns empty object for empty breakpoints', () => {
130
- expect(transformTheme({ theme: { color: 'red' }, breakpoints: [] })).toEqual({})
131
- })
132
-
133
- it('filters out unexpected breakpoint keys', () => {
134
- const theme = { color: { xs: 'red', unknown: 'green' } }
135
- const result = transformTheme({ theme, breakpoints: bpKeys })
136
- expect(result).not.toHaveProperty('unknown')
137
- })
138
- })
139
-
140
- describe('optimizeTheme', () => {
141
- const bpKeys = ['xs', 'sm', 'md', 'lg']
142
-
143
- it('keeps first breakpoint', () => {
144
- const theme = {
145
- xs: { color: 'red' },
146
- sm: { color: 'blue' },
147
- }
148
- const result = optimizeTheme({ theme, breakpoints: bpKeys })
149
- expect(result.xs).toEqual({ color: 'red' })
150
- })
151
-
152
- it('removes duplicate breakpoints', () => {
153
- const theme = {
154
- xs: { color: 'red' },
155
- sm: { color: 'red' },
156
- md: { color: 'blue' },
157
- }
158
- const result = optimizeTheme({ theme, breakpoints: bpKeys })
159
- expect(result.xs).toEqual({ color: 'red' })
160
- expect(result.sm).toBeUndefined()
161
- expect(result.md).toEqual({ color: 'blue' })
162
- })
163
-
164
- it('keeps breakpoints with different values', () => {
165
- const theme = {
166
- xs: { color: 'red', fontSize: 12 },
167
- sm: { color: 'red', fontSize: 14 },
168
- }
169
- const result = optimizeTheme({ theme, breakpoints: bpKeys })
170
- expect(result.xs).toBeDefined()
171
- expect(result.sm).toBeDefined()
172
- })
173
-
174
- it('handles empty theme', () => {
175
- expect(optimizeTheme({ theme: {}, breakpoints: bpKeys })).toEqual({})
176
- })
177
-
178
- it('keeps entire breakpoint when ANY property differs from previous (all-or-nothing)', () => {
179
- // Simple all-or-nothing: if the breakpoint differs from the previous
180
- // one AT ALL (via shallow comparison), emit the ENTIRE breakpoint.
181
- // Matches reference implementation and original monorepo-migration
182
- // version. Previous per-property "optimizations" broke shorthand/
183
- // longhand interactions and property ordering guarantees.
184
- const theme = {
185
- xs: { maxWidth: '90%', height: '100%' },
186
- sm: { maxWidth: '33.75rem', height: '100%' },
187
- md: { maxWidth: '43.75rem', height: '100%' },
188
- lg: { maxWidth: '58.75rem', height: '100%' },
189
- }
190
- const result = optimizeTheme({ theme, breakpoints: bpKeys })
191
- expect(result.xs).toEqual({ maxWidth: '90%', height: '100%' })
192
- // Full breakpoints kept — height IS duplicated but that's correct:
193
- // the browser cascades one media query's properties independently,
194
- // and partial property emission breaks shorthand/longhand ordering.
195
- expect(result.sm).toEqual({ maxWidth: '33.75rem', height: '100%' })
196
- expect(result.md).toEqual({ maxWidth: '43.75rem', height: '100%' })
197
- expect(result.lg).toEqual({ maxWidth: '58.75rem', height: '100%' })
198
- })
199
-
200
- it('drops breakpoint when FULLY identical to previous (shallowEqual)', () => {
201
- const theme = {
202
- xs: { color: 'red', size: 12 },
203
- sm: { color: 'red', size: 12 }, // identical to xs — dropped
204
- md: { color: 'blue', size: 12 }, // differs → full breakpoint kept
205
- }
206
- const result = optimizeTheme({ theme, breakpoints: bpKeys })
207
- expect(result.xs).toEqual({ color: 'red', size: 12 })
208
- expect(result.sm).toBeUndefined()
209
- expect(result.md).toEqual({ color: 'blue', size: 12 })
210
- })
211
-
212
- it('emits full breakpoint when keys differ (property added/removed)', () => {
213
- const theme = {
214
- xs: { color: 'red' },
215
- sm: { color: 'red', padding: 10 },
216
- }
217
- const result = optimizeTheme({ theme, breakpoints: bpKeys })
218
- expect(result.xs).toEqual({ color: 'red' })
219
- expect(result.sm).toEqual({ color: 'red', padding: 10 })
220
- })
221
- })
@@ -1,120 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import styles from '../styles/styles/index'
3
-
4
- const mockCss = (strings: TemplateStringsArray, ...vals: any[]) => {
5
- let r = ''
6
- for (let i = 0; i < strings.length; i++) {
7
- r += strings[i]
8
- if (i < vals.length) r += String(vals[i])
9
- }
10
- return r
11
- }
12
-
13
- // Regression: kind: 'special' descriptors (`fullScreen`, `hideEmpty`,
14
- // `clearFix`, `extendCss`, `backgroundImage`, `animation`) only carry an
15
- // `id` field — no `key` / `keys`. The keyToIndices builder used to walk
16
- // only `d.key` / `d.keys`, so special descriptors were never indexed.
17
- //
18
- // In single-special-property themes the bug was masked by the fallback
19
- // path (`if (fragments.length === 0 && Object.keys(t).length > 0)` triggers
20
- // a full-scan that hits processSpecial). The moment ANY non-special key is
21
- // also present in the theme — the real-world shape, e.g. `<Overlay>` with
22
- // `{ fullScreen: true, background: 'rgba(0,0,0,0.5)' }` — the fast path
23
- // processes `background`, fragments.length === 1, fallback skipped, the
24
- // special is silently dropped.
25
- //
26
- // Fix: index `d.id` alongside `d.key` / `d.keys` so the fast path resolves
27
- // special descriptors directly.
28
- describe('kind: special descriptors paired with non-special properties', () => {
29
- it('fullScreen + background → both render', () => {
30
- const result = styles({
31
- theme: { fullScreen: true, background: 'rgba(0,0,0,0.5)' },
32
- css: mockCss,
33
- rootSize: 16,
34
- })
35
- const output = String(result)
36
- expect(output).toContain('position: fixed;')
37
- expect(output).toContain('top: 0;')
38
- expect(output).toContain('background: rgba(0,0,0,0.5);')
39
- })
40
-
41
- it('hideEmpty + color → both render', () => {
42
- const result = styles({
43
- theme: { hideEmpty: true, color: 'red' },
44
- css: mockCss,
45
- rootSize: 16,
46
- })
47
- const normalized = String(result).replace(/\s+/g, ' ')
48
- expect(normalized).toContain('&:empty { display: none; }')
49
- expect(normalized).toContain('color: red;')
50
- })
51
-
52
- it('clearFix + padding → both render', () => {
53
- const result = styles({
54
- theme: { clearFix: true, padding: 8 },
55
- css: mockCss,
56
- rootSize: 16,
57
- })
58
- const normalized = String(result).replace(/\s+/g, ' ')
59
- expect(normalized).toContain("&::after { clear: both; content: ''; display: table; }")
60
- expect(normalized).toContain('padding:')
61
- expect(normalized).toContain('0.5rem')
62
- })
63
-
64
- it('extendCss + color → both render', () => {
65
- const result = styles({
66
- theme: { extendCss: 'border: 1px solid red;', color: 'blue' },
67
- css: mockCss,
68
- rootSize: 16,
69
- })
70
- const output = String(result)
71
- expect(output).toContain('border: 1px solid red;')
72
- expect(output).toContain('color: blue;')
73
- })
74
-
75
- it('backgroundImage + color → both render', () => {
76
- const result = styles({
77
- theme: { backgroundImage: 'https://example.com/img.png', color: 'green' },
78
- css: mockCss,
79
- rootSize: 16,
80
- })
81
- const output = String(result)
82
- expect(output).toContain('background-image: url(https://example.com/img.png);')
83
- expect(output).toContain('color: green;')
84
- })
85
-
86
- it('animation + color → both render', () => {
87
- const result = styles({
88
- theme: { animation: 'fadeIn 1s ease-in', color: 'purple' },
89
- css: mockCss,
90
- rootSize: 16,
91
- })
92
- const output = String(result)
93
- expect(output).toContain('animation:')
94
- expect(output).toContain('fadeIn 1s ease-in')
95
- expect(output).toContain('color: purple;')
96
- })
97
-
98
- it('multiple specials + non-specials → all render', () => {
99
- const result = styles({
100
- theme: {
101
- fullScreen: true,
102
- hideEmpty: true,
103
- clearFix: true,
104
- extendCss: 'outline: 2px dashed orange;',
105
- color: 'red',
106
- padding: 16,
107
- },
108
- css: mockCss,
109
- rootSize: 16,
110
- })
111
- const normalized = String(result).replace(/\s+/g, ' ')
112
- expect(normalized).toContain('position: fixed;')
113
- expect(normalized).toContain('&:empty { display: none; }')
114
- expect(normalized).toContain("&::after { clear: both; content: ''; display: table; }")
115
- expect(normalized).toContain('outline: 2px dashed orange;')
116
- expect(normalized).toContain('color: red;')
117
- expect(normalized).toContain('padding:')
118
- expect(normalized).toContain('1rem')
119
- })
120
- })