@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,537 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests verifying that styler correctly resolves the full
|
|
3
|
-
* CSS composition chain used by rocketstyle + unistyle + makeItResponsive.
|
|
4
|
-
*
|
|
5
|
-
* This tests the EXACT patterns used in production to identify where
|
|
6
|
-
* CSS properties like `position: absolute` might get lost.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { VNode } from '@pyreon/core'
|
|
10
|
-
import { h } from '@pyreon/core'
|
|
11
|
-
import { describe, expect, it } from 'vitest'
|
|
12
|
-
import { css } from '../css'
|
|
13
|
-
import { normalizeCSS, resolve, resolveValue } from '../resolve'
|
|
14
|
-
import { createSheet } from '../sheet'
|
|
15
|
-
import { styled } from '../styled'
|
|
16
|
-
|
|
17
|
-
// =====================================================================
|
|
18
|
-
// LAYER 1: resolve() with nested CSSResults — the raw resolution chain
|
|
19
|
-
// =====================================================================
|
|
20
|
-
|
|
21
|
-
describe('resolve composition chain', () => {
|
|
22
|
-
describe('CSSResult nesting (css-in-css)', () => {
|
|
23
|
-
it('resolves nested css`...` calls', () => {
|
|
24
|
-
const inner = css`
|
|
25
|
-
color: red;
|
|
26
|
-
`
|
|
27
|
-
const outer = css`
|
|
28
|
-
${inner} font-size: 16px;
|
|
29
|
-
`
|
|
30
|
-
const result = normalizeCSS(resolve(outer.strings, outer.values, {}))
|
|
31
|
-
expect(result).toContain('color: red;')
|
|
32
|
-
expect(result).toContain('font-size: 16px;')
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('resolves deeply nested css calls (3 levels)', () => {
|
|
36
|
-
const level3 = css`
|
|
37
|
-
position: absolute;
|
|
38
|
-
`
|
|
39
|
-
const level2 = css`
|
|
40
|
-
${level3} display: flex;
|
|
41
|
-
`
|
|
42
|
-
const level1 = css`
|
|
43
|
-
${level2} color: blue;
|
|
44
|
-
`
|
|
45
|
-
const result = normalizeCSS(resolve(level1.strings, level1.values, {}))
|
|
46
|
-
expect(result).toContain('position: absolute;')
|
|
47
|
-
expect(result).toContain('display: flex;')
|
|
48
|
-
expect(result).toContain('color: blue;')
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('resolves array of CSS strings (processDescriptor fragments)', () => {
|
|
52
|
-
// This mimics unistyle/styles/index.ts: fragments array from propertyMap
|
|
53
|
-
const fragments = [
|
|
54
|
-
'',
|
|
55
|
-
'',
|
|
56
|
-
'position: absolute;',
|
|
57
|
-
'',
|
|
58
|
-
'display: flex;',
|
|
59
|
-
'',
|
|
60
|
-
'height: 2.5rem;',
|
|
61
|
-
'',
|
|
62
|
-
]
|
|
63
|
-
const result = css`
|
|
64
|
-
${fragments}
|
|
65
|
-
`
|
|
66
|
-
const resolved = normalizeCSS(resolve(result.strings, result.values, {}))
|
|
67
|
-
expect(resolved).toContain('position: absolute;')
|
|
68
|
-
expect(resolved).toContain('display: flex;')
|
|
69
|
-
expect(resolved).toContain('height: 2.5rem;')
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('resolves CSSResult wrapping an array of fragments', () => {
|
|
73
|
-
// styles() returns css`${fragments}` where fragments is an array
|
|
74
|
-
const fragments = ['position: absolute;', '', 'color: red;']
|
|
75
|
-
const stylesResult = css`
|
|
76
|
-
${fragments}
|
|
77
|
-
`
|
|
78
|
-
// makeItResponsive wraps: css`${renderStyles(theme)}`
|
|
79
|
-
const mirResult = css`
|
|
80
|
-
${stylesResult}
|
|
81
|
-
`
|
|
82
|
-
const resolved = normalizeCSS(resolve(mirResult.strings, mirResult.values, {}))
|
|
83
|
-
expect(resolved).toContain('position: absolute;')
|
|
84
|
-
expect(resolved).toContain('color: red;')
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
describe('function interpolations (styled component render path)', () => {
|
|
89
|
-
it('resolves function that returns a CSSResult', () => {
|
|
90
|
-
const fn = (props: any) =>
|
|
91
|
-
css`
|
|
92
|
-
color: ${props.color};
|
|
93
|
-
`
|
|
94
|
-
const template = css`
|
|
95
|
-
${fn}
|
|
96
|
-
`
|
|
97
|
-
const result = normalizeCSS(resolve(template.strings, template.values, { color: 'red' }))
|
|
98
|
-
expect(result).toContain('color: red;')
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('resolves function that returns an array (makeItResponsive responsive path)', () => {
|
|
102
|
-
// makeItResponsive returns an array when breakpoints exist
|
|
103
|
-
const fn = () => [
|
|
104
|
-
css`
|
|
105
|
-
position: absolute;
|
|
106
|
-
`,
|
|
107
|
-
css`
|
|
108
|
-
@media (min-width: 36em) {
|
|
109
|
-
font-size: 2rem;
|
|
110
|
-
}
|
|
111
|
-
`,
|
|
112
|
-
]
|
|
113
|
-
const template = css`
|
|
114
|
-
${fn}
|
|
115
|
-
`
|
|
116
|
-
const result = normalizeCSS(resolve(template.strings, template.values, {}))
|
|
117
|
-
expect(result).toContain('position: absolute;')
|
|
118
|
-
expect(result).toContain('@media')
|
|
119
|
-
expect(result).toContain('font-size: 2rem;')
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('resolves function returning CSSResult containing another function', () => {
|
|
123
|
-
// This is the exact rocketstyle pattern:
|
|
124
|
-
// .styles((css) => css`${({$rocketstyle}) => { ... return css`${baseTheme};` }}`)
|
|
125
|
-
const innerFn = (props: any) => {
|
|
126
|
-
const theme = props.$rocketstyle
|
|
127
|
-
const fragments = [
|
|
128
|
-
theme.position ? `position: ${theme.position};` : '',
|
|
129
|
-
theme.display ? `display: ${theme.display};` : '',
|
|
130
|
-
]
|
|
131
|
-
return css`
|
|
132
|
-
${fragments}
|
|
133
|
-
`
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const outerResult = css`
|
|
137
|
-
font-weight: 500;
|
|
138
|
-
${innerFn};
|
|
139
|
-
`
|
|
140
|
-
|
|
141
|
-
const resolved = normalizeCSS(
|
|
142
|
-
resolve(outerResult.strings, outerResult.values, {
|
|
143
|
-
$rocketstyle: { position: 'absolute', display: 'flex' },
|
|
144
|
-
}),
|
|
145
|
-
)
|
|
146
|
-
expect(resolved).toContain('font-weight: 500;')
|
|
147
|
-
expect(resolved).toContain('position: absolute;')
|
|
148
|
-
expect(resolved).toContain('display: flex;')
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it('resolves the full rocketstyle+unistyle chain pattern', () => {
|
|
152
|
-
// Simulates the full chain:
|
|
153
|
-
// 1. processDescriptor generates CSS string fragments
|
|
154
|
-
// 2. styles() wraps them in css`${fragments}`
|
|
155
|
-
// 3. makeItResponsive wraps in css`${renderStyles(theme)}`
|
|
156
|
-
// 4. For responsive: media wrapper adds @media
|
|
157
|
-
// 5. Returns array of breakpoint results
|
|
158
|
-
// 6. .styles() callback wraps everything
|
|
159
|
-
|
|
160
|
-
const unistyleStyles = ({
|
|
161
|
-
theme: t,
|
|
162
|
-
css: cssFn,
|
|
163
|
-
}: {
|
|
164
|
-
theme: Record<string, any>
|
|
165
|
-
css: typeof css
|
|
166
|
-
}) => {
|
|
167
|
-
const fragments = [
|
|
168
|
-
t.position ? `position: ${t.position};` : '',
|
|
169
|
-
t.display ? `display: ${t.display};` : '',
|
|
170
|
-
t.height ? `height: ${t.height}rem;` : '',
|
|
171
|
-
t.fontSize ? `font-size: ${t.fontSize}rem;` : '',
|
|
172
|
-
t.backgroundColor ? `background-color: ${t.backgroundColor};` : '',
|
|
173
|
-
t.color ? `color: ${t.color};` : '',
|
|
174
|
-
]
|
|
175
|
-
return cssFn`${fragments}`
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Simulate makeItResponsive for non-responsive case
|
|
179
|
-
const makeItResponsiveNonBP = (config: {
|
|
180
|
-
theme: Record<string, any>
|
|
181
|
-
styles: typeof unistyleStyles
|
|
182
|
-
css: typeof css
|
|
183
|
-
}) => {
|
|
184
|
-
return () => {
|
|
185
|
-
const renderStyles = (t: Record<string, any>) =>
|
|
186
|
-
config.styles({ theme: t, css: config.css })
|
|
187
|
-
return config.css`
|
|
188
|
-
${renderStyles(config.theme)}
|
|
189
|
-
`
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Simulate .styles() callback
|
|
194
|
-
const stylesCb = (cssFn: typeof css) => {
|
|
195
|
-
return cssFn`
|
|
196
|
-
font-weight: 500;
|
|
197
|
-
${(props: any) => {
|
|
198
|
-
const rocketTheme = props.$rocketstyle
|
|
199
|
-
const baseTheme = makeItResponsiveNonBP({
|
|
200
|
-
theme: rocketTheme,
|
|
201
|
-
styles: unistyleStyles,
|
|
202
|
-
css: cssFn,
|
|
203
|
-
})
|
|
204
|
-
return cssFn`${baseTheme};`
|
|
205
|
-
}};
|
|
206
|
-
`
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// This is what calculateStyles produces
|
|
210
|
-
const stylesArray = [stylesCb(css)]
|
|
211
|
-
|
|
212
|
-
// This is the styled component template resolution
|
|
213
|
-
const templateStrings = Object.assign(['\n ', ';\n'], {
|
|
214
|
-
raw: ['\n ', ';\n'],
|
|
215
|
-
}) as unknown as TemplateStringsArray
|
|
216
|
-
|
|
217
|
-
const resolved = normalizeCSS(
|
|
218
|
-
resolve(templateStrings, [stylesArray], {
|
|
219
|
-
$rocketstyle: {
|
|
220
|
-
position: 'absolute',
|
|
221
|
-
display: 'flex',
|
|
222
|
-
height: 2.5,
|
|
223
|
-
backgroundColor: '#0070f3',
|
|
224
|
-
color: '#fff',
|
|
225
|
-
},
|
|
226
|
-
}),
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
expect(resolved).toContain('position: absolute;')
|
|
230
|
-
expect(resolved).toContain('display: flex;')
|
|
231
|
-
expect(resolved).toContain('height: 2.5rem;')
|
|
232
|
-
expect(resolved).toContain('background-color: #0070f3;')
|
|
233
|
-
expect(resolved).toContain('color: #fff;')
|
|
234
|
-
expect(resolved).toContain('font-weight: 500;')
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
it('resolves the full chain with responsive breakpoints', () => {
|
|
238
|
-
const unistyleStyles = ({
|
|
239
|
-
theme: t,
|
|
240
|
-
css: cssFn,
|
|
241
|
-
}: {
|
|
242
|
-
theme: Record<string, any>
|
|
243
|
-
css: typeof css
|
|
244
|
-
}) => {
|
|
245
|
-
const fragments = [
|
|
246
|
-
t.position ? `position: ${t.position};` : '',
|
|
247
|
-
t.height ? `height: ${t.height}rem;` : '',
|
|
248
|
-
]
|
|
249
|
-
return cssFn`${fragments}`
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Simulate createMediaQueries
|
|
253
|
-
const createMedia = (cssFn: typeof css, bps: Record<string, number>, rs: number) => {
|
|
254
|
-
const media: Record<string, (...args: any[]) => any> = {}
|
|
255
|
-
for (const [key, value] of Object.entries(bps)) {
|
|
256
|
-
if (value === 0) {
|
|
257
|
-
media[key] = (...args: any[]) => (cssFn as any)(...args)
|
|
258
|
-
} else {
|
|
259
|
-
const emSize = value / rs
|
|
260
|
-
media[key] = (...args: any[]) => cssFn`
|
|
261
|
-
@media only screen and (min-width: ${emSize}em) {
|
|
262
|
-
${(cssFn as any)(...args)};
|
|
263
|
-
}
|
|
264
|
-
`
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return media
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const breakpoints = { xs: 0, md: 768 }
|
|
271
|
-
const rootSize = 16
|
|
272
|
-
const media = createMedia(css, breakpoints, rootSize)
|
|
273
|
-
const sortedBreakpoints = ['xs', 'md']
|
|
274
|
-
|
|
275
|
-
// Simulate makeItResponsive with responsive path
|
|
276
|
-
const makeItResponsiveResp = (config: {
|
|
277
|
-
theme: Record<string, any>
|
|
278
|
-
styles: typeof unistyleStyles
|
|
279
|
-
css: typeof css
|
|
280
|
-
}) => {
|
|
281
|
-
return (_props: any) => {
|
|
282
|
-
const renderStyles = (t: Record<string, any>) =>
|
|
283
|
-
config.styles({ theme: t, css: config.css })
|
|
284
|
-
|
|
285
|
-
// After normalizeTheme + transformTheme + optimizeTheme:
|
|
286
|
-
// position: 'absolute' -> only first breakpoint
|
|
287
|
-
// height: { xs: 2.5, md: 5 } -> different per breakpoint
|
|
288
|
-
const optimizedTheme: Record<string, Record<string, any>> = {
|
|
289
|
-
xs: { position: 'absolute', height: 2.5 },
|
|
290
|
-
md: { height: 5 },
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return sortedBreakpoints.map((item) => {
|
|
294
|
-
const breakpointTheme = optimizedTheme[item]
|
|
295
|
-
if (!breakpointTheme || !media) return ''
|
|
296
|
-
const result = renderStyles(breakpointTheme)
|
|
297
|
-
return (media as Record<string, any>)[item]`
|
|
298
|
-
${result};
|
|
299
|
-
`
|
|
300
|
-
})
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const stylesCb = (cssFn: typeof css) => {
|
|
305
|
-
return cssFn`
|
|
306
|
-
${(props: any) => {
|
|
307
|
-
const rocketTheme = props.$rocketstyle
|
|
308
|
-
const baseTheme = makeItResponsiveResp({
|
|
309
|
-
theme: rocketTheme,
|
|
310
|
-
styles: unistyleStyles,
|
|
311
|
-
css: cssFn,
|
|
312
|
-
})
|
|
313
|
-
return cssFn`${baseTheme};`
|
|
314
|
-
}};
|
|
315
|
-
`
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const stylesArray = [stylesCb(css)]
|
|
319
|
-
|
|
320
|
-
const templateStrings = Object.assign(['\n ', ';\n'], {
|
|
321
|
-
raw: ['\n ', ';\n'],
|
|
322
|
-
}) as unknown as TemplateStringsArray
|
|
323
|
-
|
|
324
|
-
const resolved = normalizeCSS(
|
|
325
|
-
resolve(templateStrings, [stylesArray], {
|
|
326
|
-
$rocketstyle: { position: 'absolute', height: { xs: 2.5, md: 5 } },
|
|
327
|
-
}),
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
// Base breakpoint (xs) should have position + height
|
|
331
|
-
expect(resolved).toContain('position: absolute;')
|
|
332
|
-
expect(resolved).toContain('height: 2.5rem;')
|
|
333
|
-
|
|
334
|
-
// md breakpoint should be in @media
|
|
335
|
-
expect(resolved).toContain('@media')
|
|
336
|
-
expect(resolved).toContain('height: 5rem;')
|
|
337
|
-
})
|
|
338
|
-
})
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
// =====================================================================
|
|
342
|
-
// LAYER 2: styled component rendering — verify CSS injection + className
|
|
343
|
-
// =====================================================================
|
|
344
|
-
|
|
345
|
-
describe('styled component composition', () => {
|
|
346
|
-
it('handles array of functions as single interpolation (calculateStyles pattern)', () => {
|
|
347
|
-
// This is EXACTLY what rocketstyle does:
|
|
348
|
-
// styled(component, { layer: 'rocketstyle' })`${calculateStyles(styles)};`
|
|
349
|
-
// calculateStyles returns an array of function results
|
|
350
|
-
|
|
351
|
-
const fn1 = (props: any) => `position: ${props.$rocketstyle?.position ?? 'static'};`
|
|
352
|
-
const fn2 = (props: any) => `color: ${props.$rocketstyle?.color ?? 'inherit'};`
|
|
353
|
-
|
|
354
|
-
const Comp = styled('div')`
|
|
355
|
-
${[fn1, fn2]};
|
|
356
|
-
`
|
|
357
|
-
|
|
358
|
-
const vnode = Comp({ $rocketstyle: { position: 'absolute', color: 'red' } }) as VNode
|
|
359
|
-
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
it('handles function returning css`...` with nested function returning array', () => {
|
|
363
|
-
// This mimics the full .styles() -> makeItResponsive -> unistyle chain
|
|
364
|
-
const innerFn = (props: any) => {
|
|
365
|
-
const t = props.$rocketstyle
|
|
366
|
-
return [t.position ? `position: ${t.position};` : '', t.color ? `color: ${t.color};` : '']
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const outerCssResult = css`
|
|
370
|
-
font-weight: bold;
|
|
371
|
-
${innerFn};
|
|
372
|
-
`
|
|
373
|
-
|
|
374
|
-
const Comp = styled('div')`
|
|
375
|
-
${[outerCssResult]};
|
|
376
|
-
`
|
|
377
|
-
|
|
378
|
-
const vnode = Comp({ $rocketstyle: { position: 'absolute', color: 'blue' } }) as VNode
|
|
379
|
-
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
380
|
-
|
|
381
|
-
// Verify the CSS resolves correctly
|
|
382
|
-
const cssText = normalizeCSS(
|
|
383
|
-
resolve(outerCssResult.strings, outerCssResult.values, {
|
|
384
|
-
$rocketstyle: { position: 'absolute', color: 'blue' },
|
|
385
|
-
}),
|
|
386
|
-
)
|
|
387
|
-
expect(cssText).toContain('position: absolute;')
|
|
388
|
-
expect(cssText).toContain('color: blue;')
|
|
389
|
-
expect(cssText).toContain('font-weight: bold;')
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
it('handles css result wrapping a makeItResponsive-like function', () => {
|
|
393
|
-
// makeItResponsive returns a FUNCTION
|
|
394
|
-
// This function is used as interpolation in css`${baseTheme};`
|
|
395
|
-
// That css result is used as interpolation in css`${fn};`
|
|
396
|
-
// That css result is in an array from calculateStyles
|
|
397
|
-
|
|
398
|
-
const makeItResponsiveLike = (theme: Record<string, any>) => (_props: any) => {
|
|
399
|
-
const fragments = Object.entries(theme).map(([k, v]) => `${k}: ${v};`)
|
|
400
|
-
return css`
|
|
401
|
-
${fragments}
|
|
402
|
-
`
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const styleCallback = css`
|
|
406
|
-
font-weight: 500;
|
|
407
|
-
${(props: any) => {
|
|
408
|
-
const baseTheme = makeItResponsiveLike(props.$rocketstyle)
|
|
409
|
-
return css`
|
|
410
|
-
${baseTheme};
|
|
411
|
-
`
|
|
412
|
-
}};
|
|
413
|
-
`
|
|
414
|
-
|
|
415
|
-
const Comp = styled('div')`
|
|
416
|
-
${[styleCallback]};
|
|
417
|
-
`
|
|
418
|
-
|
|
419
|
-
const vnode = Comp({ $rocketstyle: { position: 'absolute', display: 'flex' } }) as VNode
|
|
420
|
-
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
421
|
-
|
|
422
|
-
// Resolve manually to verify CSS content
|
|
423
|
-
const cssText = normalizeCSS(
|
|
424
|
-
resolve(styleCallback.strings, styleCallback.values, {
|
|
425
|
-
$rocketstyle: { position: 'absolute', display: 'flex' },
|
|
426
|
-
}),
|
|
427
|
-
)
|
|
428
|
-
expect(cssText).toContain('position: absolute;')
|
|
429
|
-
expect(cssText).toContain('display: flex;')
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
it('wrapping a component: outer styled inherits inner className', () => {
|
|
433
|
-
// Inner is a Pyreon component wrapped by rocketstyle's styled()
|
|
434
|
-
const Inner = (props: { class?: string; $rocketstyle?: any; 'data-testid'?: string }) =>
|
|
435
|
-
h('div', { class: props.class, 'data-testid': 'inner' })
|
|
436
|
-
|
|
437
|
-
const Outer = styled(Inner)`
|
|
438
|
-
${(props: any) => {
|
|
439
|
-
const t = props.$rocketstyle || {}
|
|
440
|
-
return `position: ${t.position || 'static'};`
|
|
441
|
-
}};
|
|
442
|
-
`
|
|
443
|
-
|
|
444
|
-
const vnode = Outer({ $rocketstyle: { position: 'absolute' } }) as VNode
|
|
445
|
-
// Outer renders Inner, passing className
|
|
446
|
-
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
it('CSS output contains all properties from composition chain', () => {
|
|
450
|
-
const fragments = [
|
|
451
|
-
'position: absolute;',
|
|
452
|
-
'',
|
|
453
|
-
'display: flex;',
|
|
454
|
-
'height: 2.5rem;',
|
|
455
|
-
'',
|
|
456
|
-
'background-color: #0070f3;',
|
|
457
|
-
]
|
|
458
|
-
const cssText = normalizeCSS(
|
|
459
|
-
resolve(
|
|
460
|
-
css`
|
|
461
|
-
${fragments}
|
|
462
|
-
`.strings,
|
|
463
|
-
css`
|
|
464
|
-
${fragments}
|
|
465
|
-
`.values,
|
|
466
|
-
{},
|
|
467
|
-
),
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
// Verify the resolved CSS text contains all declarations
|
|
471
|
-
expect(cssText).toContain('position: absolute;')
|
|
472
|
-
expect(cssText).toContain('display: flex;')
|
|
473
|
-
expect(cssText).toContain('height: 2.5rem;')
|
|
474
|
-
expect(cssText).toContain('background-color: #0070f3;')
|
|
475
|
-
|
|
476
|
-
// Verify it can be inserted into a sheet
|
|
477
|
-
const s = createSheet()
|
|
478
|
-
const className = s.insert(cssText)
|
|
479
|
-
expect(className).toMatch(/^pyr-/)
|
|
480
|
-
})
|
|
481
|
-
|
|
482
|
-
it('handles the exact rocketstyle pattern with ThemeProvider context', () => {
|
|
483
|
-
// Full pattern: styled component -> function interpolation
|
|
484
|
-
// -> css result -> function -> css result -> array fragments
|
|
485
|
-
// Note: In VNode-level testing, we verify resolve output directly
|
|
486
|
-
// since ThemeProvider requires runtime context.
|
|
487
|
-
|
|
488
|
-
const innerFn = (props: any) => {
|
|
489
|
-
const t = props.$rocketstyle || {}
|
|
490
|
-
const fragments = [
|
|
491
|
-
t.position ? `position: ${t.position};` : '',
|
|
492
|
-
t.color ? `color: ${t.color};` : '',
|
|
493
|
-
t.fontSize ? `font-size: ${t.fontSize};` : '',
|
|
494
|
-
]
|
|
495
|
-
return css`
|
|
496
|
-
${fragments}
|
|
497
|
-
`
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
const Comp = styled('div')`
|
|
501
|
-
${(props: any) => {
|
|
502
|
-
const t = props.$rocketstyle || {}
|
|
503
|
-
const fragments = [
|
|
504
|
-
t.position ? `position: ${t.position};` : '',
|
|
505
|
-
t.color ? `color: ${t.color};` : '',
|
|
506
|
-
t.fontSize ? `font-size: ${t.fontSize};` : '',
|
|
507
|
-
]
|
|
508
|
-
return css`
|
|
509
|
-
${fragments}
|
|
510
|
-
`
|
|
511
|
-
}};
|
|
512
|
-
`
|
|
513
|
-
|
|
514
|
-
const vnode = Comp({
|
|
515
|
-
$rocketstyle: {
|
|
516
|
-
position: 'absolute',
|
|
517
|
-
color: '#fff',
|
|
518
|
-
fontSize: '14px',
|
|
519
|
-
},
|
|
520
|
-
}) as VNode
|
|
521
|
-
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
522
|
-
|
|
523
|
-
// Also verify the CSS text resolves correctly
|
|
524
|
-
const resolved = normalizeCSS(
|
|
525
|
-
resolveValue(innerFn, {
|
|
526
|
-
$rocketstyle: {
|
|
527
|
-
position: 'absolute',
|
|
528
|
-
color: '#fff',
|
|
529
|
-
fontSize: '14px',
|
|
530
|
-
},
|
|
531
|
-
}),
|
|
532
|
-
)
|
|
533
|
-
expect(resolved).toContain('position: absolute;')
|
|
534
|
-
expect(resolved).toContain('color: #fff;')
|
|
535
|
-
expect(resolved).toContain('font-size: 14px;')
|
|
536
|
-
})
|
|
537
|
-
})
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest"
|
|
2
|
-
import { css } from "../css"
|
|
3
|
-
import { CSSResult } from "../resolve"
|
|
4
|
-
|
|
5
|
-
describe("css", () => {
|
|
6
|
-
it("returns a CSSResult instance", () => {
|
|
7
|
-
const result = css`color: red;`
|
|
8
|
-
expect(result).toBeInstanceOf(CSSResult)
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it("captures template strings", () => {
|
|
12
|
-
const result = css`color: red;`
|
|
13
|
-
expect(result.strings[0]).toBe("color: red;")
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it("captures interpolation values", () => {
|
|
17
|
-
const color = "blue"
|
|
18
|
-
const result = css`color: ${color};`
|
|
19
|
-
expect(result.values).toEqual(["blue"])
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it("captures function interpolations without calling them", () => {
|
|
23
|
-
const fn = () => "red"
|
|
24
|
-
const result = css`color: ${fn};`
|
|
25
|
-
expect(result.values[0]).toBe(fn)
|
|
26
|
-
expect(typeof result.values[0]).toBe("function")
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it("works when called as a regular function", () => {
|
|
30
|
-
const strings = Object.assign(["color: ", ";"], {
|
|
31
|
-
raw: ["color: ", ";"],
|
|
32
|
-
}) as TemplateStringsArray
|
|
33
|
-
const result = css(strings, "red")
|
|
34
|
-
expect(result).toBeInstanceOf(CSSResult)
|
|
35
|
-
expect(result.values).toEqual(["red"])
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it("supports nesting css results", () => {
|
|
39
|
-
const inner = css`color: red;`
|
|
40
|
-
const outer = css`${inner} display: flex;`
|
|
41
|
-
expect(outer.values[0]).toBeInstanceOf(CSSResult)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it("handles multiple interpolations", () => {
|
|
45
|
-
const result = css`color: ${"red"}; font-size: ${16}px;`
|
|
46
|
-
expect(result.values).toEqual(["red", 16])
|
|
47
|
-
expect(result.strings.length).toBe(3)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it("handles null/undefined/boolean interpolations lazily", () => {
|
|
51
|
-
const n = null
|
|
52
|
-
const u = undefined
|
|
53
|
-
const f = false
|
|
54
|
-
const t = true
|
|
55
|
-
const result = css`a${n}b${u}c${f}d${t}e`
|
|
56
|
-
expect(result.values).toEqual([null, undefined, false, true])
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it("captures numeric interpolations", () => {
|
|
60
|
-
const result = css`flex: ${1};`
|
|
61
|
-
expect(result.values[0]).toBe(1)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it("empty template returns CSSResult with one empty string", () => {
|
|
65
|
-
const result = css``
|
|
66
|
-
expect(result).toBeInstanceOf(CSSResult)
|
|
67
|
-
expect(result.strings.length).toBe(1)
|
|
68
|
-
expect(result.values.length).toBe(0)
|
|
69
|
-
})
|
|
70
|
-
})
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
2
|
-
import { tmpdir } from 'node:os'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { fileURLToPath } from 'node:url'
|
|
5
|
-
import { describe, expect, it } from 'vitest'
|
|
6
|
-
import { build } from 'vite'
|
|
7
|
-
|
|
8
|
-
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
-
const SRC = path.resolve(here, '..')
|
|
10
|
-
|
|
11
|
-
// Bundle-level regression test for the styler dev-gate fix.
|
|
12
|
-
//
|
|
13
|
-
// Background — the shape of the problem:
|
|
14
|
-
// `process.env.NODE_ENV !== 'production'` is dead code in real Vite
|
|
15
|
-
// browser bundles because Vite does not polyfill `process`. The
|
|
16
|
-
// `console.warn` calls inside the gate were silently dropped in
|
|
17
|
-
// production, which masked malformed-CSS bugs (insertRule failures
|
|
18
|
-
// produced no diagnostic — empty <style> tag, classes assigned, no
|
|
19
|
-
// console output).
|
|
20
|
-
//
|
|
21
|
-
// The fix is to use bundler-agnostic `process.env.NODE_ENV !== 'production'`
|
|
22
|
-
// — every modern bundler auto-replaces `process.env.NODE_ENV` at consumer
|
|
23
|
-
// build time. This test bundles `sheet.ts` through Vite's production build
|
|
24
|
-
// and asserts the dev-warning strings are GONE. It also bundles in dev
|
|
25
|
-
// mode and asserts the strings are PRESENT, so a source-level deletion
|
|
26
|
-
// can't trivially pass the prod test.
|
|
27
|
-
//
|
|
28
|
-
// Mirrors `packages/core/runtime-dom/src/tests/dev-gate-treeshake.test.ts`.
|
|
29
|
-
|
|
30
|
-
const DEV_WARNING_STRINGS = [
|
|
31
|
-
'[styler] Failed to insert CSS rule:',
|
|
32
|
-
'[styler] Failed to insert @keyframes rule:',
|
|
33
|
-
'[styler] Failed to insert global CSS rule:',
|
|
34
|
-
]
|
|
35
|
-
|
|
36
|
-
async function bundleWithVite(entry: string, dev: boolean): Promise<string> {
|
|
37
|
-
const outDir = mkdtempSync(path.join(tmpdir(), 'pyreon-styler-treeshake-'))
|
|
38
|
-
try {
|
|
39
|
-
await build({
|
|
40
|
-
mode: dev ? 'development' : 'production',
|
|
41
|
-
logLevel: 'error',
|
|
42
|
-
configFile: false,
|
|
43
|
-
resolve: { conditions: ['bun'] },
|
|
44
|
-
define: {
|
|
45
|
-
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production'),
|
|
46
|
-
},
|
|
47
|
-
build: {
|
|
48
|
-
// PINNED minifier — see runtime-dom's tree-shake test for rationale.
|
|
49
|
-
minify: dev ? false : 'esbuild',
|
|
50
|
-
target: 'esnext',
|
|
51
|
-
write: true,
|
|
52
|
-
outDir,
|
|
53
|
-
emptyOutDir: true,
|
|
54
|
-
lib: {
|
|
55
|
-
entry,
|
|
56
|
-
formats: ['es'],
|
|
57
|
-
fileName: 'out',
|
|
58
|
-
},
|
|
59
|
-
rollupOptions: {
|
|
60
|
-
external: [],
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
})
|
|
64
|
-
return readFileSync(path.join(outDir, 'out.js'), 'utf8')
|
|
65
|
-
} finally {
|
|
66
|
-
rmSync(outDir, { recursive: true, force: true })
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
describe('styler dev-warning gate (Vite production bundle)', () => {
|
|
71
|
-
it('sheet.ts → dev warnings eliminated in Vite production bundle', async () => {
|
|
72
|
-
const code = await bundleWithVite(path.join(SRC, 'sheet.ts'), false)
|
|
73
|
-
for (const warn of DEV_WARNING_STRINGS) {
|
|
74
|
-
expect(code, `"${warn}" survived prod tree-shake`).not.toContain(warn)
|
|
75
|
-
}
|
|
76
|
-
expect(code.length).toBeGreaterThan(0)
|
|
77
|
-
}, 10000)
|
|
78
|
-
|
|
79
|
-
it('sheet.ts → dev warnings PRESERVED in Vite dev bundle (sanity)', async () => {
|
|
80
|
-
const code = await bundleWithVite(path.join(SRC, 'sheet.ts'), true)
|
|
81
|
-
for (const warn of DEV_WARNING_STRINGS) {
|
|
82
|
-
expect(code, `"${warn}" missing from dev bundle (did source change?)`).toContain(warn)
|
|
83
|
-
}
|
|
84
|
-
}, 10000)
|
|
85
|
-
})
|