@pyreon/styler 0.11.4 → 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.
- package/README.md +27 -23
- package/lib/index.d.ts +9 -2
- package/lib/index.js +47 -4
- package/package.json +22 -22
- package/src/ThemeProvider.ts +10 -3
- package/src/__tests__/ThemeProvider.test.ts +21 -21
- package/src/__tests__/benchmark.bench.ts +56 -45
- package/src/__tests__/composition-chain.test.ts +200 -151
- package/src/__tests__/forward.test.ts +122 -122
- package/src/__tests__/globalStyle.test.ts +18 -18
- package/src/__tests__/hash.test.ts +27 -27
- package/src/__tests__/hybrid-injection.test.ts +83 -59
- package/src/__tests__/index.ts +10 -10
- package/src/__tests__/insertion-effect.test.ts +45 -32
- package/src/__tests__/integration.test.ts +81 -51
- package/src/__tests__/keyframes.test.ts +13 -13
- package/src/__tests__/memory-growth.test.ts +21 -21
- package/src/__tests__/p3-features.test.ts +162 -104
- package/src/__tests__/shared.test.ts +51 -33
- package/src/__tests__/sheet-advanced.test.ts +227 -227
- package/src/__tests__/sheet-split-atrules.test.ts +85 -85
- package/src/__tests__/sheet.test.ts +69 -69
- package/src/__tests__/styled-ssr.test.ts +36 -28
- package/src/__tests__/styled.test.ts +214 -145
- package/src/__tests__/theme.test.ts +11 -11
- package/src/__tests__/useCSS.test.ts +89 -59
- package/src/css.ts +1 -1
- package/src/forward.ts +187 -187
- package/src/globalStyle.ts +5 -5
- package/src/index.ts +15 -15
- package/src/keyframes.ts +3 -3
- package/src/resolve.ts +14 -14
- package/src/shared.ts +2 -2
- package/src/sheet.ts +26 -26
- package/src/styled.tsx +145 -100
- package/src/useCSS.ts +4 -4
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from
|
|
2
|
-
import { keyframes } from
|
|
3
|
-
import { sheet } from
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { keyframes } from '../keyframes'
|
|
3
|
+
import { sheet } from '../sheet'
|
|
4
4
|
|
|
5
|
-
describe(
|
|
5
|
+
describe('keyframes', () => {
|
|
6
6
|
afterEach(() => {
|
|
7
7
|
sheet.reset()
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
-
it(
|
|
10
|
+
it('returns a KeyframesResult with a name property', () => {
|
|
11
11
|
const fadeIn = keyframes`
|
|
12
12
|
from { opacity: 0; }
|
|
13
13
|
to { opacity: 1; }
|
|
@@ -15,7 +15,7 @@ describe("keyframes", () => {
|
|
|
15
15
|
expect(fadeIn.name).toMatch(/^pyr-kf-/)
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
it(
|
|
18
|
+
it('returns pyr-kf- prefix', () => {
|
|
19
19
|
const fadeIn = keyframes`
|
|
20
20
|
from { opacity: 0; }
|
|
21
21
|
to { opacity: 1; }
|
|
@@ -23,19 +23,19 @@ describe("keyframes", () => {
|
|
|
23
23
|
expect(fadeIn.name).toMatch(/^pyr-kf-[0-9a-z]+$/)
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
-
it(
|
|
26
|
+
it('is deterministic — same input produces same name', () => {
|
|
27
27
|
const a = keyframes`from { opacity: 0; } to { opacity: 1; }`
|
|
28
28
|
const b = keyframes`from { opacity: 0; } to { opacity: 1; }`
|
|
29
29
|
expect(a.name).toBe(b.name)
|
|
30
30
|
})
|
|
31
31
|
|
|
32
|
-
it(
|
|
32
|
+
it('different input produces different names', () => {
|
|
33
33
|
const fadeIn = keyframes`from { opacity: 0; } to { opacity: 1; }`
|
|
34
34
|
const slideIn = keyframes`from { transform: translateX(-100%); } to { transform: translateX(0); }`
|
|
35
35
|
expect(fadeIn.name).not.toBe(slideIn.name)
|
|
36
36
|
})
|
|
37
37
|
|
|
38
|
-
it(
|
|
38
|
+
it('supports interpolation values', () => {
|
|
39
39
|
const from = 0
|
|
40
40
|
const to = 1
|
|
41
41
|
const anim = keyframes`
|
|
@@ -45,19 +45,19 @@ describe("keyframes", () => {
|
|
|
45
45
|
expect(anim.name).toMatch(/^pyr-kf-/)
|
|
46
46
|
})
|
|
47
47
|
|
|
48
|
-
it(
|
|
48
|
+
it('toString returns the animation name', () => {
|
|
49
49
|
const fadeIn = keyframes`from { opacity: 0; } to { opacity: 1; }`
|
|
50
50
|
expect(fadeIn.toString()).toBe(fadeIn.name)
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
-
it(
|
|
53
|
+
it('can be used in template literals for animation property', () => {
|
|
54
54
|
const fadeIn = keyframes`from { opacity: 0; } to { opacity: 1; }`
|
|
55
55
|
const animationValue = `${fadeIn} 0.3s ease-in`
|
|
56
56
|
expect(animationValue).toContain(fadeIn.name)
|
|
57
|
-
expect(animationValue).toContain(
|
|
57
|
+
expect(animationValue).toContain('0.3s ease-in')
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
-
it(
|
|
60
|
+
it('handles complex keyframe definitions', () => {
|
|
61
61
|
const pulse = keyframes`
|
|
62
62
|
0% { transform: scale(1); }
|
|
63
63
|
50% { transform: scale(1.1); }
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from
|
|
2
|
-
import { createSheet } from
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { createSheet } from '../sheet'
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
describe(
|
|
6
|
-
it(
|
|
4
|
+
describe('memory growth', () => {
|
|
5
|
+
describe('bounded cache prevents unbounded growth (DOM mode)', () => {
|
|
6
|
+
it('cache stays bounded with maxCacheSize', () => {
|
|
7
7
|
const maxSize = 50
|
|
8
8
|
const s = createSheet({ maxCacheSize: maxSize })
|
|
9
9
|
|
|
@@ -14,7 +14,7 @@ describe("memory growth", () => {
|
|
|
14
14
|
expect(s.cacheSize).toBeLessThanOrEqual(maxSize * 1.5)
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
it(
|
|
17
|
+
it('cache eviction preserves recent entries', () => {
|
|
18
18
|
const maxSize = 20
|
|
19
19
|
const s = createSheet({ maxCacheSize: maxSize })
|
|
20
20
|
|
|
@@ -33,7 +33,7 @@ describe("memory growth", () => {
|
|
|
33
33
|
}
|
|
34
34
|
})
|
|
35
35
|
|
|
36
|
-
it(
|
|
36
|
+
it('handles rapid insertions without memory issues', () => {
|
|
37
37
|
const s = createSheet({ maxCacheSize: 100 })
|
|
38
38
|
const iterations = 1000
|
|
39
39
|
|
|
@@ -46,14 +46,14 @@ describe("memory growth", () => {
|
|
|
46
46
|
})
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
describe(
|
|
49
|
+
describe('default cache (large limit, DOM mode)', () => {
|
|
50
50
|
beforeEach(() => {
|
|
51
|
-
document.querySelectorAll(
|
|
51
|
+
document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
|
|
52
52
|
el.remove()
|
|
53
53
|
})
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
it(
|
|
56
|
+
it('default cache handles many unique rules', () => {
|
|
57
57
|
const s = createSheet()
|
|
58
58
|
|
|
59
59
|
for (let i = 0; i < 500; i++) {
|
|
@@ -63,7 +63,7 @@ describe("memory growth", () => {
|
|
|
63
63
|
expect(s.cacheSize).toBe(500)
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
-
it(
|
|
66
|
+
it('deduplication prevents growth from repeated rules', () => {
|
|
67
67
|
const s = createSheet()
|
|
68
68
|
|
|
69
69
|
for (let cycle = 0; cycle < 100; cycle++) {
|
|
@@ -76,7 +76,7 @@ describe("memory growth", () => {
|
|
|
76
76
|
})
|
|
77
77
|
})
|
|
78
78
|
|
|
79
|
-
describe(
|
|
79
|
+
describe('SSR mode memory', () => {
|
|
80
80
|
let originalDocument: typeof document
|
|
81
81
|
|
|
82
82
|
beforeEach(() => {
|
|
@@ -89,7 +89,7 @@ describe("memory growth", () => {
|
|
|
89
89
|
globalThis.document = originalDocument
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
-
it(
|
|
92
|
+
it('reset prevents SSR buffer accumulation across requests', () => {
|
|
93
93
|
const s = createSheet()
|
|
94
94
|
|
|
95
95
|
for (let i = 0; i < 100; i++) {
|
|
@@ -98,13 +98,13 @@ describe("memory growth", () => {
|
|
|
98
98
|
expect(s.getStyles().length).toBeGreaterThan(0)
|
|
99
99
|
|
|
100
100
|
s.reset()
|
|
101
|
-
expect(s.getStyles()).toBe(
|
|
101
|
+
expect(s.getStyles()).toBe('')
|
|
102
102
|
|
|
103
|
-
s.insert(
|
|
104
|
-
expect(s.getStyles()).not.toContain(
|
|
103
|
+
s.insert('req2-single: value;')
|
|
104
|
+
expect(s.getStyles()).not.toContain('req1-prop')
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
-
it(
|
|
107
|
+
it('keyframes cache does not grow unboundedly', () => {
|
|
108
108
|
const s = createSheet({ maxCacheSize: 20 })
|
|
109
109
|
|
|
110
110
|
for (let i = 0; i < 50; i++) {
|
|
@@ -114,7 +114,7 @@ describe("memory growth", () => {
|
|
|
114
114
|
expect(s.cacheSize).toBeLessThanOrEqual(50)
|
|
115
115
|
})
|
|
116
116
|
|
|
117
|
-
it(
|
|
117
|
+
it('global rules cache does not grow unboundedly', () => {
|
|
118
118
|
const s = createSheet({ maxCacheSize: 20 })
|
|
119
119
|
|
|
120
120
|
for (let i = 0; i < 50; i++) {
|
|
@@ -124,7 +124,7 @@ describe("memory growth", () => {
|
|
|
124
124
|
expect(s.cacheSize).toBeLessThanOrEqual(50)
|
|
125
125
|
})
|
|
126
126
|
|
|
127
|
-
it(
|
|
127
|
+
it('SSR buffer grows with unique rules (expected behavior)', () => {
|
|
128
128
|
const s = createSheet()
|
|
129
129
|
const ruleCount = 100
|
|
130
130
|
|
|
@@ -138,11 +138,11 @@ describe("memory growth", () => {
|
|
|
138
138
|
}
|
|
139
139
|
})
|
|
140
140
|
|
|
141
|
-
it(
|
|
141
|
+
it('SSR buffer does not duplicate identical rules', () => {
|
|
142
142
|
const s = createSheet()
|
|
143
143
|
|
|
144
144
|
for (let cycle = 0; cycle < 10; cycle++) {
|
|
145
|
-
s.insert(
|
|
145
|
+
s.insert('color: red;')
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
const matches = s.getStyles().match(/color: red;/g)
|
|
@@ -1,136 +1,158 @@
|
|
|
1
|
-
import type { VNode } from
|
|
2
|
-
import { h } from
|
|
3
|
-
import { afterEach, beforeEach, describe, expect, it } from
|
|
4
|
-
import { css } from
|
|
5
|
-
import { createSheet, StyleSheet } from
|
|
6
|
-
import { styled } from
|
|
7
|
-
|
|
8
|
-
describe(
|
|
9
|
-
describe(
|
|
10
|
-
it(
|
|
11
|
-
const Comp = styled(
|
|
12
|
-
shouldForwardProp: (prop) => prop !==
|
|
13
|
-
})`
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
16
18
|
expect(vnode.props.color).toBeUndefined()
|
|
17
|
-
expect(vnode.props.title).toBe(
|
|
19
|
+
expect(vnode.props.title).toBe('hello')
|
|
18
20
|
})
|
|
19
21
|
|
|
20
|
-
it(
|
|
22
|
+
it('custom filter receives all non-system props', () => {
|
|
21
23
|
const forwarded: string[] = []
|
|
22
|
-
const Comp = styled(
|
|
24
|
+
const Comp = styled('div', {
|
|
23
25
|
shouldForwardProp: (prop) => {
|
|
24
26
|
forwarded.push(prop)
|
|
25
27
|
return true
|
|
26
28
|
},
|
|
27
|
-
})`
|
|
29
|
+
})`
|
|
30
|
+
display: flex;
|
|
31
|
+
`
|
|
28
32
|
|
|
29
|
-
Comp({
|
|
30
|
-
expect(forwarded).toContain(
|
|
31
|
-
expect(forwarded).toContain(
|
|
33
|
+
Comp({ 'data-x': '1', title: 'hi' })
|
|
34
|
+
expect(forwarded).toContain('data-x')
|
|
35
|
+
expect(forwarded).toContain('title')
|
|
32
36
|
})
|
|
33
37
|
|
|
34
|
-
it(
|
|
35
|
-
const Comp = styled(
|
|
36
|
-
shouldForwardProp: (prop) => prop ===
|
|
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
|
+
`
|
|
38
44
|
|
|
39
|
-
const vnode = Comp({ $color:
|
|
40
|
-
expect(vnode.props.title).toBe(
|
|
45
|
+
const vnode = Comp({ $color: 'red', title: 'yes', custom: 'no' }) as VNode
|
|
46
|
+
expect(vnode.props.title).toBe('yes')
|
|
41
47
|
expect(vnode.props.custom).toBeUndefined()
|
|
42
48
|
})
|
|
43
49
|
|
|
44
|
-
it(
|
|
50
|
+
it('does not affect component wrapping (components receive all props)', () => {
|
|
45
51
|
const Inner = (props: { class?: string; myProp?: string }) =>
|
|
46
|
-
h(
|
|
52
|
+
h('div', { class: props.class, 'data-my': props.myProp })
|
|
47
53
|
|
|
48
54
|
// shouldForwardProp is only for HTML elements
|
|
49
55
|
const Comp = styled(Inner, {
|
|
50
56
|
shouldForwardProp: () => false,
|
|
51
|
-
})`
|
|
57
|
+
})`
|
|
58
|
+
color: red;
|
|
59
|
+
`
|
|
52
60
|
|
|
53
|
-
const vnode = Comp({ myProp:
|
|
61
|
+
const vnode = Comp({ myProp: 'hello' }) as VNode
|
|
54
62
|
// Components always receive all props (no filtering)
|
|
55
63
|
// The VNode wraps Inner and should pass myProp through
|
|
56
|
-
expect(vnode.props.myProp).toBe(
|
|
64
|
+
expect(vnode.props.myProp).toBe('hello')
|
|
57
65
|
})
|
|
58
66
|
})
|
|
59
67
|
|
|
60
|
-
describe(
|
|
61
|
-
it(
|
|
62
|
-
const Base = styled(
|
|
63
|
-
|
|
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
|
+
`
|
|
64
76
|
|
|
65
77
|
const vnode = Extended({}) as VNode
|
|
66
78
|
// Extended wraps Base, so Base applies its own className
|
|
67
79
|
// and Extended passes its className to Base as a prop
|
|
68
|
-
expect(vnode.props.class).toContain(
|
|
80
|
+
expect(vnode.props.class).toContain('pyr-')
|
|
69
81
|
})
|
|
70
82
|
|
|
71
|
-
it(
|
|
72
|
-
const Base = styled(
|
|
73
|
-
|
|
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
|
+
`
|
|
74
90
|
|
|
75
|
-
const vnode = Extended({ className:
|
|
76
|
-
expect(vnode.props.class).toContain(
|
|
91
|
+
const vnode = Extended({ className: 'user-cls' }) as VNode
|
|
92
|
+
expect(vnode.props.class).toContain('user-cls')
|
|
77
93
|
})
|
|
78
94
|
|
|
79
|
-
it(
|
|
80
|
-
const L1 = styled(
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
`
|
|
83
105
|
|
|
84
106
|
const vnode = L3({}) as VNode
|
|
85
|
-
expect(vnode.props.class).toContain(
|
|
107
|
+
expect(vnode.props.class).toContain('pyr-')
|
|
86
108
|
// L3 wraps L2 which wraps L1 which wraps 'div'.
|
|
87
109
|
// The outermost VNode's type is the next component in the chain.
|
|
88
|
-
expect(typeof vnode.type).toBe(
|
|
110
|
+
expect(typeof vnode.type).toBe('function')
|
|
89
111
|
})
|
|
90
112
|
})
|
|
91
113
|
|
|
92
|
-
describe(
|
|
114
|
+
describe('HMR cleanup API', () => {
|
|
93
115
|
beforeEach(() => {
|
|
94
|
-
document.querySelectorAll(
|
|
116
|
+
document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
|
|
95
117
|
el.remove()
|
|
96
118
|
})
|
|
97
119
|
})
|
|
98
120
|
|
|
99
|
-
it(
|
|
121
|
+
it('clearCache removes all cached entries', () => {
|
|
100
122
|
const s = createSheet()
|
|
101
|
-
s.insert(
|
|
102
|
-
s.insert(
|
|
123
|
+
s.insert('color: red;')
|
|
124
|
+
s.insert('color: blue;')
|
|
103
125
|
expect(s.cacheSize).toBe(2)
|
|
104
126
|
|
|
105
127
|
s.clearCache()
|
|
106
128
|
expect(s.cacheSize).toBe(0)
|
|
107
129
|
})
|
|
108
130
|
|
|
109
|
-
it(
|
|
131
|
+
it('clearAll removes cache, SSR buffer, and DOM rules', () => {
|
|
110
132
|
const s = new StyleSheet()
|
|
111
|
-
s.insert(
|
|
112
|
-
s.insert(
|
|
133
|
+
s.insert('color: red;')
|
|
134
|
+
s.insert('color: blue;')
|
|
113
135
|
expect(s.cacheSize).toBe(2)
|
|
114
136
|
|
|
115
137
|
s.clearAll()
|
|
116
138
|
expect(s.cacheSize).toBe(0)
|
|
117
139
|
})
|
|
118
140
|
|
|
119
|
-
it(
|
|
141
|
+
it('after clearCache, same CSS gets re-inserted', () => {
|
|
120
142
|
const s = createSheet()
|
|
121
|
-
s.insert(
|
|
143
|
+
s.insert('color: red;')
|
|
122
144
|
expect(s.cacheSize).toBe(1)
|
|
123
145
|
|
|
124
146
|
s.clearCache()
|
|
125
147
|
expect(s.cacheSize).toBe(0)
|
|
126
148
|
|
|
127
149
|
// Re-insert — should work since cache was cleared
|
|
128
|
-
s.insert(
|
|
150
|
+
s.insert('color: red;')
|
|
129
151
|
expect(s.cacheSize).toBe(1)
|
|
130
152
|
})
|
|
131
153
|
})
|
|
132
154
|
|
|
133
|
-
describe(
|
|
155
|
+
describe('HMR cleanup API (SSR mode)', () => {
|
|
134
156
|
let originalDocument: typeof document
|
|
135
157
|
|
|
136
158
|
beforeEach(() => {
|
|
@@ -143,98 +165,132 @@ describe("P3 features", () => {
|
|
|
143
165
|
globalThis.document = originalDocument
|
|
144
166
|
})
|
|
145
167
|
|
|
146
|
-
it(
|
|
168
|
+
it('clearAll in SSR mode clears buffer and cache', () => {
|
|
147
169
|
const s = createSheet()
|
|
148
|
-
s.insert(
|
|
149
|
-
expect(s.getStyles()).toContain(
|
|
170
|
+
s.insert('color: red;')
|
|
171
|
+
expect(s.getStyles()).toContain('color: red;')
|
|
150
172
|
expect(s.cacheSize).toBe(1)
|
|
151
173
|
|
|
152
174
|
s.clearAll()
|
|
153
|
-
expect(s.getStyles()).toBe(
|
|
175
|
+
expect(s.getStyles()).toBe('')
|
|
154
176
|
expect(s.cacheSize).toBe(0)
|
|
155
177
|
})
|
|
156
178
|
})
|
|
157
179
|
|
|
158
|
-
describe(
|
|
159
|
-
it(
|
|
180
|
+
describe('CSS nesting (& selectors)', () => {
|
|
181
|
+
it('& selectors pass through to the CSS rule', () => {
|
|
160
182
|
// Native CSS nesting is supported by modern browsers
|
|
161
183
|
// The resolver passes CSS through without transformation
|
|
162
|
-
const Comp = styled(
|
|
184
|
+
const Comp = styled('div')`
|
|
163
185
|
color: red;
|
|
164
|
-
&:hover {
|
|
186
|
+
&:hover {
|
|
187
|
+
color: blue;
|
|
188
|
+
}
|
|
165
189
|
`
|
|
166
190
|
const vnode = Comp({}) as VNode
|
|
167
191
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
168
192
|
})
|
|
169
193
|
|
|
170
|
-
it(
|
|
171
|
-
const Comp = styled(
|
|
194
|
+
it('nested & with pseudo-elements', () => {
|
|
195
|
+
const Comp = styled('div')`
|
|
172
196
|
position: relative;
|
|
173
|
-
&::before {
|
|
174
|
-
|
|
197
|
+
&::before {
|
|
198
|
+
content: '';
|
|
199
|
+
display: block;
|
|
200
|
+
}
|
|
201
|
+
&::after {
|
|
202
|
+
content: '';
|
|
203
|
+
display: block;
|
|
204
|
+
}
|
|
175
205
|
`
|
|
176
206
|
const vnode = Comp({}) as VNode
|
|
177
207
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
178
208
|
})
|
|
179
209
|
})
|
|
180
210
|
|
|
181
|
-
describe(
|
|
182
|
-
it(
|
|
183
|
-
const Comp = styled(
|
|
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
|
+
`
|
|
184
220
|
const vnode = Comp({ $show: false }) as VNode
|
|
185
221
|
// When resolved CSS is empty/whitespace, no className
|
|
186
222
|
expect(vnode.props.class).toBeFalsy()
|
|
187
223
|
})
|
|
188
224
|
|
|
189
|
-
it(
|
|
190
|
-
const Comp = styled(
|
|
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
|
+
`
|
|
191
233
|
const vnode = Comp({ $show: true }) as VNode
|
|
192
234
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
193
235
|
})
|
|
194
236
|
|
|
195
|
-
it(
|
|
196
|
-
const l1 = css`
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
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
|
+
`
|
|
201
253
|
|
|
202
254
|
const resolved = l5.toString()
|
|
203
|
-
expect(resolved).toContain(
|
|
204
|
-
expect(resolved).toContain(
|
|
205
|
-
expect(resolved).toContain(
|
|
206
|
-
expect(resolved).toContain(
|
|
207
|
-
expect(resolved).toContain(
|
|
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;')
|
|
208
260
|
})
|
|
209
261
|
|
|
210
|
-
it(
|
|
262
|
+
it('anonymous component gets fallback displayName', () => {
|
|
211
263
|
const Anon = (() => {
|
|
212
264
|
const fn = () => null
|
|
213
|
-
Object.defineProperty(fn,
|
|
265
|
+
Object.defineProperty(fn, 'name', { value: '' })
|
|
214
266
|
return fn
|
|
215
267
|
})()
|
|
216
268
|
|
|
217
|
-
const Comp = styled(Anon)`
|
|
218
|
-
|
|
269
|
+
const Comp = styled(Anon)`
|
|
270
|
+
color: red;
|
|
271
|
+
`
|
|
272
|
+
expect((Comp as any).displayName).toBe('styled(Component)')
|
|
219
273
|
})
|
|
220
274
|
|
|
221
|
-
it(
|
|
222
|
-
const bigCSS = Array.from({ length: 100 }, (_, i) => `prop${i}: val${i};`).join(
|
|
223
|
-
const Comp = styled(
|
|
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
|
+
`
|
|
224
280
|
const vnode = Comp({}) as VNode
|
|
225
281
|
expect(vnode.props.class).toMatch(/^pyr-/)
|
|
226
282
|
})
|
|
227
283
|
|
|
228
|
-
it(
|
|
229
|
-
const Comp = styled(
|
|
284
|
+
it('different dynamic values cause different classNames', () => {
|
|
285
|
+
const Comp = styled('div')`
|
|
230
286
|
color: ${(p: any) => p.$color};
|
|
231
287
|
font-size: ${(p: any) => p.$size};
|
|
232
288
|
`
|
|
233
289
|
|
|
234
|
-
const vnode1 = Comp({ $color:
|
|
290
|
+
const vnode1 = Comp({ $color: 'red', $size: '14px' }) as VNode
|
|
235
291
|
const cls1 = vnode1.props.class as string
|
|
236
292
|
|
|
237
|
-
const vnode2 = Comp({ $color:
|
|
293
|
+
const vnode2 = Comp({ $color: 'blue', $size: '16px' }) as VNode
|
|
238
294
|
const cls2 = vnode2.props.class as string
|
|
239
295
|
|
|
240
296
|
expect(cls1).not.toBe(cls2)
|
|
@@ -242,14 +298,16 @@ describe("P3 features", () => {
|
|
|
242
298
|
expect(cls2).toMatch(/^pyr-/)
|
|
243
299
|
})
|
|
244
300
|
|
|
245
|
-
it(
|
|
246
|
-
const Comp = styled(
|
|
301
|
+
it('same dynamic values produce same className (dedup cache)', () => {
|
|
302
|
+
const Comp = styled('div')`
|
|
303
|
+
color: ${(p: any) => p.$color};
|
|
304
|
+
`
|
|
247
305
|
|
|
248
|
-
const vnode1 = Comp({ $color:
|
|
306
|
+
const vnode1 = Comp({ $color: 'red' }) as VNode
|
|
249
307
|
const cls1 = vnode1.props.class as string
|
|
250
308
|
|
|
251
309
|
// Re-render with same value
|
|
252
|
-
const vnode2 = Comp({ $color:
|
|
310
|
+
const vnode2 = Comp({ $color: 'red' }) as VNode
|
|
253
311
|
const cls2 = vnode2.props.class as string
|
|
254
312
|
|
|
255
313
|
expect(cls1).toBe(cls2)
|