@pyreon/styler 0.11.5 → 0.11.7

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 (36) hide show
  1. package/README.md +27 -23
  2. package/lib/index.d.ts +9 -2
  3. package/lib/index.js +49 -4
  4. package/package.json +22 -22
  5. package/src/ThemeProvider.ts +10 -3
  6. package/src/__tests__/ThemeProvider.test.ts +21 -21
  7. package/src/__tests__/benchmark.bench.ts +56 -45
  8. package/src/__tests__/composition-chain.test.ts +200 -151
  9. package/src/__tests__/forward.test.ts +122 -122
  10. package/src/__tests__/globalStyle.test.ts +18 -18
  11. package/src/__tests__/hash.test.ts +27 -27
  12. package/src/__tests__/hybrid-injection.test.ts +83 -59
  13. package/src/__tests__/index.ts +10 -10
  14. package/src/__tests__/insertion-effect.test.ts +45 -32
  15. package/src/__tests__/integration.test.ts +81 -51
  16. package/src/__tests__/keyframes.test.ts +13 -13
  17. package/src/__tests__/memory-growth.test.ts +21 -21
  18. package/src/__tests__/p3-features.test.ts +162 -104
  19. package/src/__tests__/shared.test.ts +51 -33
  20. package/src/__tests__/sheet-advanced.test.ts +227 -227
  21. package/src/__tests__/sheet-split-atrules.test.ts +85 -85
  22. package/src/__tests__/sheet.test.ts +69 -69
  23. package/src/__tests__/styled-ssr.test.ts +36 -28
  24. package/src/__tests__/styled.test.ts +214 -145
  25. package/src/__tests__/theme.test.ts +11 -11
  26. package/src/__tests__/useCSS.test.ts +89 -59
  27. package/src/css.ts +1 -1
  28. package/src/forward.ts +187 -187
  29. package/src/globalStyle.ts +5 -5
  30. package/src/index.ts +15 -15
  31. package/src/keyframes.ts +3 -3
  32. package/src/resolve.ts +14 -14
  33. package/src/shared.ts +2 -2
  34. package/src/sheet.ts +26 -26
  35. package/src/styled.tsx +157 -100
  36. package/src/useCSS.ts +4 -4
@@ -1,6 +1,6 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest"
2
- import { hash } from "../hash"
3
- import { createSheet, StyleSheet } from "../sheet"
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
2
+ import { hash } from '../hash'
3
+ import { createSheet, StyleSheet } from '../sheet'
4
4
 
5
5
  /**
6
6
  * Tests for the @media/@supports/@container splitting behavior.
@@ -10,8 +10,8 @@ import { createSheet, StyleSheet } from "../sheet"
10
10
  * This matches the approach used by styled-components and Emotion.
11
11
  */
12
12
 
