@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,659 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
- import { hash } from '../hash'
3
- import { createSheet, StyleSheet } from '../sheet'
4
-
5
- describe('StyleSheet -- advanced features', () => {
6
- describe('getClassName (pure hash computation)', () => {
7
- it('returns className without inserting a rule', () => {
8
- const s = createSheet()
9
- const cls = s.getClassName('display: flex;')
10
- expect(cls).toMatch(/^pyr-[0-9a-z]+$/)
11
- })
12
-
13
- it('returns same className as insert for same CSS', () => {
14
- const s = createSheet()
15
- const clsPure = s.getClassName('display: flex;')
16
- const clsInsert = s.insert('display: flex;')
17
- expect(clsPure).toBe(clsInsert)
18
- })
19
-
20
- it('matches hash-based className', () => {
21
- const s = createSheet()
22
- const cssText = 'display: flex;'
23
- expect(s.getClassName(cssText)).toBe(`pyr-${hash(cssText)}`)
24
- })
25
- })
26
-
27
- describe('bounded cache (eviction) -- DOM mode', () => {
28
- beforeEach(() => {
29
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
30
- el.remove()
31
- })
32
- })
33
-
34
- it('still deduplicates within cache bounds', () => {
35
- const s = createSheet({ maxCacheSize: 100 })
36
- const cls1 = s.insert('color: red;')
37
- const cls2 = s.insert('color: red;')
38
- expect(cls1).toBe(cls2)
39
- })
40
-
41
- it('cacheSize property reflects current cache size', () => {
42
- const s = createSheet()
43
- expect(s.cacheSize).toBe(0)
44
-
45
- s.insert('color: red;')
46
- expect(s.cacheSize).toBe(1)
47
-
48
- s.insert('color: blue;')
49
- expect(s.cacheSize).toBe(2)
50
-
51
- // Duplicate doesn't increase size
52
- s.insert('color: red;')
53
- expect(s.cacheSize).toBe(2)
54
- })
55
-
56
- it('evicts oldest entries when max cache size is exceeded', () => {
57
- const s = createSheet({ maxCacheSize: 10 })
58
-
59
- for (let i = 0; i < 15; i++) {
60
- s.insert(`prop-${i}: val-${i};`)
61
- }
62
-
63
- expect(s.cacheSize).toBeLessThanOrEqual(15)
64
- })
65
-
66
- it('accepts custom maxCacheSize option', () => {
67
- const s = createSheet({ maxCacheSize: 5 })
68
-
69
- for (let i = 0; i < 20; i++) {
70
- s.insert(`prop${i}: val${i};`)
71
- }
72
-
73
- expect(s.cacheSize).toBeLessThanOrEqual(20)
74
- })
75
- })
76
-
77
- describe('dev-mode warnings', () => {
78
- it('warns on invalid CSS in dev mode', () => {
79
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
80
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
81
- el.remove()
82
- })
83
-
84
- const s = new StyleSheet()
85
- s.insert('invalid{{{css')
86
-
87
- warnSpy.mockRestore()
88
- })
89
- })
90
-
91
- describe('insertGlobal -- multi-rule splitting', () => {
92
- beforeEach(() => {
93
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
94
- el.remove()
95
- })
96
- })
97
-
98
- it('injects multiple top-level rules from a single insertGlobal call', () => {
99
- const s = createSheet()
100
- s.insertGlobal('html { font-size: 16px; } body { margin: 0; }')
101
- // Should not throw -- both rules are injected individually
102
- expect(s.cacheSize).toBe(1) // single cache entry for the whole CSS text
103
- })
104
-
105
- it('injects nested @media rules correctly', () => {
106
- const s = createSheet()
107
- s.insertGlobal('body { margin: 0; } @media (min-width: 768px) { body { font-size: 18px; } }')
108
- expect(s.cacheSize).toBe(1)
109
- })
110
-
111
- it('handles three or more rules', () => {
112
- const s = createSheet()
113
- s.insertGlobal(
114
- 'html { box-sizing: border-box; } *, *::before, *::after { box-sizing: inherit; } body { margin: 0; font-family: sans-serif; }',
115
- )
116
- expect(s.cacheSize).toBe(1)
117
- })
118
-
119
- it('deduplicates identical multi-rule CSS', () => {
120
- const s = createSheet()
121
- const cssStr = 'html { font-size: 16px; } body { margin: 0; }'
122
- s.insertGlobal(cssStr)
123
- s.insertGlobal(cssStr)
124
- expect(s.cacheSize).toBe(1)
125
- })
126
-
127
- it('inserts multiple rules into the CSSOM via splitRules and insertRule', () => {
128
- const s = createSheet()
129
- s.insertGlobal('body{margin:0}div{padding:0}')
130
-
131
- // Verify rules were actually inserted into the CSSOM
132
- const styleEl = document.querySelector('style[data-pyreon-styler]') as HTMLStyleElement
133
- const cssSheet = styleEl.sheet as CSSStyleSheet
134
- const ruleTexts = Array.from(cssSheet.cssRules).map((r) => r.cssText)
135
- expect(ruleTexts.some((r) => r.includes('margin'))).toBe(true)
136
- expect(ruleTexts.some((r) => r.includes('padding'))).toBe(true)
137
- })
138
-
139
- it('deduplicates insertGlobal -- second call with same CSS is a no-op', () => {
140
- const s = createSheet()
141
- s.insertGlobal('body{margin:0}div{padding:0}')
142
-
143
- const styleEl = document.querySelector('style[data-pyreon-styler]') as HTMLStyleElement
144
- const cssSheet = styleEl.sheet as CSSStyleSheet
145
- const countBefore = cssSheet.cssRules.length
146
-
147
- // Second call should be deduped via cache
148
- s.insertGlobal('body{margin:0}div{padding:0}')
149
- expect(cssSheet.cssRules.length).toBe(countBefore)
150
- })
151
-
152
- it('inserts global CSS with @media into CSSOM', () => {
153
- const s = createSheet()
154
- s.insertGlobal('body{margin:0}@media (min-width:768px){body{font-size:18px}}')
155
-
156
- const styleEl = document.querySelector('style[data-pyreon-styler]') as HTMLStyleElement
157
- const cssSheet = styleEl.sheet as CSSStyleSheet
158
- const ruleTexts = Array.from(cssSheet.cssRules).map((r) => r.cssText)
159
- expect(ruleTexts.some((r) => r.includes('margin'))).toBe(true)
160
- expect(ruleTexts.some((r) => r.includes('media'))).toBe(true)
161
- })
162
- })
163
-
164
- describe('@layer on client-side insert', () => {
165
- beforeEach(() => {
166
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
167
- el.remove()
168
- })
169
- })
170
-
171
- it('wraps inserted rules in @layer on client side', () => {
172
- const s = createSheet({ layer: 'components' })
173
- const className = s.insert('color: red;')
174
- expect(className).toMatch(/^pyr-/)
175
- // The sheet should have injected a @layer declaration + the wrapped rule
176
- })
177
-
178
- it('injects @layer declaration rule on mount', () => {
179
- const s = createSheet({ layer: 'myLayer' })
180
- s.insert('display: flex;')
181
- // No crash -- @layer declaration was injected at mount time
182
- expect(s.cacheSize).toBe(1)
183
- })
184
- })
185
-
186
- describe('hydration from existing SSR style tag', () => {
187
- beforeEach(() => {
188
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
189
- el.remove()
190
- })
191
- })
192
-
193
- it('reuses existing <style data-pyreon-styler> tag and hydrates cache', () => {
194
- // Create a style tag that simulates SSR output
195
- const el = document.createElement('style')
196
- el.setAttribute('data-pyreon-styler', '')
197
- document.head.appendChild(el)
198
-
199
- // Insert a rule directly so hydrateFromTag can discover it
200
- const sheetRef = el.sheet
201
- if (sheetRef) sheetRef.insertRule('.pyr-abc { color: red; }', 0)
202
-
203
- // Create a new sheet -- it should find and reuse the existing tag
204
- const s = createSheet()
205
- // The hydration should have populated the cache with 'pyr-abc'
206
- expect(s.has('pyr-abc')).toBe(true)
207
- })
208
-
209
- it('hydrates @media-wrapped rules from SSR style tag', () => {
210
- const el = document.createElement('style')
211
- el.setAttribute('data-pyreon-styler', '')
212
- document.head.appendChild(el)
213
-
214
- const sheetRef = el.sheet
215
- if (sheetRef) {
216
- sheetRef.insertRule('@media (min-width: 768px) { .pyr-xyz { font-size: 2rem; } }', 0)
217
- }
218
-
219
- const s = createSheet()
220
- expect(s.has('pyr-xyz')).toBe(true)
221
- })
222
- })
223
-
224
- describe('prepare()', () => {
225
- it('produces single selector', () => {
226
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
227
- el.remove()
228
- })
229
- const s = createSheet()
230
- const result = s.prepare('color: red;')
231
- expect(result.className).toMatch(/^pyr-/)
232
- // Single selector, no doubling
233
- expect(result.rules).toContain(`.${result.className}{`)
234
- expect(result.rules).not.toContain(`.${result.className}.${result.className}`)
235
- })
236
-
237
- it('prepare with empty base -- all CSS is @rules', () => {
238
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
239
- el.remove()
240
- })
241
- const s = createSheet()
242
- // CSS that is entirely an @media rule, so base is empty after splitting
243
- const result = s.prepare('@media(min-width:0){color:red}')
244
- expect(result.className).toMatch(/^pyr-/)
245
- // Should contain the @media rule but NOT a bare selector rule (no base)
246
- expect(result.rules).toContain('@media')
247
- expect(result.rules).toContain('color:red')
248
- // The rules should NOT start with a plain selector -- no base rule emitted
249
- expect(result.rules).not.toMatch(new RegExp(`^\\.${result.className}\\{`))
250
- })
251
-
252
- it('prepare with only @supports produces no base rule', () => {
253
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
254
- el.remove()
255
- })
256
- const s = createSheet()
257
- const result = s.prepare(
258
- '@supports(display:grid){display:grid}@media(min-width:0){color:red}',
259
- )
260
- expect(result.className).toMatch(/^pyr-/)
261
- expect(result.rules).toContain('@supports')
262
- expect(result.rules).toContain('@media')
263
- // No base selector rule
264
- expect(result.rules).not.toMatch(new RegExp(`^\\.${result.className}\\{`))
265
- })
266
- })
267
-
268
- describe('layer on insert()', () => {
269
- beforeEach(() => {
270
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
271
- el.remove()
272
- })
273
- })
274
-
275
- it('inserts rule with layer on client side', () => {
276
- const s = createSheet()
277
- const cls = s.insert('color: red;', false, 'rocketstyle')
278
- expect(cls).toMatch(/^pyr-/)
279
- })
280
-
281
- it('deduplicates layered and non-layered separately via insertCache key', () => {
282
- const s = createSheet()
283
- const cls1 = s.insert('color: green;', false)
284
- const cls2 = s.insert('color: green;', false, 'rocketstyle')
285
- // Same className (same hash) but both should work without error
286
- expect(cls1).toBe(cls2)
287
- })
288
- })
289
-
290
- describe('clearAll() with rules', () => {
291
- beforeEach(() => {
292
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
293
- el.remove()
294
- })
295
- })
296
-
297
- it('removes all CSS rules from the DOM sheet', () => {
298
- const s = createSheet()
299
- s.insert('color: red;')
300
- s.insert('color: blue;')
301
- expect(s.cacheSize).toBe(2)
302
-
303
- s.clearAll()
304
- expect(s.cacheSize).toBe(0)
305
- })
306
-
307
- it('allows re-insertion after clearAll', () => {
308
- const s = createSheet()
309
- s.insert('color: red;')
310
- s.clearAll()
311
- // Re-insert the same CSS -- should work since cache was cleared
312
- const cls = s.insert('color: red;')
313
- expect(cls).toMatch(/^pyr-/)
314
- expect(s.cacheSize).toBe(1)
315
- })
316
- })
317
-
318
- describe('clearCache()', () => {
319
- beforeEach(() => {
320
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
321
- el.remove()
322
- })
323
- })
324
-
325
- it('clears the dedup cache but leaves DOM rules in place', () => {
326
- const s = createSheet()
327
- s.insert('color: red;')
328
- expect(s.cacheSize).toBe(1)
329
-
330
- s.clearCache()
331
- expect(s.cacheSize).toBe(0)
332
- })
333
- })
334
-
335
- describe('insertRule failure warning', () => {
336
- beforeEach(() => {
337
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
338
- el.remove()
339
- })
340
- })
341
-
342
- it('warns in dev mode when insertRule fails for insert()', () => {
343
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
344
- const s = createSheet()
345
- // Insert invalid CSS that will cause insertRule to throw
346
- s.insert('color: red; @INVALID_RULE {{{')
347
- warnSpy.mockRestore()
348
- })
349
-
350
- it('warns in dev mode when insertGlobal insertRule fails', () => {
351
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
352
- const s = createSheet()
353
- // Insert intentionally malformed global CSS
354
- s.insertGlobal('@INVALID {{{')
355
- warnSpy.mockRestore()
356
- })
357
-
358
- it('warns in dev mode when insertKeyframes insertRule fails', () => {
359
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
360
- const s = createSheet()
361
- s.insertKeyframes('bad', '{{{invalid')
362
- warnSpy.mockRestore()
363
- })
364
- })
365
-
366
- describe('insert() insertRule failure via mock', () => {
367
- beforeEach(() => {
368
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
369
- el.remove()
370
- })
371
- })
372
-
373
- it('warns when CSSStyleSheet.insertRule throws during insert()', () => {
374
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
375
- const s = createSheet()
376
-
377
- // Access the internal sheet and make insertRule throw
378
- const styleEl = document.querySelector('style[data-pyreon-styler]') as HTMLStyleElement
379
- const realSheet = styleEl.sheet
380
- if (!realSheet) throw new Error('Expected sheet to exist')
381
- const origInsertRule = realSheet.insertRule.bind(realSheet)
382
- realSheet.insertRule = (rule: string, index?: number) => {
383
- // Let @layer declaration through (if any), fail on component rules
384
- if (rule.startsWith('.pyr-')) {
385
- throw new Error('Mock insertRule failure')
386
- }
387
- return origInsertRule(rule, index)
388
- }
389
-
390
- s.insert('color: magenta;')
391
-
392
- expect(warnSpy).toHaveBeenCalled()
393
- expect(warnSpy.mock.calls[0]?.[0]).toContain('[styler] Failed to insert CSS rule')
394
-
395
- realSheet.insertRule = origInsertRule
396
- warnSpy.mockRestore()
397
- })
398
- })
399
-
400
- describe('insertGlobal insertRule failure via mock', () => {
401
- beforeEach(() => {
402
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
403
- el.remove()
404
- })
405
- })
406
-
407
- it('warns when CSSStyleSheet.insertRule throws during insertGlobal()', () => {
408
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
409
- const s = createSheet()
410
-
411
- const styleEl = document.querySelector('style[data-pyreon-styler]') as HTMLStyleElement
412
- const realSheet = styleEl.sheet
413
- if (!realSheet) throw new Error('Expected sheet to exist')
414
- const origInsertRule = realSheet.insertRule.bind(realSheet)
415
- realSheet.insertRule = (_rule: string, _index?: number) => {
416
- throw new Error('Mock insertGlobal failure')
417
- }
418
-
419
- s.insertGlobal('body { margin: 0; }')
420
-
421
- expect(warnSpy).toHaveBeenCalled()
422
- expect(warnSpy.mock.calls[0]?.[0]).toContain('[styler] Failed to insert global CSS rule')
423
-
424
- realSheet.insertRule = origInsertRule
425
- warnSpy.mockRestore()
426
- })
427
- })
428
-
429
- describe('getClassName with insertCache populated', () => {
430
- beforeEach(() => {
431
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
432
- el.remove()
433
- })
434
- })
435
-
436
- it('returns cached className from insertCache after insert()', () => {
437
- const s = createSheet()
438
- const cls1 = s.insert('color: purple;')
439
- // getClassName should hit the insertCache
440
- const cls2 = s.getClassName('color: purple;')
441
- expect(cls1).toBe(cls2)
442
- })
443
- })
444
-
445
- // SSR-specific tests: mock document as undefined to simulate server
446
- describe('SSR mode (mocked)', () => {
447
- let originalDocument: typeof document
448
-
449
- beforeEach(() => {
450
- originalDocument = globalThis.document
451
- // Temporarily remove document to simulate SSR
452
- // @ts-expect-error - intentionally deleting for SSR simulation
453
- delete globalThis.document
454
- })
455
-
456
- afterEach(() => {
457
- globalThis.document = originalDocument
458
- })
459
-
460
- it('creates independent StyleSheet instances in SSR', () => {
461
- const sheet1 = createSheet()
462
- const sheet2 = createSheet()
463
-
464
- sheet1.insert('color: red;')
465
- expect(sheet1.getStyles()).toContain('color: red;')
466
- expect(sheet2.getStyles()).toBe('')
467
- })
468
-
469
- it('each instance has its own SSR buffer', () => {
470
- const sheet1 = createSheet()
471
- const sheet2 = createSheet()
472
-
473
- sheet1.insert('color: red;')
474
- sheet2.insert('color: blue;')
475
-
476
- expect(sheet1.getStyles()).toContain('color: red;')
477
- expect(sheet1.getStyles()).not.toContain('color: blue;')
478
- expect(sheet2.getStyles()).toContain('color: blue;')
479
- expect(sheet2.getStyles()).not.toContain('color: red;')
480
- })
481
-
482
- it('reset only clears the buffer of that instance', () => {
483
- const sheet1 = createSheet()
484
- const sheet2 = createSheet()
485
-
486
- sheet1.insert('color: red;')
487
- sheet2.insert('color: blue;')
488
-
489
- sheet1.reset()
490
- expect(sheet1.getStyles()).toBe('')
491
- expect(sheet2.getStyles()).toContain('color: blue;')
492
- })
493
-
494
- it('each instance has its own dedup cache', () => {
495
- const sheet1 = createSheet()
496
- const sheet2 = createSheet()
497
-
498
- const cls1 = sheet1.insert('display: flex;')
499
- const cls2 = sheet2.insert('display: flex;')
500
-
501
- // Same CSS -> same class name (deterministic hashing)
502
- expect(cls1).toBe(cls2)
503
-
504
- // Both inject independently
505
- expect(sheet1.getStyles()).toContain('display: flex;')
506
- expect(sheet2.getStyles()).toContain('display: flex;')
507
- })
508
-
509
- describe('concurrent SSR simulation', () => {
510
- it('handles concurrent requests without cross-contamination', () => {
511
- const request1 = createSheet()
512
- const request2 = createSheet()
513
-
514
- request1.insert('color: red;')
515
- request1.insertGlobal('body { margin: 0; }')
516
-
517
- request2.insert('color: blue;')
518
- request2.insertGlobal('body { padding: 0; }')
519
-
520
- const html1 = request1.getStyleTag()
521
- const html2 = request2.getStyleTag()
522
-
523
- expect(html1).toContain('color: red;')
524
- expect(html1).not.toContain('color: blue;')
525
- expect(html1).toContain('body { margin: 0; }')
526
- expect(html1).not.toContain('body { padding: 0; }')
527
-
528
- expect(html2).toContain('color: blue;')
529
- expect(html2).not.toContain('color: red;')
530
- expect(html2).toContain('body { padding: 0; }')
531
- expect(html2).not.toContain('body { margin: 0; }')
532
- })
533
-
534
- it('handles many concurrent requests', () => {
535
- const requests = Array.from({ length: 50 }, (_, i) => {
536
- const s = createSheet()
537
- s.insert(`color: color-${i};`)
538
- return s
539
- })
540
-
541
- for (let i = 0; i < requests.length; i++) {
542
- const styles = requests[i]?.getStyles()
543
- expect(styles).toContain(`color: color-${i};`)
544
- const otherIdx = (i + 1) % requests.length
545
- expect(styles).not.toContain(`color: color-${otherIdx};`)
546
- }
547
- })
548
- })
549
-
550
- describe('@layer support', () => {
551
- it('wraps inserted rules in @layer when configured', () => {
552
- const s = createSheet({ layer: 'components' })
553
-
554
- s.insert('color: red;')
555
- const styles = s.getStyles()
556
-
557
- expect(styles).toContain('@layer components')
558
- expect(styles).toContain('color: red;')
559
- })
560
-
561
- it('does not wrap rules without layer option', () => {
562
- const s = createSheet()
563
-
564
- s.insert('color: red;')
565
- const styles = s.getStyles()
566
-
567
- expect(styles).not.toContain('@layer')
568
- expect(styles).toContain('color: red;')
569
- })
570
-
571
- it('does not wrap @keyframes in @layer', () => {
572
- const s = createSheet({ layer: 'components' })
573
-
574
- s.insertKeyframes('fadeIn', 'from { opacity: 0; } to { opacity: 1; }')
575
- const styles = s.getStyles()
576
-
577
- expect(styles).toContain('@keyframes fadeIn')
578
- // Keyframes are not wrapped in a layer block (layer declaration before them is fine)
579
- expect(styles).not.toMatch(/@layer\s+\w+\s*\{[^}]*@keyframes/)
580
- })
581
-
582
- it('does not wrap global rules in @layer', () => {
583
- const s = createSheet({ layer: 'components' })
584
-
585
- s.insertGlobal('body { margin: 0; }')
586
- const styles = s.getStyles()
587
-
588
- expect(styles).toContain('body { margin: 0; }')
589
- expect(styles).not.toMatch(/@layer components\{body/)
590
- })
591
-
592
- it('prepare() wraps rules in @layer when configured', () => {
593
- const s = createSheet({ layer: 'components' })
594
- const result = s.prepare('color: red;')
595
- expect(result.rules).toContain('@layer components')
596
- expect(result.rules).toContain('color: red;')
597
- })
598
-
599
- it('prepare() wraps in @layer with layer option', () => {
600
- const s = createSheet({ layer: 'lib' })
601
- const result = s.prepare('color: blue;')
602
- expect(result.rules).toContain('@layer lib')
603
- expect(result.rules).toContain(`.${result.className}`)
604
- })
605
- })
606
-
607
- describe('SSR output', () => {
608
- it('getStyleTag returns complete <style> tag', () => {
609
- const s = createSheet()
610
- s.insert('color: red;')
611
- const tag = s.getStyleTag()
612
-
613
- expect(tag).toMatch(/^<style data-pyreon-styler="">.*<\/style>$/)
614
- expect(tag).toContain('color: red;')
615
- })
616
-
617
- it('getStyles returns raw CSS', () => {
618
- const s = createSheet()
619
- s.insert('color: red;')
620
- s.insert('color: blue;')
621
- const styles = s.getStyles()
622
-
623
- expect(styles).not.toContain('<style')
624
- expect(styles).toContain('color: red;')
625
- expect(styles).toContain('color: blue;')
626
- })
627
-
628
- it('reset clears SSR buffer and cache', () => {
629
- const s = createSheet()
630
- s.insert('color: red;')
631
- expect(s.getStyles()).toContain('color: red;')
632
-
633
- s.reset()
634
- expect(s.getStyles()).toBe('')
635
- expect(s.cacheSize).toBe(0) // cache also cleared for SSR correctness
636
- })
637
-
638
- it('insertKeyframes deduplicates in SSR', () => {
639
- const s = createSheet()
640
- s.insertKeyframes('fadeIn', 'from { opacity: 0; } to { opacity: 1; }')
641
- s.insertKeyframes('fadeIn', 'from { opacity: 0; } to { opacity: 1; }')
642
-
643
- const styles = s.getStyles()
644
- const matches = styles.match(/@keyframes fadeIn/g)
645
- expect(matches).toHaveLength(1)
646
- })
647
-
648
- it('insertGlobal deduplicates in SSR', () => {
649
- const s = createSheet()
650
- s.insertGlobal('body { margin: 0; }')
651
- s.insertGlobal('body { margin: 0; }')
652
-
653
- const styles = s.getStyles()
654
- const matches = styles.match(/body \{ margin: 0; \}/g)
655
- expect(matches).toHaveLength(1)
656
- })
657
- })
658
- })
659
- })