@pyreon/styler 0.11.0 → 0.11.2

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/package.json +12 -10
  2. package/src/ThemeProvider.ts +37 -0
  3. package/src/__tests__/ThemeProvider.test.ts +67 -0
  4. package/src/__tests__/benchmark.bench.ts +189 -0
  5. package/src/__tests__/composition-chain.test.ts +489 -0
  6. package/src/__tests__/css.test.ts +70 -0
  7. package/src/__tests__/forward.test.ts +282 -0
  8. package/src/__tests__/globalStyle.test.ts +72 -0
  9. package/src/__tests__/hash.test.ts +70 -0
  10. package/src/__tests__/hybrid-injection.test.ts +205 -0
  11. package/src/__tests__/index.ts +14 -0
  12. package/src/__tests__/insertion-effect.test.ts +106 -0
  13. package/src/__tests__/integration.test.ts +149 -0
  14. package/src/__tests__/keyframes.test.ts +68 -0
  15. package/src/__tests__/memory-growth.test.ts +152 -0
  16. package/src/__tests__/p3-features.test.ts +258 -0
  17. package/src/__tests__/resolve.test.ts +249 -0
  18. package/src/__tests__/shared.test.ts +73 -0
  19. package/src/__tests__/sheet-advanced.test.ts +669 -0
  20. package/src/__tests__/sheet-split-atrules.test.ts +411 -0
  21. package/src/__tests__/sheet.test.ts +164 -0
  22. package/src/__tests__/styled-ssr.test.ts +67 -0
  23. package/src/__tests__/styled.test.ts +303 -0
  24. package/src/__tests__/theme.test.ts +33 -0
  25. package/src/__tests__/useCSS.test.ts +142 -0
  26. package/src/css.ts +13 -0
  27. package/src/forward.ts +276 -0
  28. package/src/globalStyle.ts +48 -0
  29. package/src/hash.ts +30 -0
  30. package/src/index.ts +15 -0
  31. package/src/keyframes.ts +36 -0
  32. package/src/resolve.ts +172 -0
  33. package/src/shared.ts +12 -0
  34. package/src/sheet.ts +387 -0
  35. package/src/styled.tsx +277 -0
  36. package/src/useCSS.ts +20 -0