13
- describe("StyleSheet -- at-rule splitting", () => {
14
- describe("SSR mode (splitAtRules internals via SSR output)", () => {
13
+ describe('StyleSheet -- at-rule splitting', () => {
14
+ describe('SSR mode (splitAtRules internals via SSR output)', () => {
15
15
  let originalDocument: typeof document
16
16
 
17
17
  beforeEach(() => {
@@ -24,18 +24,18 @@ describe("StyleSheet -- at-rule splitting", () => {
24
24
  globalThis.document = originalDocument
25
25
  })
26
26
 
27
- it("CSS without @media produces a single rule", () => {
27
+ it('CSS without @media produces a single rule', () => {
28
28
  const s = createSheet()
29
- s.insert("color: red; font-size: 16px;")
29
+ s.insert('color: red; font-size: 16px;')
30
30
  const styles = s.getStyles()
31
31
 
32
32
  // Should have exactly one rule: .pyr-xxx{color: red; font-size: 16px;}
33
33
  expect(styles).toMatch(/^\.pyr-[0-9a-z]+\{color: red; font-size: 16px;\}$/)
34
34
  })
35
35
 
36
- it("CSS with @media splits into base + media rules", () => {
36
+ it('CSS with @media splits into base + media rules', () => {
37
37
  const s = createSheet()
38
- s.insert("color: red; @media (min-width: 768px){color: blue;}")
38
+ s.insert('color: red; @media (min-width: 768px){color: blue;}')
39
39
  const styles = s.getStyles()
40
40
 
41
41
  // Base rule: .pyr-xxx{color: red;}
@@ -46,15 +46,15 @@ describe("StyleSheet -- at-rule splitting", () => {
46
46
  expect(styles).not.toMatch(/\.pyr-[0-9a-z]+\{[^}]*@media/)
47
47
  })
48
48
 
49
- it("CSS with multiple @media produces multiple separate rules", () => {
49
+ it('CSS with multiple @media produces multiple separate rules', () => {
50
50
  const s = createSheet()
51
51
  s.insert(
52
- "position: absolute; bottom: -4.375rem; @media (min-width: 36em){right: -11.25rem;} @media (min-width: 48em){bottom: 0; height: 40rem;}",
52
+ 'position: absolute; bottom: -4.375rem; @media (min-width: 36em){right: -11.25rem;} @media (min-width: 48em){bottom: 0; height: 40rem;}',
53
53
  )
54
54
  const styles = s.getStyles()
55
55
 
56
56
  // Base
57
- expect(styles).toContain("position: absolute; bottom: -4.375rem;")
57
+ expect(styles).toContain('position: absolute; bottom: -4.375rem;')
58
58
  // Two separate media rules
59
59
  expect(styles).toMatch(/@media \(min-width: 36em\)\{\.pyr-[0-9a-z]+\{right: -11.25rem;\}\}/)
60
60
  expect(styles).toMatch(
@@ -62,9 +62,9 @@ describe("StyleSheet -- at-rule splitting", () => {
62
62
  )
63
63
  })
64
64
 
65
- it("CSS with only @media (no base declarations) works correctly", () => {
65
+ it('CSS with only @media (no base declarations) works correctly', () => {
66
66
  const s = createSheet()
67
- s.insert("@media (min-width: 768px){color: blue;} @media (min-width: 1024px){color: green;}")
67
+ s.insert('@media (min-width: 768px){color: blue;} @media (min-width: 1024px){color: green;}')
68
68
  const styles = s.getStyles()
69
69
 
70
70
  // No base rule (or empty base)
@@ -74,9 +74,9 @@ describe("StyleSheet -- at-rule splitting", () => {
74
74
  expect(styles).toMatch(/@media \(min-width: 1024px\)\{\.pyr-[0-9a-z]+\{color: green;\}\}/)
75
75
  })
76
76
 
77
- it("boost doubles selector in both base and media rules", () => {
77
+ it('boost doubles selector in both base and media rules', () => {
78
78
  const s = createSheet()
79
- s.insert("color: red; @media (min-width: 768px){color: blue;}", true)
79
+ s.insert('color: red; @media (min-width: 768px){color: blue;}', true)
80
80
  const styles = s.getStyles()
81
81
 
82
82
  // Base: .pyr-xxx.pyr-xxx{color: red;}
@@ -87,18 +87,18 @@ describe("StyleSheet -- at-rule splitting", () => {
87
87
  )
88
88
  })
89
89
 
90
- it("@supports blocks are also split out", () => {
90
+ it('@supports blocks are also split out', () => {
91
91
  const s = createSheet()
92
- s.insert("display: flex; @supports (display: grid){display: grid;}")
92
+ s.insert('display: flex; @supports (display: grid){display: grid;}')
93
93
  const styles = s.getStyles()
94
94
 
95
95
  expect(styles).toMatch(/\.pyr-[0-9a-z]+\{display: flex;\}/)
96
96
  expect(styles).toMatch(/@supports \(display: grid\)\{\.pyr-[0-9a-z]+\{display: grid;\}\}/)
97
97
  })
98
98
 
99
- it("@container blocks are also split out", () => {
99
+ it('@container blocks are also split out', () => {
100
100
  const s = createSheet()
101
- s.insert("font-size: 1rem; @container (min-width: 400px){font-size: 1.25rem;}")
101
+ s.insert('font-size: 1rem; @container (min-width: 400px){font-size: 1.25rem;}')
102
102
  const styles = s.getStyles()
103
103
 
104
104
  expect(styles).toMatch(/\.pyr-[0-9a-z]+\{font-size: 1rem;\}/)
@@ -107,9 +107,9 @@ describe("StyleSheet -- at-rule splitting", () => {
107
107
  )
108
108
  })
109
109
 
110
- it("@layer wraps each split rule individually", () => {
111
- const s = createSheet({ layer: "components" })
112
- s.insert("color: red; @media (min-width: 768px){color: blue;}")
110
+ it('@layer wraps each split rule individually', () => {
111
+ const s = createSheet({ layer: 'components' })
112
+ s.insert('color: red; @media (min-width: 768px){color: blue;}')
113
113
  const styles = s.getStyles()
114
114
 
115
115
  // Base wrapped in layer
@@ -120,9 +120,9 @@ describe("StyleSheet -- at-rule splitting", () => {
120
120
  )
121
121
  })
122
122
 
123
- it("deduplicates CSS with @media (same CSS -> same className -> single insert)", () => {
123
+ it('deduplicates CSS with @media (same CSS -> same className -> single insert)', () => {
124
124
  const s = createSheet()
125
- const cssStr = "color: red; @media (min-width: 768px){color: blue;}"
125
+ const cssStr = 'color: red; @media (min-width: 768px){color: blue;}'
126
126
  s.insert(cssStr)
127
127
  s.insert(cssStr)
128
128
 
@@ -131,22 +131,22 @@ describe("StyleSheet -- at-rule splitting", () => {
131
131
  expect(baseMatches).toHaveLength(1)
132
132
  })
133
133
 
134
- it("real-world example: position + responsive inset/height", () => {
134
+ it('real-world example: position + responsive inset/height', () => {
135
135
  const s = createSheet()
136
136
  const cssStr =
137
- "position: absolute; bottom: -4.375rem; right: -12.5rem; height: 28.75rem; " +
138
- "@media only screen and (min-width: 36em){right: -11.25rem;} " +
139
- "@media only screen and (min-width: 48em){bottom: 0; height: 40rem;} " +
140
- "@media only screen and (min-width: 62em){right: -6.25rem;} " +
141
- "@media only screen and (min-width: 100em){right: initial; left: 55%;}"
137
+ 'position: absolute; bottom: -4.375rem; right: -12.5rem; height: 28.75rem; ' +
138
+ '@media only screen and (min-width: 36em){right: -11.25rem;} ' +
139
+ '@media only screen and (min-width: 48em){bottom: 0; height: 40rem;} ' +
140
+ '@media only screen and (min-width: 62em){right: -6.25rem;} ' +
141
+ '@media only screen and (min-width: 100em){right: initial; left: 55%;}'
142
142
  s.insert(cssStr, true)
143
143
  const styles = s.getStyles()
144
144
 
145
145
  // Base rule has position, bottom, right, height
146
- expect(styles).toContain("position: absolute;")
147
- expect(styles).toContain("bottom: -4.375rem;")
148
- expect(styles).toContain("right: -12.5rem;")
149
- expect(styles).toContain("height: 28.75rem;")
146
+ expect(styles).toContain('position: absolute;')
147
+ expect(styles).toContain('bottom: -4.375rem;')
148
+ expect(styles).toContain('right: -12.5rem;')
149
+ expect(styles).toContain('height: 28.75rem;')
150
150
 
151
151
  // Each media query is a separate top-level rule
152
152
  expect(styles).toMatch(/@media only screen and \(min-width: 36em\)\{/)
@@ -158,42 +158,42 @@ describe("StyleSheet -- at-rule splitting", () => {
158
158
  expect(styles).not.toMatch(/\.pyr-[0-9a-z]+\{[^}]*@media/)
159
159
  })
160
160
 
161
- it("getStyleTag contains all split rules", () => {
161
+ it('getStyleTag contains all split rules', () => {
162
162
  const s = createSheet()
163
- s.insert("color: red; @media (min-width: 768px){color: blue;}")
163
+ s.insert('color: red; @media (min-width: 768px){color: blue;}')
164
164
  const tag = s.getStyleTag()
165
165
 
166
166
  expect(tag).toMatch(/^<style data-pyreon-styler="">.*<\/style>$/)
167
- expect(tag).toContain("color: red;")
168
- expect(tag).toContain("@media (min-width: 768px)")
167
+ expect(tag).toContain('color: red;')
168
+ expect(tag).toContain('@media (min-width: 768px)')
169
169
  })
170
170
 
171
- it("reset clears all split rules from SSR buffer and cache", () => {
171
+ it('reset clears all split rules from SSR buffer and cache', () => {
172
172
  const s = createSheet()
173
- s.insert("color: red; @media (min-width: 768px){color: blue;}")
174
- expect(s.getStyles()).not.toBe("")
173
+ s.insert('color: red; @media (min-width: 768px){color: blue;}')
174
+ expect(s.getStyles()).not.toBe('')
175
175
 
176
176
  s.reset()
177
- expect(s.getStyles()).toBe("")
177
+ expect(s.getStyles()).toBe('')
178
178
  expect(s.cacheSize).toBe(0) // cache also cleared for SSR correctness
179
179
  })
180
180
  })
181
181
 
182
- describe("DOM mode (insertRule verification)", () => {
182
+ describe('DOM mode (insertRule verification)', () => {
183
183
  beforeEach(() => {
184
- for (const el of Array.from(document.querySelectorAll("style[data-pyreon-styler]")))
184
+ for (const el of Array.from(document.querySelectorAll('style[data-pyreon-styler]')))
185
185
  el.remove()
186
186
  })
187
187
 
188
- it("inserts base + media as separate CSSRules", () => {
188
+ it('inserts base + media as separate CSSRules', () => {
189
189
  const s = createSheet()
190
- s.insert("color: red; @media (min-width: 768px){color: blue;}")
190
+ s.insert('color: red; @media (min-width: 768px){color: blue;}')
191
191
 
192
192
  // Find the style element
193
- const styleEl = document.querySelector("style[data-pyreon-styler]") as HTMLStyleElement
193
+ const styleEl = document.querySelector('style[data-pyreon-styler]') as HTMLStyleElement
194
194
  expect(styleEl).not.toBeNull()
195
195
  const sheet = styleEl.sheet
196
- if (!sheet) throw new Error("expected sheet")
196
+ if (!sheet) throw new Error('expected sheet')
197
197
 
198
198
  // Should have at least 2 rules: one CSSStyleRule + one CSSMediaRule
199
199
  let hasStyleRule = false
@@ -201,7 +201,7 @@ describe("StyleSheet -- at-rule splitting", () => {
201
201
 
202
202
  for (let i = 0; i < sheet.cssRules.length; i++) {
203
203
  const rule = sheet.cssRules[i]
204
- if (rule instanceof CSSStyleRule && rule.selectorText.startsWith(".pyr-")) {
204
+ if (rule instanceof CSSStyleRule && rule.selectorText.startsWith('.pyr-')) {
205
205
  hasStyleRule = true
206
206
  }
207
207
  if (rule instanceof CSSMediaRule) {
@@ -214,13 +214,13 @@ describe("StyleSheet -- at-rule splitting", () => {
214
214
  })
215
215
 
216
216
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: complex logic is inherent to this function
217
- it("boosted selector appears in both base and media rules", () => {
217
+ it('boosted selector appears in both base and media rules', () => {
218
218
  const s = createSheet()
219
- const className = s.insert("color: red; @media (min-width: 768px){color: blue;}", true)
219
+ const className = s.insert('color: red; @media (min-width: 768px){color: blue;}', true)
220
220
 
221
- const styleEl = document.querySelector("style[data-pyreon-styler]") as HTMLStyleElement
221
+ const styleEl = document.querySelector('style[data-pyreon-styler]') as HTMLStyleElement
222
222
  const sheet = styleEl.sheet
223
- if (!sheet) throw new Error("expected sheet")
223
+ if (!sheet) throw new Error('expected sheet')
224
224
  const boostedSelector = `.${className}.${className}`
225
225
 
226
226
  let baseFound = false
@@ -246,19 +246,19 @@ describe("StyleSheet -- at-rule splitting", () => {
246
246
  })
247
247
  })
248
248
 
249
- describe("hydration with split rules", () => {
249
+ describe('hydration with split rules', () => {
250
250
  beforeEach(() => {
251
- for (const el of Array.from(document.querySelectorAll("style[data-pyreon-styler]")))
251
+ for (const el of Array.from(document.querySelectorAll('style[data-pyreon-styler]')))
252
252
  el.remove()
253
253
  })
254
254
 
255
- it("hydrates className from CSSMediaRule inner selectors", () => {
255
+ it('hydrates className from CSSMediaRule inner selectors', () => {
256
256
  // Simulate SSR: create a style tag with split rules
257
- const el = document.createElement("style")
258
- el.setAttribute("data-pyreon-styler", "")
257
+ const el = document.createElement('style')
258
+ el.setAttribute('data-pyreon-styler', '')
259
259
  document.head.appendChild(el)
260
260
 
261
- const className = `pyr-${hash("color: red;")}`
261
+ const className = `pyr-${hash('color: red;')}`
262
262
 
263
263
  // Insert rules that simulate what SSR produces
264
264
  el.sheet?.insertRule(`.${className}{color: red;}`, 0)
@@ -272,12 +272,12 @@ describe("StyleSheet -- at-rule splitting", () => {
272
272
  expect(s.cacheSize).toBeGreaterThanOrEqual(1)
273
273
  })
274
274
 
275
- it("hydrates className from boosted selectors in media rules", () => {
276
- const el = document.createElement("style")
277
- el.setAttribute("data-pyreon-styler", "")
275
+ it('hydrates className from boosted selectors in media rules', () => {
276
+ const el = document.createElement('style')
277
+ el.setAttribute('data-pyreon-styler', '')
278
278
  document.head.appendChild(el)
279
279
 
280
- const className = `pyr-${hash("font-size: 1rem;")}`
280
+ const className = `pyr-${hash('font-size: 1rem;')}`
281
281
 
282
282
  el.sheet?.insertRule(`.${className}.${className}{font-size: 1rem;}`, 0)
283
283
  el.sheet?.insertRule(
@@ -289,12 +289,12 @@ describe("StyleSheet -- at-rule splitting", () => {
289
289
  expect(s.has(className)).toBe(true)
290
290
  })
291
291
 
292
- it("hydrates from media-only rules (no base style rule)", () => {
293
- const el = document.createElement("style")
294
- el.setAttribute("data-pyreon-styler", "")
292
+ it('hydrates from media-only rules (no base style rule)', () => {
293
+ const el = document.createElement('style')
294
+ el.setAttribute('data-pyreon-styler', '')
295
295
  document.head.appendChild(el)
296
296
 
297
- const className = `pyr-${hash("responsive-only")}`
297
+ const className = `pyr-${hash('responsive-only')}`
298
298
 
299
299
  // Only a media rule, no base rule
300
300
  el.sheet?.insertRule(`@media (min-width: 768px){.${className}{color: blue;}}`, 0)
@@ -304,7 +304,7 @@ describe("StyleSheet -- at-rule splitting", () => {
304
304
  })
305
305
  })
306
306
 
307
- describe("edge cases", () => {
307
+ describe('edge cases', () => {
308
308
  let originalDocument: typeof document
309
309
 
310
310
  beforeEach(() => {
@@ -317,39 +317,39 @@ describe("StyleSheet -- at-rule splitting", () => {
317
317
  globalThis.document = originalDocument
318
318
  })
319
319
 
320
- it("handles empty CSS text", () => {
320
+ it('handles empty CSS text', () => {
321
321
  const s = createSheet()
322
- const cls = s.insert("")
322
+ const cls = s.insert('')
323
323
  expect(cls).toMatch(/^pyr-/)
324
- expect(s.getStyles()).toBe("")
324
+ expect(s.getStyles()).toBe('')
325
325
  })
326
326
 
327
- it("handles CSS with @ in a value (not an at-rule)", () => {
327
+ it('handles CSS with @ in a value (not an at-rule)', () => {
328
328
  const s = createSheet()
329
329
  s.insert('content: "@media";')
330
330
  // Should not be confused by @ in a string value
331
331
  expect(s.getStyles()).toContain('content: "@media";')
332
332
  })
333
333
 
334
- it("handles @keyframes reference in the CSS without splitting it", () => {
334
+ it('handles @keyframes reference in the CSS without splitting it', () => {
335
335
  const s = createSheet()
336
- s.insert("animation: fadeIn 0.3s;")
336
+ s.insert('animation: fadeIn 0.3s;')
337
337
  const styles = s.getStyles()
338
- expect(styles).toContain("animation: fadeIn 0.3s;")
338
+ expect(styles).toContain('animation: fadeIn 0.3s;')
339
339
  })
340
340
 
341
- it("preserves &:hover nesting in base CSS", () => {
341
+ it('preserves &:hover nesting in base CSS', () => {
342
342
  const s = createSheet()
343
- s.insert("color: red; &:hover{color: blue;}")
343
+ s.insert('color: red; &:hover{color: blue;}')
344
344
  const styles = s.getStyles()
345
345
 
346
346
  // The &:hover block should stay inside the base rule
347
347
  expect(styles).toMatch(/\.pyr-[0-9a-z]+\{color: red; &:hover\{color: blue;\}\}/)
348
348
  })
349
349
 
350
- it("preserves &:hover nesting alongside @media splitting", () => {
350
+ it('preserves &:hover nesting alongside @media splitting', () => {
351
351
  const s = createSheet()
352
- s.insert("color: red; &:hover{color: blue;} @media (min-width: 768px){font-size: 2rem;}")
352
+ s.insert('color: red; &:hover{color: blue;} @media (min-width: 768px){font-size: 2rem;}')
353
353
  const styles = s.getStyles()
354
354
 
355
355
  // Base rule has color + &:hover
@@ -358,9 +358,9 @@ describe("StyleSheet -- at-rule splitting", () => {
358
358
  expect(styles).toMatch(/@media \(min-width: 768px\)\{\.pyr-[0-9a-z]+\{font-size: 2rem;\}\}/)
359
359
  })
360
360
 
361
- it("handles consecutive @media blocks with no base CSS between them", () => {
361
+ it('handles consecutive @media blocks with no base CSS between them', () => {
362
362
  const s = createSheet()
363
- s.insert("@media (min-width: 768px){color: blue;} @media (min-width: 1024px){color: green;}")
363
+ s.insert('@media (min-width: 768px){color: blue;} @media (min-width: 1024px){color: green;}')
364
364
  const styles = s.getStyles()
365
365
 
366
366
  expect(styles).toMatch(/@media \(min-width: 768px\)/)
@@ -368,7 +368,7 @@ describe("StyleSheet -- at-rule splitting", () => {
368
368
  })
369
369
  })
370
370
 
371
- describe("performance characteristics", () => {
371
+ describe('performance characteristics', () => {
372
372
  let originalDocument: typeof document
373
373
 
374
374
  beforeEach(() => {
@@ -381,7 +381,7 @@ describe("StyleSheet -- at-rule splitting", () => {
381
381
  globalThis.document = originalDocument
382
382
  })
383
383
 
384
- it("fast path: no scanning when CSS has no @ character", () => {
384
+ it('fast path: no scanning when CSS has no @ character', () => {
385
385
  const s = createSheet()
386
386
  // Insert 1000 simple rules -- should not trigger any splitting logic
387
387
  const start = performance.now()
@@ -395,7 +395,7 @@ describe("StyleSheet -- at-rule splitting", () => {
395
395
  expect(elapsed).toBeLessThan(500)
396
396
  })
397
397
 
398
- it("splitting adds minimal overhead for CSS with @media", () => {
398
+ it('splitting adds minimal overhead for CSS with @media', () => {
399
399
  const s = createSheet()
400
400
  const start = performance.now()
401
401
  for (let i = 0; i < 500; i++) {
@@ -1,8 +1,8 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest"
2
- import { hash } from "../hash"
3
- import { sheet } from "../sheet"
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
2
+ import { hash } from '../hash'
3
+ import { sheet } from '../sheet'
4
4
 
5
- describe("StyleSheet", () => {
5
+ describe('StyleSheet', () => {
6
6
  beforeEach(() => {
7
7
  sheet.reset()
8
8
  })
@@ -11,154 +11,154 @@ describe("StyleSheet", () => {
11
11
  sheet.reset()
12
12
  })
13
13
 
14
- describe("insert", () => {
15
- it("returns a class name with pyr- prefix", () => {
16
- const className = sheet.insert("display: flex;")
14
+ describe('insert', () => {
15
+ it('returns a class name with pyr- prefix', () => {
16
+ const className = sheet.insert('display: flex;')
17
17
  expect(className).toMatch(/^pyr-[0-9a-z]+$/)
18
18
  })
19
19
 
20
- it("same CSS text always returns same class name (dedup)", () => {
21
- const cls1 = sheet.insert("color: red;")
22
- const cls2 = sheet.insert("color: red;")
20
+ it('same CSS text always returns same class name (dedup)', () => {
21
+ const cls1 = sheet.insert('color: red;')
22
+ const cls2 = sheet.insert('color: red;')
23
23
  expect(cls1).toBe(cls2)
24
24
  })
25
25
 
26
- it("different CSS text returns different class names", () => {
27
- const cls1 = sheet.insert("color: red;")
28
- const cls2 = sheet.insert("color: blue;")
26
+ it('different CSS text returns different class names', () => {
27
+ const cls1 = sheet.insert('color: red;')
28
+ const cls2 = sheet.insert('color: blue;')
29
29
  expect(cls1).not.toBe(cls2)
30
30
  })
31
31
 
32
- it("class name matches hash of CSS text", () => {
33
- const cssText = "display: flex;"
32
+ it('class name matches hash of CSS text', () => {
33
+ const cssText = 'display: flex;'
34
34
  const className = sheet.insert(cssText)
35
35
  expect(className).toBe(`pyr-${hash(cssText)}`)
36
36
  })
37
37
 
38
- it("handles empty string CSS", () => {
39
- const className = sheet.insert("")
38
+ it('handles empty string CSS', () => {
39
+ const className = sheet.insert('')
40
40
  expect(className).toMatch(/^pyr-[0-9a-z]+$/)
41
41
  })
42
42
 
43
- it("supports boost mode (doubled selector)", () => {
44
- const className = sheet.insert("color: red;", true)
43
+ it('supports boost mode (doubled selector)', () => {
44
+ const className = sheet.insert('color: red;', true)
45
45
  expect(className).toMatch(/^pyr-[0-9a-z]+$/)
46
46
  })
47
47
  })
48
48
 
49
- describe("cache eviction", () => {
50
- it("evicts oldest entries when cache exceeds MAX_CACHE", () => {
49
+ describe('cache eviction', () => {
50
+ it('evicts oldest entries when cache exceeds MAX_CACHE', () => {
51
51
  for (let i = 0; i < 100; i++) {
52
52
  sheet.insert(`unique-prop-${i}: value-${i};`)
53
53
  }
54
- const result = sheet.insert("color: red;")
54
+ const result = sheet.insert('color: red;')
55
55
  expect(result).toMatch(/^pyr-/)
56
56
  })
57
57
  })
58
58
 
59
- describe("insertKeyframes", () => {
60
- it("does not throw", () => {
61
- expect(() => sheet.insertKeyframes("test-anim", "from{opacity:0}to{opacity:1}")).not.toThrow()
59
+ describe('insertKeyframes', () => {
60
+ it('does not throw', () => {
61
+ expect(() => sheet.insertKeyframes('test-anim', 'from{opacity:0}to{opacity:1}')).not.toThrow()
62
62
  })
63
63
 
64
- it("deduplicates by name", () => {
65
- sheet.insertKeyframes("test-anim", "from{opacity:0}to{opacity:1}")
64
+ it('deduplicates by name', () => {
65
+ sheet.insertKeyframes('test-anim', 'from{opacity:0}to{opacity:1}')
66
66
  // Second call with same name should not throw
67
- expect(() => sheet.insertKeyframes("test-anim", "from{opacity:0}to{opacity:1}")).not.toThrow()
67
+ expect(() => sheet.insertKeyframes('test-anim', 'from{opacity:0}to{opacity:1}')).not.toThrow()
68
68
  })
69
69
  })
70
70
 
71
- describe("insertGlobal", () => {
72
- it("does not throw for valid CSS", () => {
73
- expect(() => sheet.insertGlobal("body { margin: 0; }")).not.toThrow()
71
+ describe('insertGlobal', () => {
72
+ it('does not throw for valid CSS', () => {
73
+ expect(() => sheet.insertGlobal('body { margin: 0; }')).not.toThrow()
74
74
  })
75
75
 
76
- it("handles multiple calls without error", () => {
77
- sheet.insertGlobal("body { margin: 0; }")
78
- sheet.insertGlobal("html { box-sizing: border-box; }")
76
+ it('handles multiple calls without error', () => {
77
+ sheet.insertGlobal('body { margin: 0; }')
78
+ sheet.insertGlobal('html { box-sizing: border-box; }')
79
79
  })
80
80
 
81
- it("deduplicates same CSS", () => {
82
- sheet.insertGlobal("body { margin: 0; }")
83
- sheet.insertGlobal("body { margin: 0; }")
81
+ it('deduplicates same CSS', () => {
82
+ sheet.insertGlobal('body { margin: 0; }')
83
+ sheet.insertGlobal('body { margin: 0; }')
84
84
  // No error, second is deduped
85
85
  })
86
86
  })
87
87
 
88
- describe("getClassName", () => {
89
- it("returns a className without injecting", () => {
90
- const className = sheet.getClassName("color: red;")
88
+ describe('getClassName', () => {
89
+ it('returns a className without injecting', () => {
90
+ const className = sheet.getClassName('color: red;')
91
91
  expect(className).toMatch(/^pyr-[0-9a-z]+$/)
92
92
  })
93
93
 
94
- it("returns same className as insert for same CSS", () => {
95
- const cssText = "display: flex;"
94
+ it('returns same className as insert for same CSS', () => {
95
+ const cssText = 'display: flex;'
96
96
  const getResult = sheet.getClassName(cssText)
97
97
  const insertResult = sheet.insert(cssText)
98
98
  expect(getResult).toBe(insertResult)
99
99
  })
100
100
  })
101
101
 
102
- describe("prepare", () => {
103
- it("returns className and rules", () => {
104
- const { className, rules } = sheet.prepare("color: red;")
102
+ describe('prepare', () => {
103
+ it('returns className and rules', () => {
104
+ const { className, rules } = sheet.prepare('color: red;')
105
105
  expect(className).toMatch(/^pyr-[0-9a-z]+$/)
106
106
  expect(rules).toContain(className)
107
- expect(rules).toContain("color: red;")
107
+ expect(rules).toContain('color: red;')
108
108
  })
109
109
 
110
- it("supports boost mode", () => {
111
- const { className, rules } = sheet.prepare("color: red;", true)
110
+ it('supports boost mode', () => {
111
+ const { className, rules } = sheet.prepare('color: red;', true)
112
112
  // Boosted selector should have doubled class
113
113
  expect(rules).toContain(`.${className}.${className}`)
114
114
  })
115
115
  })
116
116
 
117
- describe("SSR support", () => {
118
- it("getStyleTag returns a string", () => {
117
+ describe('SSR support', () => {
118
+ it('getStyleTag returns a string', () => {
119
119
  const result = sheet.getStyleTag()
120
- expect(typeof result).toBe("string")
121
- expect(result).toContain("data-pyreon-styler")
120
+ expect(typeof result).toBe('string')
121
+ expect(result).toContain('data-pyreon-styler')
122
122
  })
123
123
 
124
- it("getStyles returns empty string when no rules inserted", () => {
124
+ it('getStyles returns empty string when no rules inserted', () => {
125
125
  const result = sheet.getStyles()
126
- expect(result).toBe("")
126
+ expect(result).toBe('')
127
127
  })
128
128
  })
129
129
 
130
- describe("reset", () => {
131
- it("clears cache so new inserts re-generate class names", () => {
132
- const cls1 = sheet.insert("color: green;")
130
+ describe('reset', () => {
131
+ it('clears cache so new inserts re-generate class names', () => {
132
+ const cls1 = sheet.insert('color: green;')
133
133
  sheet.reset()
134
- const cls2 = sheet.insert("color: green;")
134
+ const cls2 = sheet.insert('color: green;')
135
135
  expect(cls1).toBe(cls2)
136
136
  })
137
137
  })
138
138
 
139
- describe("clearCache and clearAll", () => {
140
- it("clearCache clears the cache", () => {
141
- sheet.insert("color: red;")
139
+ describe('clearCache and clearAll', () => {
140
+ it('clearCache clears the cache', () => {
141
+ sheet.insert('color: red;')
142
142
  sheet.clearCache()
143
143
  expect(sheet.cacheSize).toBe(0)
144
144
  })
145
145
 
146
- it("clearAll clears cache and SSR buffer", () => {
147
- sheet.insert("color: red;")
146
+ it('clearAll clears cache and SSR buffer', () => {
147
+ sheet.insert('color: red;')
148
148
  sheet.clearAll()
149
149
  expect(sheet.cacheSize).toBe(0)
150
- expect(sheet.getStyles()).toBe("")
150
+ expect(sheet.getStyles()).toBe('')
151
151
  })
152
152
  })
153
153
 
154
- describe("has", () => {
155
- it("returns true for cached classNames", () => {
156
- const className = sheet.insert("color: red;")
154
+ describe('has', () => {
155
+ it('returns true for cached classNames', () => {
156
+ const className = sheet.insert('color: red;')
157
157
  expect(sheet.has(className)).toBe(true)
158
158
  })
159
159
 
160
- it("returns false for unknown classNames", () => {
161
- expect(sheet.has("pyr-unknown")).toBe(false)
160
+ it('returns false for unknown classNames', () => {
161
+ expect(sheet.has('pyr-unknown')).toBe(false)
162
162
  })
163
163
  })
164
164
  })