@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.
- package/package.json +7 -9
- package/src/__tests__/alignContent.test.ts +0 -121
- package/src/__tests__/borderRadius.test.ts +0 -125
- package/src/__tests__/camelToKebab.test.ts +0 -44
- package/src/__tests__/context.test.ts +0 -147
- package/src/__tests__/createMediaQueries.test.ts +0 -98
- package/src/__tests__/edge.test.ts +0 -164
- package/src/__tests__/enrichTheme.test.ts +0 -56
- package/src/__tests__/extendCss.test.ts +0 -45
- package/src/__tests__/index.test.ts +0 -79
- package/src/__tests__/makeItResponsive.test.ts +0 -431
- package/src/__tests__/manifest-snapshot.test.ts +0 -34
- package/src/__tests__/native-marker.test.ts +0 -9
- package/src/__tests__/optimizeBreakpointDeltas.test.ts +0 -124
- package/src/__tests__/processDescriptor.test.ts +0 -322
- package/src/__tests__/responsive.test.ts +0 -221
- package/src/__tests__/special-keys.test.ts +0 -120
- package/src/__tests__/styles.test.ts +0 -273
- package/src/__tests__/unistyle.browser.test.tsx +0 -169
- package/src/__tests__/units.test.ts +0 -134
- package/src/context.tsx +0 -44
- package/src/enrichTheme.ts +0 -42
- package/src/env.d.ts +0 -6
- package/src/index.ts +0 -91
- package/src/manifest.ts +0 -197
- package/src/responsive/breakpoints.ts +0 -15
- package/src/responsive/createMediaQueries.ts +0 -43
- package/src/responsive/index.ts +0 -15
- package/src/responsive/makeItResponsive.ts +0 -223
- package/src/responsive/normalizeTheme.ts +0 -79
- package/src/responsive/optimizeBreakpointDeltas.ts +0 -190
- package/src/responsive/optimizeTheme.ts +0 -60
- package/src/responsive/sortBreakpoints.ts +0 -10
- package/src/responsive/transformTheme.ts +0 -54
- package/src/styles/alignContent.ts +0 -62
- package/src/styles/extendCss.ts +0 -26
- package/src/styles/index.ts +0 -16
- package/src/styles/shorthands/borderRadius.ts +0 -89
- package/src/styles/shorthands/edge.ts +0 -108
- package/src/styles/shorthands/index.ts +0 -4
- package/src/styles/styles/camelToKebab.ts +0 -3
- package/src/styles/styles/index.ts +0 -132
- package/src/styles/styles/processDescriptor.ts +0 -136
- package/src/styles/styles/propertyMap.ts +0 -438
- package/src/styles/styles/types.ts +0 -368
- package/src/types.ts +0 -175
- package/src/units/index.ts +0 -6
- package/src/units/stripUnit.ts +0 -25
- package/src/units/value.ts +0 -47
- package/src/units/values.ts +0 -40
|
@@ -1,431 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
vi.mock('@pyreon/ui-core', () => ({
|
|
4
|
-
isEmpty: (val: unknown) =>
|
|
5
|
-
val == null || (typeof val === 'object' && Object.keys(val as object).length === 0),
|
|
6
|
-
set: (obj: any, path: (string | number)[], value: unknown) => {
|
|
7
|
-
let current = obj
|
|
8
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
9
|
-
const key = path[i] as string | number
|
|
10
|
-
if (current[key] == null || typeof current[key] !== 'object') {
|
|
11
|
-
current[key] = {}
|
|
12
|
-
}
|
|
13
|
-
current = current[key]
|
|
14
|
-
}
|
|
15
|
-
const lastKey = path[path.length - 1] as string | number
|
|
16
|
-
current[lastKey] = value
|
|
17
|
-
},
|
|
18
|
-
}))
|
|
19
|
-
|
|
20
|
-
import makeItResponsive from '../responsive/makeItResponsive'
|
|
21
|
-
|
|
22
|
-
const mockCss = (strings: TemplateStringsArray, ...vals: any[]) => {
|
|
23
|
-
let r = ''
|
|
24
|
-
for (let i = 0; i < strings.length; i++) {
|
|
25
|
-
r += strings[i]
|
|
26
|
-
if (i < vals.length) r += String(vals[i])
|
|
27
|
-
}
|
|
28
|
-
return r
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const mockStyles = ({ theme }: { theme: Record<string, unknown> }) => {
|
|
32
|
-
return Object.entries(theme)
|
|
33
|
-
.map(([k, v]) => `${k}: ${v};`)
|
|
34
|
-
.join(' ')
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
describe('makeItResponsive', () => {
|
|
38
|
-
it('returns empty string when customTheme is empty/undefined', () => {
|
|
39
|
-
const responsive = makeItResponsive({
|
|
40
|
-
key: 'styles',
|
|
41
|
-
css: mockCss,
|
|
42
|
-
styles: mockStyles,
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const result = responsive({ theme: {} })
|
|
46
|
-
expect(result).toBe('')
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('returns empty string when customTheme is empty object', () => {
|
|
50
|
-
const responsive = makeItResponsive({
|
|
51
|
-
theme: {},
|
|
52
|
-
key: 'styles',
|
|
53
|
-
css: mockCss,
|
|
54
|
-
styles: mockStyles,
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const result = responsive({ theme: {} })
|
|
58
|
-
expect(result).toBe('')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('without breakpoints: wraps styles output in css template', () => {
|
|
62
|
-
const responsive = makeItResponsive({
|
|
63
|
-
theme: { color: 'red' },
|
|
64
|
-
css: mockCss,
|
|
65
|
-
styles: mockStyles,
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
const result = responsive({ theme: {} })
|
|
69
|
-
expect(result).toContain('color: red;')
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('uses props[key] when theme is not provided', () => {
|
|
73
|
-
const responsive = makeItResponsive({
|
|
74
|
-
key: 'myStyles',
|
|
75
|
-
css: mockCss,
|
|
76
|
-
styles: mockStyles,
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
const result = responsive({
|
|
80
|
-
theme: {},
|
|
81
|
-
myStyles: { fontSize: '16px' },
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
expect(result).toContain('fontSize: 16px;')
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('with breakpoints and __PYREON__: returns array of media-wrapped styles per breakpoint', () => {
|
|
88
|
-
const sortedBreakpoints = ['xs', 'sm']
|
|
89
|
-
const media: Record<string, (strings: TemplateStringsArray, ...vals: any[]) => string> = {
|
|
90
|
-
xs: mockCss,
|
|
91
|
-
sm: (strings: TemplateStringsArray, ...vals: any[]) => {
|
|
92
|
-
let r = '@media (min-width: 36em) {'
|
|
93
|
-
for (let i = 0; i < strings.length; i++) {
|
|
94
|
-
r += strings[i]
|
|
95
|
-
if (i < vals.length) r += String(vals[i])
|
|
96
|
-
}
|
|
97
|
-
r += '}'
|
|
98
|
-
return r
|
|
99
|
-
},
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const responsive = makeItResponsive({
|
|
103
|
-
theme: { color: { xs: 'red', sm: 'blue' } },
|
|
104
|
-
css: mockCss,
|
|
105
|
-
styles: mockStyles,
|
|
106
|
-
normalize: true,
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
const result = responsive({
|
|
110
|
-
theme: {
|
|
111
|
-
breakpoints: { xs: 0, sm: 576 },
|
|
112
|
-
__PYREON__: { sortedBreakpoints, media },
|
|
113
|
-
},
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
expect(Array.isArray(result)).toBe(true)
|
|
117
|
-
expect(result).toHaveLength(2)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('caching: second call with same internalTheme object returns same result', () => {
|
|
121
|
-
const sortedBreakpoints = ['xs', 'sm']
|
|
122
|
-
const media: Record<string, (strings: TemplateStringsArray, ...vals: any[]) => string> = {
|
|
123
|
-
xs: mockCss,
|
|
124
|
-
sm: mockCss,
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const themeObj = { color: { xs: 'red', sm: 'blue' } }
|
|
128
|
-
|
|
129
|
-
const responsive = makeItResponsive({
|
|
130
|
-
theme: themeObj,
|
|
131
|
-
css: mockCss,
|
|
132
|
-
styles: mockStyles,
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
const globalTheme = {
|
|
136
|
-
breakpoints: { xs: 0, sm: 576 },
|
|
137
|
-
__PYREON__: { sortedBreakpoints, media },
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const result1 = responsive({ theme: globalTheme })
|
|
141
|
-
const result2 = responsive({ theme: globalTheme })
|
|
142
|
-
|
|
143
|
-
expect(result1).toEqual(result2)
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('normalize=false skips normalizeTheme step', () => {
|
|
147
|
-
const sortedBreakpoints = ['xs', 'sm']
|
|
148
|
-
const media: Record<string, (strings: TemplateStringsArray, ...vals: any[]) => string> = {
|
|
149
|
-
xs: mockCss,
|
|
150
|
-
sm: mockCss,
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// When normalize=false, object values are not expanded across breakpoints.
|
|
154
|
-
// Provide a pre-normalized theme (object keyed by breakpoint names).
|
|
155
|
-
const responsive = makeItResponsive({
|
|
156
|
-
theme: { color: { xs: 'red', sm: 'blue' } },
|
|
157
|
-
css: mockCss,
|
|
158
|
-
styles: mockStyles,
|
|
159
|
-
normalize: false,
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
const result = responsive({
|
|
163
|
-
theme: {
|
|
164
|
-
breakpoints: { xs: 0, sm: 576 },
|
|
165
|
-
__PYREON__: { sortedBreakpoints, media },
|
|
166
|
-
},
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
expect(Array.isArray(result)).toBe(true)
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
describe('delta optimization (mirrors vitus-labs)', () => {
|
|
173
|
-
it('strips re-emitted unchanged declarations across breakpoints', () => {
|
|
174
|
-
// mockStyles emits `color: red; padding: 0;` at xs and the same color
|
|
175
|
-
// with a different padding at sm. The delta optimizer should drop
|
|
176
|
-
// `color: red` from sm because it's already cascaded from xs via
|
|
177
|
-
// `@media (min-width: …)`.
|
|
178
|
-
const sortedBreakpoints = ['xs', 'sm']
|
|
179
|
-
const captured: Record<string, string> = {}
|
|
180
|
-
const media: Record<string, (s: TemplateStringsArray, ...v: any[]) => string> = {
|
|
181
|
-
xs: (s, ...vals) => {
|
|
182
|
-
let out = ''
|
|
183
|
-
for (let i = 0; i < s.length; i++) {
|
|
184
|
-
out += s[i]
|
|
185
|
-
if (i < vals.length) out += String(vals[i])
|
|
186
|
-
}
|
|
187
|
-
captured.xs = out
|
|
188
|
-
return out
|
|
189
|
-
},
|
|
190
|
-
sm: (s, ...vals) => {
|
|
191
|
-
let out = ''
|
|
192
|
-
for (let i = 0; i < s.length; i++) {
|
|
193
|
-
out += s[i]
|
|
194
|
-
if (i < vals.length) out += String(vals[i])
|
|
195
|
-
}
|
|
196
|
-
captured.sm = out
|
|
197
|
-
return out
|
|
198
|
-
},
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const responsive = makeItResponsive({
|
|
202
|
-
theme: { color: { xs: 'red', sm: 'red' }, padding: { xs: '0', sm: '1rem' } },
|
|
203
|
-
css: mockCss,
|
|
204
|
-
styles: mockStyles,
|
|
205
|
-
normalize: true,
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
responsive({
|
|
209
|
-
theme: {
|
|
210
|
-
breakpoints: { xs: 0, sm: 576 },
|
|
211
|
-
__PYREON__: { sortedBreakpoints, media },
|
|
212
|
-
},
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
// xs sees full output
|
|
216
|
-
expect(captured.xs).toContain('color: red;')
|
|
217
|
-
expect(captured.xs).toContain('padding: 0;')
|
|
218
|
-
// sm sees only the delta — color is in cascade already
|
|
219
|
-
expect(captured.sm).toContain('padding: 1rem;')
|
|
220
|
-
expect(captured.sm).not.toContain('color:')
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
it('skips the media-template call entirely when a breakpoint has no deltas', () => {
|
|
224
|
-
const sortedBreakpoints = ['xs', 'sm']
|
|
225
|
-
let xsCalls = 0
|
|
226
|
-
let smCalls = 0
|
|
227
|
-
const media: Record<string, (s: TemplateStringsArray, ...v: any[]) => string> = {
|
|
228
|
-
xs: (s, ...vals) => {
|
|
229
|
-
xsCalls++
|
|
230
|
-
let out = ''
|
|
231
|
-
for (let i = 0; i < s.length; i++) {
|
|
232
|
-
out += s[i]
|
|
233
|
-
if (i < vals.length) out += String(vals[i])
|
|
234
|
-
}
|
|
235
|
-
return out
|
|
236
|
-
},
|
|
237
|
-
sm: (s, ...vals) => {
|
|
238
|
-
smCalls++
|
|
239
|
-
let out = ''
|
|
240
|
-
for (let i = 0; i < s.length; i++) {
|
|
241
|
-
out += s[i]
|
|
242
|
-
if (i < vals.length) out += String(vals[i])
|
|
243
|
-
}
|
|
244
|
-
return out
|
|
245
|
-
},
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const responsive = makeItResponsive({
|
|
249
|
-
// Identical values at both breakpoints — sm produces zero deltas.
|
|
250
|
-
theme: { color: { xs: 'red', sm: 'red' } },
|
|
251
|
-
css: mockCss,
|
|
252
|
-
styles: mockStyles,
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
const result = responsive({
|
|
256
|
-
theme: {
|
|
257
|
-
breakpoints: { xs: 0, sm: 576 },
|
|
258
|
-
__PYREON__: { sortedBreakpoints, media },
|
|
259
|
-
},
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
// xs renders (has content); sm produces no @media wrapper at all
|
|
263
|
-
expect(xsCalls).toBe(1)
|
|
264
|
-
expect(smCalls).toBe(0)
|
|
265
|
-
// sm slot is the empty-string sentinel
|
|
266
|
-
expect((result as unknown[])[1]).toBe('')
|
|
267
|
-
})
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
describe('stringify fallback (mirrors vitus-labs)', () => {
|
|
271
|
-
// Where the two paths diverge observably: `styles` is invoked
|
|
272
|
-
// ONCE per breakpoint in the optimized path (for stringification),
|
|
273
|
-
// and TWICE per breakpoint in the fallback path (once for
|
|
274
|
-
// stringify which returns null, then again to re-render against
|
|
275
|
-
// the engine for the @media wrapper). With 2 breakpoints that's
|
|
276
|
-
// 2 calls vs 4 calls — clean observable distinction.
|
|
277
|
-
|
|
278
|
-
it('takes the unoptimized path when styles result has [object Foo] toString', () => {
|
|
279
|
-
// Foreign-engine result whose default toString is `[object ForeignResult]`.
|
|
280
|
-
// stringifyResult returns null → canOptimize=false → fallback path.
|
|
281
|
-
class ForeignResult {
|
|
282
|
-
constructor(public payload: string) {}
|
|
283
|
-
// Default toString → "[object ForeignResult]"
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const sortedBreakpoints = ['xs', 'sm']
|
|
287
|
-
const media: Record<string, (s: TemplateStringsArray, ...v: any[]) => string> = {
|
|
288
|
-
xs: mockCss,
|
|
289
|
-
sm: mockCss,
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
let stylesCalls = 0
|
|
293
|
-
const stylesReturningForeign = ({ theme }: { theme: Record<string, unknown> }) => {
|
|
294
|
-
stylesCalls++
|
|
295
|
-
return new ForeignResult(JSON.stringify(theme))
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Distinct values per breakpoint so optimizeTheme keeps both keys
|
|
299
|
-
// (identical values get deduplicated upstream and never reach the
|
|
300
|
-
// optimization-vs-fallback split).
|
|
301
|
-
const responsive = makeItResponsive({
|
|
302
|
-
theme: { color: { xs: 'red', sm: 'blue' } },
|
|
303
|
-
css: mockCss,
|
|
304
|
-
styles: stylesReturningForeign as any,
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
responsive({
|
|
308
|
-
theme: {
|
|
309
|
-
breakpoints: { xs: 0, sm: 576 },
|
|
310
|
-
__PYREON__: { sortedBreakpoints, media },
|
|
311
|
-
},
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
// Fallback signature: stringify pass + re-render pass = 2 calls per bp
|
|
315
|
-
expect(stylesCalls).toBe(4)
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
it('takes the optimized path for plain-string styles results', () => {
|
|
319
|
-
const sortedBreakpoints = ['xs', 'sm']
|
|
320
|
-
const media: Record<string, (s: TemplateStringsArray, ...v: any[]) => string> = {
|
|
321
|
-
xs: mockCss,
|
|
322
|
-
sm: mockCss,
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
let stylesCalls = 0
|
|
326
|
-
const stylesCountingMock = (args: { theme: Record<string, unknown> }) => {
|
|
327
|
-
stylesCalls++
|
|
328
|
-
return mockStyles(args)
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const responsive = makeItResponsive({
|
|
332
|
-
theme: { color: { xs: 'red', sm: 'blue' } },
|
|
333
|
-
css: mockCss,
|
|
334
|
-
styles: stylesCountingMock,
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
responsive({
|
|
338
|
-
theme: {
|
|
339
|
-
breakpoints: { xs: 0, sm: 576 },
|
|
340
|
-
__PYREON__: { sortedBreakpoints, media },
|
|
341
|
-
},
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
// Optimized signature: stringify pass only = 1 call per bp
|
|
345
|
-
expect(stylesCalls).toBe(2)
|
|
346
|
-
})
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
describe('render-output cache (mirrors vitus-labs)', () => {
|
|
350
|
-
it('returns the same rendered output by reference when called twice with stable theme + internal-theme refs', () => {
|
|
351
|
-
const sortedBreakpoints = ['xs', 'sm']
|
|
352
|
-
let xsCalls = 0
|
|
353
|
-
const media: Record<string, (s: TemplateStringsArray, ...v: any[]) => string> = {
|
|
354
|
-
xs: (s, ...vals) => {
|
|
355
|
-
xsCalls++
|
|
356
|
-
let out = ''
|
|
357
|
-
for (let i = 0; i < s.length; i++) {
|
|
358
|
-
out += s[i]
|
|
359
|
-
if (i < vals.length) out += String(vals[i])
|
|
360
|
-
}
|
|
361
|
-
return out
|
|
362
|
-
},
|
|
363
|
-
sm: mockCss,
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const themeObj = { color: { xs: 'red', sm: 'blue' } }
|
|
367
|
-
const globalTheme = {
|
|
368
|
-
breakpoints: { xs: 0, sm: 576 },
|
|
369
|
-
__PYREON__: { sortedBreakpoints, media },
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const responsive = makeItResponsive({
|
|
373
|
-
theme: themeObj,
|
|
374
|
-
css: mockCss,
|
|
375
|
-
styles: mockStyles,
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
const result1 = responsive({ theme: globalTheme })
|
|
379
|
-
const xsCallsAfterFirst = xsCalls
|
|
380
|
-
const result2 = responsive({ theme: globalTheme })
|
|
381
|
-
|
|
382
|
-
// Same identity means the rendered cache hit (no re-rendering)
|
|
383
|
-
expect(result2).toBe(result1)
|
|
384
|
-
// Render cache hit means the media template was NOT called again
|
|
385
|
-
expect(xsCalls).toBe(xsCallsAfterFirst)
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
it('re-renders when the outer theme reference changes (e.g. provider value swap)', () => {
|
|
389
|
-
const sortedBreakpoints = ['xs', 'sm']
|
|
390
|
-
let xsCalls = 0
|
|
391
|
-
const media: Record<string, (s: TemplateStringsArray, ...v: any[]) => string> = {
|
|
392
|
-
xs: (s, ...vals) => {
|
|
393
|
-
xsCalls++
|
|
394
|
-
let out = ''
|
|
395
|
-
for (let i = 0; i < s.length; i++) {
|
|
396
|
-
out += s[i]
|
|
397
|
-
if (i < vals.length) out += String(vals[i])
|
|
398
|
-
}
|
|
399
|
-
return out
|
|
400
|
-
},
|
|
401
|
-
sm: mockCss,
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const themeObj = { color: { xs: 'red', sm: 'blue' } }
|
|
405
|
-
|
|
406
|
-
const responsive = makeItResponsive({
|
|
407
|
-
theme: themeObj,
|
|
408
|
-
css: mockCss,
|
|
409
|
-
styles: mockStyles,
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
// Two distinct outer-theme objects with the same content
|
|
413
|
-
const globalA = {
|
|
414
|
-
breakpoints: { xs: 0, sm: 576 },
|
|
415
|
-
__PYREON__: { sortedBreakpoints, media },
|
|
416
|
-
}
|
|
417
|
-
const globalB = {
|
|
418
|
-
breakpoints: { xs: 0, sm: 576 },
|
|
419
|
-
__PYREON__: { sortedBreakpoints, media },
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
responsive({ theme: globalA })
|
|
423
|
-
const callsAfterA = xsCalls
|
|
424
|
-
responsive({ theme: globalB })
|
|
425
|
-
|
|
426
|
-
// New outer theme object → no render cache hit → media template re-runs
|
|
427
|
-
expect(xsCalls).toBeGreaterThan(callsAfterA)
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
})
|
|
431
|
-
})
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
renderApiReferenceEntries,
|
|
3
|
-
renderLlmsFullSection,
|
|
4
|
-
renderLlmsTxtLine,
|
|
5
|
-
} from '@pyreon/manifest'
|
|
6
|
-
import manifest from '../manifest'
|
|
7
|
-
|
|
8
|
-
describe('gen-docs — unistyle snapshot', () => {
|
|
9
|
-
it('renders a llms.txt bullet starting with the package prefix', () => {
|
|
10
|
-
const line = renderLlmsTxtLine(manifest)
|
|
11
|
-
expect(line.startsWith('- @pyreon/unistyle —')).toBe(true)
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('renders a llms-full.txt section with the right header', () => {
|
|
15
|
-
const section = renderLlmsFullSection(manifest)
|
|
16
|
-
expect(section.startsWith('## @pyreon/unistyle —')).toBe(true)
|
|
17
|
-
expect(section).toContain('```typescript')
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('renders MCP api-reference entries for every api[] item', () => {
|
|
21
|
-
const record = renderApiReferenceEntries(manifest)
|
|
22
|
-
expect(Object.keys(record).sort()).toEqual([
|
|
23
|
-
'unistyle/alignContent',
|
|
24
|
-
'unistyle/breakpoints',
|
|
25
|
-
'unistyle/createMediaQueries',
|
|
26
|
-
'unistyle/enrichTheme',
|
|
27
|
-
'unistyle/extendCss',
|
|
28
|
-
'unistyle/makeItResponsive',
|
|
29
|
-
'unistyle/stripUnit',
|
|
30
|
-
'unistyle/styles',
|
|
31
|
-
'unistyle/value',
|
|
32
|
-
])
|
|
33
|
-
})
|
|
34
|
-
})
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { isNativeCompat } from '@pyreon/core'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
import UnistyleProvider from '../context'
|
|
4
|
-
|
|
5
|
-
describe('native-compat marker — @pyreon/unistyle', () => {
|
|
6
|
-
it('Provider is marked native', () => {
|
|
7
|
-
expect(isNativeCompat(UnistyleProvider)).toBe(true)
|
|
8
|
-
})
|
|
9
|
-
})
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { optimizeBreakpointDeltas } from '../responsive'
|
|
3
|
-
|
|
4
|
-
describe('optimizeBreakpointDeltas', () => {
|
|
5
|
-
describe('cascade pruning', () => {
|
|
6
|
-
it('returns input unchanged when there is one or fewer breakpoints', () => {
|
|
7
|
-
expect(optimizeBreakpointDeltas([])).toEqual([])
|
|
8
|
-
expect(optimizeBreakpointDeltas(['color: red;'])).toEqual(['color: red;'])
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it('strips re-emitted unchanged declarations from later breakpoints', () => {
|
|
12
|
-
const out = optimizeBreakpointDeltas([
|
|
13
|
-
'color: red; padding: 0;',
|
|
14
|
-
'color: red; padding: 1rem;',
|
|
15
|
-
])
|
|
16
|
-
expect(out[0]).toBe('color: red; padding: 0;')
|
|
17
|
-
// `color: red` was already in the cascade — only padding survives
|
|
18
|
-
expect(out[1]).toBe('padding: 1rem;')
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('keeps changed declarations across multiple breakpoints', () => {
|
|
22
|
-
const out = optimizeBreakpointDeltas([
|
|
23
|
-
'color: red; font-size: 12px;',
|
|
24
|
-
'color: blue; font-size: 12px;',
|
|
25
|
-
'color: blue; font-size: 16px;',
|
|
26
|
-
])
|
|
27
|
-
expect(out[0]).toBe('color: red; font-size: 12px;')
|
|
28
|
-
expect(out[1]).toBe('color: blue;')
|
|
29
|
-
expect(out[2]).toBe('font-size: 16px;')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('emits empty string when a later breakpoint adds no deltas', () => {
|
|
33
|
-
const out = optimizeBreakpointDeltas([
|
|
34
|
-
'color: red; padding: 0;',
|
|
35
|
-
'color: red; padding: 0;',
|
|
36
|
-
])
|
|
37
|
-
expect(out[0]).toBe('color: red; padding: 0;')
|
|
38
|
-
expect(out[1]).toBe('')
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('passes through empty / null breakpoints unchanged', () => {
|
|
42
|
-
const out = optimizeBreakpointDeltas(['color: red;', '', 'color: blue;'])
|
|
43
|
-
expect(out[0]).toBe('color: red;')
|
|
44
|
-
expect(out[1]).toBe('')
|
|
45
|
-
expect(out[2]).toBe('color: blue;')
|
|
46
|
-
})
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
describe('parser edge cases', () => {
|
|
50
|
-
it('skips colons inside parens (linear-gradient args)', () => {
|
|
51
|
-
const out = optimizeBreakpointDeltas([
|
|
52
|
-
'background: linear-gradient(red 0%, blue 100%);',
|
|
53
|
-
'background: linear-gradient(red 0%, blue 100%);',
|
|
54
|
-
])
|
|
55
|
-
expect(out[0]).toBe('background: linear-gradient(red 0%, blue 100%);')
|
|
56
|
-
// Same value cascades — delta is empty
|
|
57
|
-
expect(out[1]).toBe('')
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('skips semicolons inside quoted strings (content: ";")', () => {
|
|
61
|
-
const out = optimizeBreakpointDeltas([
|
|
62
|
-
`content: ";"; color: red;`,
|
|
63
|
-
`content: ";"; color: blue;`,
|
|
64
|
-
])
|
|
65
|
-
// Both declarations parsed correctly on bp1; bp2 only color delta
|
|
66
|
-
expect(out[0]).toContain(`content: ";";`)
|
|
67
|
-
expect(out[0]).toContain('color: red;')
|
|
68
|
-
expect(out[1]).toBe('color: blue;')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('treats nested selector blocks as opaque, deduped by exact text', () => {
|
|
72
|
-
const out = optimizeBreakpointDeltas([
|
|
73
|
-
'&:hover { color: red; } padding: 0;',
|
|
74
|
-
'&:hover { color: red; } padding: 1rem;',
|
|
75
|
-
])
|
|
76
|
-
// The hover block dedupes; padding delta survives
|
|
77
|
-
expect(out[1]).not.toContain('&:hover')
|
|
78
|
-
expect(out[1]).toContain('padding: 1rem;')
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('keeps differently-shaped nested blocks across breakpoints', () => {
|
|
82
|
-
const out = optimizeBreakpointDeltas([
|
|
83
|
-
'&:hover { color: red; }',
|
|
84
|
-
'&:hover { color: blue; }',
|
|
85
|
-
])
|
|
86
|
-
expect(out[0]).toContain('&:hover { color: red; }')
|
|
87
|
-
// Different inner text → not deduped
|
|
88
|
-
expect(out[1]).toContain('&:hover { color: blue; }')
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('handles trailing declarations with no terminating semicolon', () => {
|
|
92
|
-
const out = optimizeBreakpointDeltas(['color: red', 'color: blue'])
|
|
93
|
-
expect(out[0]).toBe('color: red;')
|
|
94
|
-
expect(out[1]).toBe('color: blue;')
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('preserves @supports / @media-style nested blocks as opaque blocks', () => {
|
|
98
|
-
const out = optimizeBreakpointDeltas([
|
|
99
|
-
'@supports (display: grid) { display: grid; }',
|
|
100
|
-
'@supports (display: grid) { display: grid; } color: red;',
|
|
101
|
-
])
|
|
102
|
-
expect(out[0]).toBe('@supports (display: grid) { display: grid; }')
|
|
103
|
-
// @supports block dedupes; color is new
|
|
104
|
-
expect(out[1]).toBe('color: red;')
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('keeps shorthand and longhand decls separately (no shorthand modeling)', () => {
|
|
108
|
-
const out = optimizeBreakpointDeltas([
|
|
109
|
-
'padding: 1rem;',
|
|
110
|
-
'padding-top: 0;',
|
|
111
|
-
])
|
|
112
|
-
// Different `prop` keys → both retained
|
|
113
|
-
expect(out[0]).toBe('padding: 1rem;')
|
|
114
|
-
expect(out[1]).toBe('padding-top: 0;')
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('keeps malformed declaration-shaped fragments without losing them', () => {
|
|
118
|
-
const out = optimizeBreakpointDeltas([':abc;', ':abc;'])
|
|
119
|
-
// No prop name (starts with `:`) → kept as opaque block; deduped on bp2
|
|
120
|
-
expect(out[0]).toBe(':abc;')
|
|
121
|
-
expect(out[1]).toBe('')
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
})
|