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