@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.
Files changed (47) hide show
  1. package/package.json +5 -7
  2. package/src/ThemeProvider.ts +0 -65
  3. package/src/__tests__/ThemeProvider.test.ts +0 -67
  4. package/src/__tests__/benchmark.bench.ts +0 -200
  5. package/src/__tests__/composition-chain.test.ts +0 -537
  6. package/src/__tests__/css.test.ts +0 -70
  7. package/src/__tests__/dev-gate-treeshake.test.ts +0 -85
  8. package/src/__tests__/forward.test.ts +0 -282
  9. package/src/__tests__/globalStyle.test.ts +0 -72
  10. package/src/__tests__/hash.test.ts +0 -70
  11. package/src/__tests__/hybrid-injection.test.ts +0 -225
  12. package/src/__tests__/index.ts +0 -14
  13. package/src/__tests__/inject-rules.browser.test.ts +0 -40
  14. package/src/__tests__/insertion-effect.test.ts +0 -119
  15. package/src/__tests__/integration-dom.test.ts +0 -58
  16. package/src/__tests__/integration.test.ts +0 -179
  17. package/src/__tests__/keyframes.test.ts +0 -68
  18. package/src/__tests__/memory-growth.test.ts +0 -220
  19. package/src/__tests__/native-marker.test.ts +0 -9
  20. package/src/__tests__/p3-features.test.ts +0 -316
  21. package/src/__tests__/resolve-cache.test.ts +0 -94
  22. package/src/__tests__/resolve.test.ts +0 -308
  23. package/src/__tests__/shared.test.ts +0 -133
  24. package/src/__tests__/sheet-advanced.test.ts +0 -659
  25. package/src/__tests__/sheet-split-atrules.test.ts +0 -410
  26. package/src/__tests__/sheet.test.ts +0 -250
  27. package/src/__tests__/static-styler-resolve-cost.test.ts +0 -160
  28. package/src/__tests__/styled-reactive.test.ts +0 -74
  29. package/src/__tests__/styled-ssr.test.ts +0 -75
  30. package/src/__tests__/styled.test.ts +0 -511
  31. package/src/__tests__/styler.browser.test.tsx +0 -194
  32. package/src/__tests__/theme.test.ts +0 -33
  33. package/src/__tests__/useCSS.test.ts +0 -172
  34. package/src/css.ts +0 -13
  35. package/src/env.d.ts +0 -6
  36. package/src/forward.ts +0 -308
  37. package/src/globalStyle.ts +0 -53
  38. package/src/hash.ts +0 -28
  39. package/src/index.ts +0 -15
  40. package/src/keyframes.ts +0 -36
  41. package/src/manifest.ts +0 -332
  42. package/src/resolve.ts +0 -225
  43. package/src/shared.ts +0 -22
  44. package/src/sheet.ts +0 -635
  45. package/src/styled.tsx +0 -503
  46. package/src/tests/manifest-snapshot.test.ts +0 -51
  47. 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
- })