@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,316 +0,0 @@
1
- import type { VNode } from '@pyreon/core'
2
- import { h } from '@pyreon/core'
3
- import { afterEach, beforeEach, describe, expect, it } from 'vitest'
4
- import { css } from '../css'
5
- import { createSheet, StyleSheet } from '../sheet'
6
- import { styled } from '../styled'
7
-
8
- describe('P3 features', () => {
9
- describe('shouldForwardProp', () => {
10
- it('allows custom prop filtering', () => {
11
- const Comp = styled('div', {
12
- shouldForwardProp: (prop) => prop !== 'color',
13
- })`
14
- display: flex;
15
- `
16
-
17
- const vnode = Comp({ color: 'red', title: 'hello' }) as VNode
18
- expect(vnode.props.color).toBeUndefined()
19
- expect(vnode.props.title).toBe('hello')
20
- })
21
-
22
- it('custom filter receives all non-system props', () => {
23
- const forwarded: string[] = []
24
- const Comp = styled('div', {
25
- shouldForwardProp: (prop) => {
26
- forwarded.push(prop)
27
- return true
28
- },
29
- })`
30
- display: flex;
31
- `
32
-
33
- Comp({ 'data-x': '1', title: 'hi' })
34
- expect(forwarded).toContain('data-x')
35
- expect(forwarded).toContain('title')
36
- })
37
-
38
- it('works with dynamic interpolations', () => {
39
- const Comp = styled('div', {
40
- shouldForwardProp: (prop) => prop === 'title',
41
- })`
42
- color: ${(p: any) => p.$color};
43
- `
44
-
45
- const vnode = Comp({ $color: 'red', title: 'yes', custom: 'no' }) as VNode
46
- expect(vnode.props.title).toBe('yes')
47
- expect(vnode.props.custom).toBeUndefined()
48
- })
49
-
50
- it('does not affect component wrapping (components receive all props)', () => {
51
- const Inner = (props: { class?: string; myProp?: string }) =>
52
- h('div', { class: props.class, 'data-my': props.myProp })
53
-
54
- // shouldForwardProp is only for HTML elements
55
- const Comp = styled(Inner, {
56
- shouldForwardProp: () => false,
57
- })`
58
- color: red;
59
- `
60
-
61
- const vnode = Comp({ myProp: 'hello' }) as VNode
62
- // Components always receive all props (no filtering)
63
- // The VNode wraps Inner and should pass myProp through
64
- expect(vnode.props.myProp).toBe('hello')
65
- })
66
- })
67
-
68
- describe('styled(StyledComponent) — extending', () => {
69
- it('extends a styled component', () => {
70
- const Base = styled('div')`
71
- color: red;
72
- `
73
- const Extended = styled(Base)`
74
- font-size: 20px;
75
- `
76
-
77
- const vnode = Extended({}) as VNode
78
- // Extended wraps Base, so Base applies its own className
79
- // and Extended passes its className to Base as a prop
80
- expect(vnode.props.class).toContain('pyr-')
81
- })
82
-
83
- it('extended component receives className from outer', () => {
84
- const Base = styled('div')`
85
- color: red;
86
- `
87
- const Extended = styled(Base)`
88
- font-size: 20px;
89
- `
90
-
91
- const vnode = Extended({ className: 'user-cls' }) as VNode
92
- expect(vnode.props.class).toContain('user-cls')
93
- })
94
-
95
- it('multi-level extension works', () => {
96
- const L1 = styled('div')`
97
- display: flex;
98
- `
99
- const L2 = styled(L1)`
100
- color: red;
101
- `
102
- const L3 = styled(L2)`
103
- font-size: 14px;
104
- `
105
-
106
- const vnode = L3({}) as VNode
107
- expect(vnode.props.class).toContain('pyr-')
108
- // L3 wraps L2 which wraps L1 which wraps 'div'.
109
- // The outermost VNode's type is the next component in the chain.
110
- expect(typeof vnode.type).toBe('function')
111
- })
112
- })
113
-
114
- describe('HMR cleanup API', () => {
115
- beforeEach(() => {
116
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
117
- el.remove()
118
- })
119
- })
120
-
121
- it('clearCache removes all cached entries', () => {
122
- const s = createSheet()
123
- s.insert('color: red;')
124
- s.insert('color: blue;')
125
- expect(s.cacheSize).toBe(2)
126
-
127
- s.clearCache()
128
- expect(s.cacheSize).toBe(0)
129
- })
130
-
131
- it('clearAll removes cache, SSR buffer, and DOM rules', () => {
132
- const s = new StyleSheet()
133
- s.insert('color: red;')
134
- s.insert('color: blue;')
135
- expect(s.cacheSize).toBe(2)
136
-
137
- s.clearAll()
138
- expect(s.cacheSize).toBe(0)
139
- })
140
-
141
- it('after clearCache, same CSS gets re-inserted', () => {
142
- const s = createSheet()
143
- s.insert('color: red;')
144
- expect(s.cacheSize).toBe(1)
145
-
146
- s.clearCache()
147
- expect(s.cacheSize).toBe(0)
148
-
149
- // Re-insert — should work since cache was cleared
150
- s.insert('color: red;')
151
- expect(s.cacheSize).toBe(1)
152
- })
153
- })
154
-
155
- describe('HMR cleanup API (SSR mode)', () => {
156
- let originalDocument: typeof document
157
-
158
- beforeEach(() => {
159
- originalDocument = globalThis.document
160
- // @ts-expect-error - intentionally deleting for SSR simulation
161
- delete globalThis.document
162
- })
163
-
164
- afterEach(() => {
165
- globalThis.document = originalDocument
166
- })
167
-
168
- it('clearAll in SSR mode clears buffer and cache', () => {
169
- const s = createSheet()
170
- s.insert('color: red;')
171
- expect(s.getStyles()).toContain('color: red;')
172
- expect(s.cacheSize).toBe(1)
173
-
174
- s.clearAll()
175
- expect(s.getStyles()).toBe('')
176
- expect(s.cacheSize).toBe(0)
177
- })
178
- })
179
-
180
- describe('CSS nesting (& selectors)', () => {
181
- it('& selectors pass through to the CSS rule', () => {
182
- // Native CSS nesting is supported by modern browsers
183
- // The resolver passes CSS through without transformation
184
- const Comp = styled('div')`
185
- color: red;
186
- &:hover {
187
- color: blue;
188
- }
189
- `
190
- const vnode = Comp({}) as VNode
191
- expect(vnode.props.class).toMatch(/^pyr-/)
192
- })
193
-
194
- it('nested & with pseudo-elements', () => {
195
- const Comp = styled('div')`
196
- position: relative;
197
- &::before {
198
- content: '';
199
- display: block;
200
- }
201
- &::after {
202
- content: '';
203
- display: block;
204
- }
205
- `
206
- const vnode = Comp({}) as VNode
207
- expect(vnode.props.class).toMatch(/^pyr-/)
208
- })
209
- })
210
-
211
- describe('edge cases', () => {
212
- it('empty template with dynamic interpolation returning nothing', () => {
213
- const Comp = styled('div')`
214
- ${(p: any) =>
215
- p.$show &&
216
- css`
217
- color: red;
218
- `}
219
- `
220
- const vnode = Comp({ $show: false }) as VNode
221
- // When resolved CSS is empty/whitespace, no className
222
- expect(vnode.props.class).toBeFalsy()
223
- })
224
-
225
- it('empty template with dynamic interpolation returning value', () => {
226
- const Comp = styled('div')`
227
- ${(p: any) =>
228
- p.$show &&
229
- css`
230
- color: red;
231
- `}
232
- `
233
- const vnode = Comp({ $show: true }) as VNode
234
- expect(vnode.props.class).toMatch(/^pyr-/)
235
- })
236
-
237
- it('deeply nested CSSResult chains resolve correctly', () => {
238
- const l1 = css`
239
- color: red;
240
- `
241
- const l2 = css`
242
- ${l1} font-size: 14px;
243
- `
244
- const l3 = css`
245
- ${l2} display: flex;
246
- `
247
- const l4 = css`
248
- ${l3} padding: 8px;
249
- `
250
- const l5 = css`
251
- ${l4} margin: 4px;
252
- `
253
-
254
- const resolved = l5.toString()
255
- expect(resolved).toContain('color: red;')
256
- expect(resolved).toContain('font-size: 14px;')
257
- expect(resolved).toContain('display: flex;')
258
- expect(resolved).toContain('padding: 8px;')
259
- expect(resolved).toContain('margin: 4px;')
260
- })
261
-
262
- it('anonymous component gets fallback displayName', () => {
263
- const Anon = (() => {
264
- const fn = () => null
265
- Object.defineProperty(fn, 'name', { value: '' })
266
- return fn
267
- })()
268
-
269
- const Comp = styled(Anon)`
270
- color: red;
271
- `
272
- expect((Comp as any).displayName).toBe('styled(Component)')
273
- })
274
-
275
- it('handles very large CSS strings', () => {
276
- const bigCSS = Array.from({ length: 100 }, (_, i) => `prop${i}: val${i};`).join(' ')
277
- const Comp = styled('div')`
278
- ${bigCSS}
279
- `
280
- const vnode = Comp({}) as VNode
281
- expect(vnode.props.class).toMatch(/^pyr-/)
282
- })
283
-
284
- it('different dynamic values cause different classNames', () => {
285
- const Comp = styled('div')`
286
- color: ${(p: any) => p.$color};
287
- font-size: ${(p: any) => p.$size};
288
- `
289
-
290
- const vnode1 = Comp({ $color: 'red', $size: '14px' }) as VNode
291
- const cls1 = vnode1.props.class as string
292
-
293
- const vnode2 = Comp({ $color: 'blue', $size: '16px' }) as VNode
294
- const cls2 = vnode2.props.class as string
295
-
296
- expect(cls1).not.toBe(cls2)
297
- expect(cls1).toMatch(/^pyr-/)
298
- expect(cls2).toMatch(/^pyr-/)
299
- })
300
-
301
- it('same dynamic values produce same className (dedup cache)', () => {
302
- const Comp = styled('div')`
303
- color: ${(p: any) => p.$color};
304
- `
305
-
306
- const vnode1 = Comp({ $color: 'red' }) as VNode
307
- const cls1 = vnode1.props.class as string
308
-
309
- // Re-render with same value
310
- const vnode2 = Comp({ $color: 'red' }) as VNode
311
- const cls2 = vnode2.props.class as string
312
-
313
- expect(cls1).toBe(cls2)
314
- })
315
- })
316
- })
@@ -1,94 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
- import { normalizeCSS, resolve } from '../resolve'
3
-
4
- /**
5
- * Tier 2 cache correctness proof: demonstrates that the WeakMap cache
6
- * in DynamicStyled correctly handles:
7
- * 1. Same $rocketstyle + $rocketstate → cache hit (skip resolve)
8
- * 2. Same $rocketstyle, different $rocketstate → cache miss (different CSS)
9
- * 3. Different $rocketstyle → cache miss (different CSS)
10
- *
11
- * These tests verify the LOGIC, not the styled() integration (which
12
- * runs in a browser environment). They prove the cache key scheme is
13
- * correct: both $rocketstyle AND $rocketstate identity matter.
14
- */
15
-
16
- describe('Tier 2: resolve cache correctness', () => {
17
- const strings = Object.assign(['background-color: ', ';'] as unknown as TemplateStringsArray, { raw: ['background-color: ', ';'] })
18
- const values = [(props: any) => props.$rocketstyle?.backgroundColor ?? 'transparent']
19
- const theme = { colors: { primary: '#3b82f6' } }
20
-
21
- it('same $rocketstyle object produces same CSS text (cache would hit)', () => {
22
- const rs = { backgroundColor: 'red' }
23
- const props1 = { $rocketstyle: rs, $rocketstate: { state: 'default' }, theme }
24
- const props2 = { $rocketstyle: rs, $rocketstate: { state: 'default' }, theme }
25
-
26
- const css1 = normalizeCSS(resolve(strings, values, props1))
27
- const css2 = normalizeCSS(resolve(strings, values, props2))
28
-
29
- expect(css1).toBe(css2)
30
- expect(css1).toContain('red')
31
- })
32
-
33
- it('different $rocketstyle objects produce different CSS (cache miss)', () => {
34
- const rs1 = { backgroundColor: 'red' }
35
- const rs2 = { backgroundColor: 'blue' }
36
-
37
- const css1 = normalizeCSS(resolve(strings, values, { $rocketstyle: rs1, theme }))
38
- const css2 = normalizeCSS(resolve(strings, values, { $rocketstyle: rs2, theme }))
39
-
40
- expect(css1).not.toBe(css2)
41
- expect(css1).toContain('red')
42
- expect(css2).toContain('blue')
43
- })
44
-
45
- it('same $rocketstyle but different $rocketstate produces different CSS when state-dependent', () => {
46
- // Interpolation that reads $rocketstate
47
- const stateStrings = Object.assign(['opacity: ', ';'] as unknown as TemplateStringsArray, { raw: ['opacity: ', ';'] })
48
- const stateValues = [(props: any) => props.$rocketstate?.disabled ? '0.5' : '1']
49
-
50
- const rs = { backgroundColor: 'red' }
51
- const state1 = { disabled: false }
52
- const state2 = { disabled: true }
53
-
54
- const css1 = normalizeCSS(resolve(stateStrings, stateValues, { $rocketstyle: rs, $rocketstate: state1, theme }))
55
- const css2 = normalizeCSS(resolve(stateStrings, stateValues, { $rocketstyle: rs, $rocketstate: state2, theme }))
56
-
57
- expect(css1).toContain('opacity: 1;')
58
- expect(css2).toContain('opacity: 0.5;')
59
- // Proves: caching on $rocketstyle alone would be WRONG here.
60
- // The two-level WeakMap ($rocketstyle → $rocketstate → class) is correct.
61
- })
62
- })
63
-
64
- describe('Tier 2: allocation reduction', () => {
65
- it('resolve() is expensive — cache eliminates it for repeated identical inputs', () => {
66
- const rs = { backgroundColor: 'red', color: 'white', padding: '8px' }
67
- const strings = Object.assign(
68
- ['background-color: ', '; color: ', '; padding: ', ';'] as unknown as TemplateStringsArray,
69
- { raw: ['background-color: ', '; color: ', '; padding: ', ';'] },
70
- )
71
- const values = [
72
- (p: any) => p.$rocketstyle?.backgroundColor,
73
- (p: any) => p.$rocketstyle?.color,
74
- (p: any) => p.$rocketstyle?.padding,
75
- ]
76
-
77
- const resolveSpy = vi.fn(resolve)
78
-
79
- // Simulate 50 renders with identical $rocketstyle
80
- const results: string[] = []
81
- for (let i = 0; i < 50; i++) {
82
- const cssText = normalizeCSS(resolveSpy(strings, values, { $rocketstyle: rs, theme: {} }))
83
- results.push(cssText)
84
- }
85
-
86
- // All 50 produce identical CSS
87
- expect(new Set(results).size).toBe(1)
88
- // resolve was called 50 times (no cache at this level — the cache is
89
- // in DynamicStyled). This test just proves the CONTRACT: identical
90
- // inputs → identical outputs, so caching is safe.
91
- expect(resolveSpy).toHaveBeenCalledTimes(50)
92
- expect(results[0]).toContain('background-color: red')
93
- })
94
- })