@@ -0,0 +1,258 @@
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
+ })`display: flex;`
14
+
15
+ const vnode = Comp({ color: "red", title: "hello" }) as VNode
16
+ expect(vnode.props.color).toBeUndefined()
17
+ expect(vnode.props.title).toBe("hello")
18
+ })
19
+
20
+ it("custom filter receives all non-system props", () => {
21
+ const forwarded: string[] = []
22
+ const Comp = styled("div", {
23
+ shouldForwardProp: (prop) => {
24
+ forwarded.push(prop)
25
+ return true
26
+ },
27
+ })`display: flex;`
28
+
29
+ Comp({ "data-x": "1", title: "hi" })
30
+ expect(forwarded).toContain("data-x")
31
+ expect(forwarded).toContain("title")
32
+ })
33
+
34
+ it("works with dynamic interpolations", () => {
35
+ const Comp = styled("div", {
36
+ shouldForwardProp: (prop) => prop === "title",
37
+ })`color: ${(p: any) => p.$color};`
38
+
39
+ const vnode = Comp({ $color: "red", title: "yes", custom: "no" }) as VNode
40
+ expect(vnode.props.title).toBe("yes")
41
+ expect(vnode.props.custom).toBeUndefined()
42
+ })
43
+
44
+ it("does not affect component wrapping (components receive all props)", () => {
45
+ const Inner = (props: { class?: string; myProp?: string }) =>
46
+ h("div", { class: props.class, "data-my": props.myProp })
47
+
48
+ // shouldForwardProp is only for HTML elements
49
+ const Comp = styled(Inner, {
50
+ shouldForwardProp: () => false,
51
+ })`color: red;`
52
+
53
+ const vnode = Comp({ myProp: "hello" }) as VNode
54
+ // Components always receive all props (no filtering)
55
+ // The VNode wraps Inner and should pass myProp through
56
+ expect(vnode.props.myProp).toBe("hello")
57
+ })
58
+ })
59
+
60
+ describe("styled(StyledComponent) — extending", () => {
61
+ it("extends a styled component", () => {
62
+ const Base = styled("div")`color: red;`
63
+ const Extended = styled(Base)`font-size: 20px;`
64
+
65
+ const vnode = Extended({}) as VNode
66
+ // Extended wraps Base, so Base applies its own className
67
+ // and Extended passes its className to Base as a prop
68
+ expect(vnode.props.class).toContain("pyr-")
69
+ })
70
+
71
+ it("extended component receives className from outer", () => {
72
+ const Base = styled("div")`color: red;`
73
+ const Extended = styled(Base)`font-size: 20px;`
74
+
75
+ const vnode = Extended({ className: "user-cls" }) as VNode
76
+ expect(vnode.props.class).toContain("user-cls")
77
+ })
78
+
79
+ it("multi-level extension works", () => {
80
+ const L1 = styled("div")`display: flex;`
81
+ const L2 = styled(L1)`color: red;`
82
+ const L3 = styled(L2)`font-size: 14px;`
83
+
84
+ const vnode = L3({}) as VNode
85
+ expect(vnode.props.class).toContain("pyr-")
86
+ // L3 wraps L2 which wraps L1 which wraps 'div'.
87
+ // The outermost VNode's type is the next component in the chain.
88
+ expect(typeof vnode.type).toBe("function")
89
+ })
90
+ })
91
+
92
+ describe("HMR cleanup API", () => {
93
+ beforeEach(() => {
94
+ document.querySelectorAll("style[data-pyreon-styler]").forEach((el) => {
95
+ el.remove()
96
+ })
97
+ })
98
+
99
+ it("clearCache removes all cached entries", () => {
100
+ const s = createSheet()
101
+ s.insert("color: red;")
102
+ s.insert("color: blue;")
103
+ expect(s.cacheSize).toBe(2)
104
+
105
+ s.clearCache()
106
+ expect(s.cacheSize).toBe(0)
107
+ })
108
+
109
+ it("clearAll removes cache, SSR buffer, and DOM rules", () => {
110
+ const s = new StyleSheet()
111
+ s.insert("color: red;")
112
+ s.insert("color: blue;")
113
+ expect(s.cacheSize).toBe(2)
114
+
115
+ s.clearAll()
116
+ expect(s.cacheSize).toBe(0)
117
+ })
118
+
119
+ it("after clearCache, same CSS gets re-inserted", () => {
120
+ const s = createSheet()
121
+ s.insert("color: red;")
122
+ expect(s.cacheSize).toBe(1)
123
+
124
+ s.clearCache()
125
+ expect(s.cacheSize).toBe(0)
126
+
127
+ // Re-insert — should work since cache was cleared
128
+ s.insert("color: red;")
129
+ expect(s.cacheSize).toBe(1)
130
+ })
131
+ })
132
+
133
+ describe("HMR cleanup API (SSR mode)", () => {
134
+ let originalDocument: typeof document
135
+
136
+ beforeEach(() => {
137
+ originalDocument = globalThis.document
138
+ // @ts-expect-error - intentionally deleting for SSR simulation
139
+ delete globalThis.document
140
+ })
141
+
142
+ afterEach(() => {
143
+ globalThis.document = originalDocument
144
+ })
145
+
146
+ it("clearAll in SSR mode clears buffer and cache", () => {
147
+ const s = createSheet()
148
+ s.insert("color: red;")
149
+ expect(s.getStyles()).toContain("color: red;")
150
+ expect(s.cacheSize).toBe(1)
151
+
152
+ s.clearAll()
153
+ expect(s.getStyles()).toBe("")
154
+ expect(s.cacheSize).toBe(0)
155
+ })
156
+ })
157
+
158
+ describe("CSS nesting (& selectors)", () => {
159
+ it("& selectors pass through to the CSS rule", () => {
160
+ // Native CSS nesting is supported by modern browsers
161
+ // The resolver passes CSS through without transformation
162
+ const Comp = styled("div")`
163
+ color: red;
164
+ &:hover { color: blue; }
165
+ `
166
+ const vnode = Comp({}) as VNode
167
+ expect(vnode.props.class).toMatch(/^pyr-/)
168
+ })
169
+
170
+ it("nested & with pseudo-elements", () => {
171
+ const Comp = styled("div")`
172
+ position: relative;
173
+ &::before { content: ""; display: block; }
174
+ &::after { content: ""; display: block; }
175
+ `
176
+ const vnode = Comp({}) as VNode
177
+ expect(vnode.props.class).toMatch(/^pyr-/)
178
+ })
179
+ })
180
+
181
+ describe("edge cases", () => {
182
+ it("empty template with dynamic interpolation returning nothing", () => {
183
+ const Comp = styled("div")`${(p: any) => p.$show && css`color: red;`}`
184
+ const vnode = Comp({ $show: false }) as VNode
185
+ // When resolved CSS is empty/whitespace, no className
186
+ expect(vnode.props.class).toBeFalsy()
187
+ })
188
+
189
+ it("empty template with dynamic interpolation returning value", () => {
190
+ const Comp = styled("div")`${(p: any) => p.$show && css`color: red;`}`
191
+ const vnode = Comp({ $show: true }) as VNode
192
+ expect(vnode.props.class).toMatch(/^pyr-/)
193
+ })
194
+
195
+ it("deeply nested CSSResult chains resolve correctly", () => {
196
+ const l1 = css`color: red;`
197
+ const l2 = css`${l1} font-size: 14px;`
198
+ const l3 = css`${l2} display: flex;`
199
+ const l4 = css`${l3} padding: 8px;`
200
+ const l5 = css`${l4} margin: 4px;`
201
+
202
+ const resolved = l5.toString()
203
+ expect(resolved).toContain("color: red;")
204
+ expect(resolved).toContain("font-size: 14px;")
205
+ expect(resolved).toContain("display: flex;")
206
+ expect(resolved).toContain("padding: 8px;")
207
+ expect(resolved).toContain("margin: 4px;")
208
+ })
209
+
210
+ it("anonymous component gets fallback displayName", () => {
211
+ const Anon = (() => {
212
+ const fn = () => null
213
+ Object.defineProperty(fn, "name", { value: "" })
214
+ return fn
215
+ })()
216
+
217
+ const Comp = styled(Anon)`color: red;`
218
+ expect((Comp as any).displayName).toBe("styled(Component)")
219
+ })
220
+
221
+ it("handles very large CSS strings", () => {
222
+ const bigCSS = Array.from({ length: 100 }, (_, i) => `prop${i}: val${i};`).join(" ")
223
+ const Comp = styled("div")`${bigCSS}`
224
+ const vnode = Comp({}) as VNode
225
+ expect(vnode.props.class).toMatch(/^pyr-/)
226
+ })
227
+
228
+ it("different dynamic values cause different classNames", () => {
229
+ const Comp = styled("div")`
230
+ color: ${(p: any) => p.$color};
231
+ font-size: ${(p: any) => p.$size};
232
+ `
233
+
234
+ const vnode1 = Comp({ $color: "red", $size: "14px" }) as VNode
235
+ const cls1 = vnode1.props.class as string
236
+
237
+ const vnode2 = Comp({ $color: "blue", $size: "16px" }) as VNode
238
+ const cls2 = vnode2.props.class as string
239
+
240
+ expect(cls1).not.toBe(cls2)
241
+ expect(cls1).toMatch(/^pyr-/)
242
+ expect(cls2).toMatch(/^pyr-/)
243
+ })
244
+
245
+ it("same dynamic values produce same className (dedup cache)", () => {
246
+ const Comp = styled("div")`color: ${(p: any) => p.$color};`
247
+
248
+ const vnode1 = Comp({ $color: "red" }) as VNode
249
+ const cls1 = vnode1.props.class as string
250
+
251
+ // Re-render with same value
252
+ const vnode2 = Comp({ $color: "red" }) as VNode
253
+ const cls2 = vnode2.props.class as string
254
+
255
+ expect(cls1).toBe(cls2)
256
+ })
257
+ })
258
+ })
@@ -0,0 +1,249 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { css } from "../css"
3
+ import { CSSResult, normalizeCSS, resolve, resolveValue } from "../resolve"
4
+
5
+ // Helper to create a TemplateStringsArray
6
+ const tsa = (strings: readonly string[]): TemplateStringsArray => {
7
+ const arr = [...strings] as string[] & { raw: readonly string[] }
8
+ arr.raw = strings
9
+ return arr
10
+ }
11
+
12
+ describe("resolve", () => {
13
+ describe("primitive interpolations", () => {
14
+ it("resolves strings", () => {
15
+ const result = resolve(tsa(["color: ", ";"]), ["red"], {})
16
+ expect(result).toBe("color: red;")
17
+ })
18
+
19
+ it("resolves numbers", () => {
20
+ const result = resolve(tsa(["flex: ", ";"]), [1], {})
21
+ expect(result).toBe("flex: 1;")
22
+ })
23
+
24
+ it("resolves null as empty string", () => {
25
+ const result = resolve(tsa(["a", "b"]), [null], {})
26
+ expect(result).toBe("ab")
27
+ })
28
+
29
+ it("resolves undefined as empty string", () => {
30
+ const result = resolve(tsa(["a", "b"]), [undefined], {})
31
+ expect(result).toBe("ab")
32
+ })
33
+
34
+ it("resolves false as empty string", () => {
35
+ const result = resolve(tsa(["a", "b"]), [false], {})
36
+ expect(result).toBe("ab")
37
+ })
38
+
39
+ it("resolves true as empty string", () => {
40
+ const result = resolve(tsa(["a", "b"]), [true], {})
41
+ expect(result).toBe("ab")
42
+ })
43
+ })
44
+
45
+ describe("function interpolations", () => {
46
+ it("calls functions with props and uses return value", () => {
47
+ const fn = (props: Record<string, unknown>) => props.color as string
48
+ const result = resolve(tsa(["color: ", ";"]), [fn], { color: "blue" })
49
+ expect(result).toBe("color: blue;")
50
+ })
51
+
52
+ it("resolves nested function results recursively", () => {
53
+ const fn = () => () => "red"
54
+ const result = resolve(tsa(["color: ", ";"]), [fn], {})
55
+ expect(result).toBe("color: red;")
56
+ })
57
+
58
+ it("handles functions returning null", () => {
59
+ const fn = () => null
60
+ const result = resolve(tsa(["a", "b"]), [fn], {})
61
+ expect(result).toBe("ab")
62
+ })
63
+
64
+ it("handles functions returning false (conditional)", () => {
65
+ const fn = (props: Record<string, unknown>) => (props.active ? "color: red;" : false)
66
+ const result = resolve(tsa(["", ""]), [fn], { active: false })
67
+ expect(result).toBe("")
68
+ })
69
+
70
+ it("uses empty object when no props provided", () => {
71
+ const fn = (props: Record<string, unknown>) =>
72
+ Object.keys(props).length === 0 ? "empty" : "has-props"
73
+ const result = resolve(tsa(["", ""]), [fn], {})
74
+ expect(result).toBe("empty")
75
+ })
76
+ })
77
+
78
+ describe("CSSResult interpolations", () => {
79
+ it("resolves nested CSSResult", () => {
80
+ const inner = css`color: red;`
81
+ const result = resolveValue(inner, {})
82
+ expect(result).toBe("color: red;")
83
+ })
84
+
85
+ it("resolves deeply nested CSSResults", () => {
86
+ const inner1 = css`color: red;`
87
+ const inner2 = css`${inner1} display: flex;`
88
+ const result = resolveValue(inner2, {})
89
+ expect(result).toBe("color: red; display: flex;")
90
+ })
91
+
92
+ it("resolves CSSResult with function interpolations", () => {
93
+ const inner = css`color: ${((p: Record<string, unknown>) => p.color) as any};`
94
+ const result = resolveValue(inner, { color: "green" })
95
+ expect(result).toBe("color: green;")
96
+ })
97
+ })
98
+
99
+ describe("array interpolations", () => {
100
+ it("resolves arrays of values", () => {
101
+ const result = resolve(tsa(["", ""]), [["a", "b", "c"]], {})
102
+ expect(result).toBe("abc")
103
+ })
104
+
105
+ it("resolves arrays with CSSResults", () => {
106
+ const inner = css`color: red;`
107
+ const result = resolveValue([inner, " display: flex;"], {})
108
+ expect(result).toBe("color: red; display: flex;")
109
+ })
110
+ })
111
+
112
+ describe("combined patterns", () => {
113
+ it("handles multiple interpolation types", () => {
114
+ const result = resolve(
115
+ tsa(["display: ", "; color: ", "; flex: ", ";"]),
116
+ ["flex", "red", 1],
117
+ {},
118
+ )
119
+ expect(result).toBe("display: flex; color: red; flex: 1;")
120
+ })
121
+
122
+ it("handles conditional CSS with logical AND (truthy)", () => {
123
+ const condition = true
124
+ const conditionalCss = condition && css`color: red;`
125
+ const result = resolveValue(conditionalCss, {})
126
+ expect(result).toBe("color: red;")
127
+ })
128
+
129
+ it("handles conditional CSS with logical AND (falsy)", () => {
130
+ const condition = false
131
+ const conditionalCss = condition && css`color: red;`
132
+ const result = resolveValue(conditionalCss, {})
133
+ expect(result).toBe("")
134
+ })
135
+ })
136
+ })
137
+
138
+ describe("CSSResult", () => {
139
+ it("stores strings and values as readonly properties", () => {
140
+ const strings = ["color: ", ";"] as unknown as TemplateStringsArray
141
+ const values = ["red"]
142
+ const result = new CSSResult(strings, values)
143
+ expect(result.strings).toBe(strings)
144
+ expect(result.values).toBe(values)
145
+ })
146
+
147
+ it("toString resolves with empty props", () => {
148
+ const result = css`color: red;`
149
+ expect(result.toString()).toBe("color: red;")
150
+ })
151
+ })
152
+
153
+ describe("normalizeCSS", () => {
154
+ describe("comment stripping", () => {
155
+ it("strips CSS block comments", () => {
156
+ expect(normalizeCSS("/* comment */ color: red;")).toBe("color: red;")
157
+ })
158
+
159
+ it("strips multiple block comments", () => {
160
+ expect(normalizeCSS("/* BASE */ color: red; /* HOVER */ font-size: 1rem;")).toBe(
161
+ "color: red; font-size: 1rem;",
162
+ )
163
+ })
164
+
165
+ it("strips multiline block comments", () => {
166
+ expect(normalizeCSS("/* --------\n BASE STATE\n -------- */\nheight: 3rem;")).toBe(
167
+ "height: 3rem;",
168
+ )
169
+ })
170
+
171
+ it("strips JS-style line comments", () => {
172
+ expect(normalizeCSS("// this is not valid CSS\ncolor: red;")).toBe("color: red;")
173
+ })
174
+
175
+ it("preserves :// in URLs", () => {
176
+ expect(normalizeCSS("background: url(https://example.com/img.png);")).toContain(
177
+ "https://example.com/img.png",
178
+ )
179
+ })
180
+
181
+ it("strips line comments but preserves URL protocols", () => {
182
+ const result = normalizeCSS("// comment\nbackground: url(https://example.com/img.png);")
183
+ expect(result).toContain("https://example.com/img.png")
184
+ expect(result).not.toContain("// comment")
185
+ })
186
+
187
+ it("handles unterminated block comment", () => {
188
+ expect(normalizeCSS("color: red; /* never closed")).toBe("color: red;")
189
+ })
190
+
191
+ it("handles unterminated line comment", () => {
192
+ expect(normalizeCSS("color: red;\n// trailing comment")).toBe("color: red;")
193
+ })
194
+ })
195
+
196
+ describe("whitespace handling", () => {
197
+ it("collapses whitespace", () => {
198
+ expect(normalizeCSS(" color: red; font-size: 1rem; ")).toBe(
199
+ "color: red; font-size: 1rem;",
200
+ )
201
+ })
202
+
203
+ it("converts tabs and newlines to spaces", () => {
204
+ expect(normalizeCSS("color:\tred;\nfont-size:\t1rem;")).toBe("color: red; font-size: 1rem;")
205
+ })
206
+
207
+ it("collapses multiple spaces", () => {
208
+ expect(normalizeCSS("color: red;")).toBe("color: red;")
209
+ })
210
+
211
+ it("trims leading and trailing whitespace", () => {
212
+ expect(normalizeCSS(" color: red; ")).toBe("color: red;")
213
+ })
214
+
215
+ it("handles carriage returns", () => {
216
+ expect(normalizeCSS("color: red;\r\nfont-size: 1rem;")).toBe("color: red; font-size: 1rem;")
217
+ })
218
+ })
219
+
220
+ describe("semicolon handling", () => {
221
+ it("removes redundant semicolons after {", () => {
222
+ expect(normalizeCSS(".foo {; color: red; }")).toBe(".foo { color: red; }")
223
+ })
224
+
225
+ it("removes redundant semicolons after }", () => {
226
+ expect(normalizeCSS(".foo { color: red; }; .bar { }")).toBe(".foo { color: red; } .bar { }")
227
+ })
228
+ })
229
+
230
+ describe("edge cases", () => {
231
+ it("returns empty string for empty input", () => {
232
+ expect(normalizeCSS("")).toBe("")
233
+ })
234
+
235
+ it("returns empty string for whitespace-only input", () => {
236
+ expect(normalizeCSS(" \n\t ")).toBe("")
237
+ })
238
+
239
+ it("handles CSS with braces", () => {
240
+ expect(normalizeCSS(".foo { color: red; }")).toBe(".foo { color: red; }")
241
+ })
242
+
243
+ it("handles @media rules", () => {
244
+ const result = normalizeCSS("@media (min-width: 48em) { color: blue; }")
245
+ expect(result).toContain("@media")
246
+ expect(result).toContain("color: blue;")
247
+ })
248
+ })
249
+ })
@@ -0,0 +1,73 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { css } from "../css"
3
+ import { CSSResult } from "../resolve"
4
+ import { isDynamic } from "../shared"
5
+
6
+ describe("isDynamic", () => {
7
+ it("returns true for function values", () => {
8
+ expect(isDynamic(() => "red")).toBe(true)
9
+ })
10
+
11
+ it("returns false for string values", () => {
12
+ expect(isDynamic("color: red;")).toBe(false)
13
+ })
14
+
15
+ it("returns false for number values", () => {
16
+ expect(isDynamic(42)).toBe(false)
17
+ })
18
+
19
+ it("returns false for null and undefined", () => {
20
+ expect(isDynamic(null)).toBe(false)
21
+ expect(isDynamic(undefined)).toBe(false)
22
+ })
23
+
24
+ it("returns false for boolean values", () => {
25
+ expect(isDynamic(true)).toBe(false)
26
+ expect(isDynamic(false)).toBe(false)
27
+ })
28
+
29
+ it("returns true for arrays containing functions", () => {
30
+ expect(isDynamic(["a", () => "b"])).toBe(true)
31
+ })
32
+
33
+ it("returns false for arrays of static values", () => {
34
+ expect(isDynamic(["a", "b", 42])).toBe(false)
35
+ })
36
+
37
+ it("returns true for CSSResult with dynamic values", () => {
38
+ const result = css`color: ${() => "red"};`
39
+ expect(isDynamic(result)).toBe(true)
40
+ })
41
+
42
+ it("returns false for CSSResult with only static values", () => {
43
+ const result = css`color: ${"red"};`
44
+ expect(isDynamic(result)).toBe(false)
45
+ })
46
+
47
+ it("returns true for nested dynamic CSSResult", () => {
48
+ const inner = css`color: ${() => "red"};`
49
+ const outer = css`${inner}`
50
+ expect(isDynamic(outer)).toBe(true)
51
+ })
52
+
53
+ it("returns false for nested static CSSResult", () => {
54
+ const inner = css`color: red;`
55
+ const outer = css`${inner}`
56
+ expect(isDynamic(outer)).toBe(false)
57
+ })
58
+
59
+ it("detects deeply nested dynamic values", () => {
60
+ const deep = css`color: ${() => "red"};`
61
+ const mid = css`${deep}`
62
+ const outer = css`${mid}`
63
+ expect(isDynamic(outer)).toBe(true)
64
+ })
65
+
66
+ it("handles arrays inside CSSResult", () => {
67
+ const result = new CSSResult(
68
+ Object.assign(["", ""], { raw: ["", ""] }) as TemplateStringsArray,
69
+ [["a", () => "b"]],
70
+ )
71
+ expect(isDynamic(result)).toBe(true)
72
+ })
73
+ })