@pyreon/unistyle 0.11.1 → 0.11.3
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/package.json +8 -7
- package/src/__tests__/alignContent.test.ts +121 -0
- package/src/__tests__/borderRadius.test.ts +125 -0
- package/src/__tests__/camelToKebab.test.ts +44 -0
- package/src/__tests__/context.test.ts +147 -0
- package/src/__tests__/createMediaQueries.test.ts +98 -0
- package/src/__tests__/edge.test.ts +164 -0
- package/src/__tests__/enrichTheme.test.ts +56 -0
- package/src/__tests__/extendCss.test.ts +45 -0
- package/src/__tests__/index.test.ts +79 -0
- package/src/__tests__/makeItResponsive.test.ts +171 -0
- package/src/__tests__/processDescriptor.test.ts +320 -0
- package/src/__tests__/responsive.test.ts +177 -0
- package/src/__tests__/styles.test.ts +119 -0
- package/src/__tests__/units.test.ts +134 -0
- package/src/context.tsx +34 -0
- package/src/enrichTheme.ts +42 -0
- package/src/index.ts +89 -0
- package/src/responsive/breakpoints.ts +15 -0
- package/src/responsive/createMediaQueries.ts +43 -0
- package/src/responsive/index.ts +14 -0
- package/src/responsive/makeItResponsive.ts +118 -0
- package/src/responsive/normalizeTheme.ts +65 -0
- package/src/responsive/optimizeTheme.ts +39 -0
- package/src/responsive/sortBreakpoints.ts +10 -0
- package/src/responsive/transformTheme.ts +48 -0
- package/src/styles/alignContent.ts +58 -0
- package/src/styles/extendCss.ts +26 -0
- package/src/styles/index.ts +16 -0
- package/src/styles/shorthands/borderRadius.ts +89 -0
- package/src/styles/shorthands/edge.ts +108 -0
- package/src/styles/shorthands/index.ts +4 -0
- package/src/styles/styles/camelToKebab.ts +3 -0
- package/src/styles/styles/index.ts +33 -0
- package/src/styles/styles/processDescriptor.ts +100 -0
- package/src/styles/styles/propertyMap.ts +436 -0
- package/src/styles/styles/types.ts +366 -0
- package/src/styles/styles/utils.ts +62 -0
- package/src/types.ts +175 -0
- package/src/units/index.ts +6 -0
- package/src/units/stripUnit.ts +25 -0
- package/src/units/value.ts +47 -0
- package/src/units/values.ts +40 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import processDescriptor from "../styles/styles/processDescriptor"
|
|
3
|
+
import type { PropertyDescriptor } from "../styles/styles/propertyMap"
|
|
4
|
+
import type { InnerTheme } from "../styles/styles/types"
|
|
5
|
+
|
|
6
|
+
// Minimal helpers matching the signature expected by processDescriptor
|
|
7
|
+
const mockCss = (strings: TemplateStringsArray, ...vals: any[]) => {
|
|
8
|
+
let result = ""
|
|
9
|
+
for (let i = 0; i < strings.length; i++) {
|
|
10
|
+
result += strings[i]
|
|
11
|
+
if (i < vals.length) result += String(vals[i] ?? "")
|
|
12
|
+
}
|
|
13
|
+
return result
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const mockCalc = (...params: any[]) => {
|
|
17
|
+
const val = params.find((p) => p != null)
|
|
18
|
+
if (val == null) return null
|
|
19
|
+
if (typeof val === "string") return val
|
|
20
|
+
return `${val / 16}rem`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const mockEdge = (_property: string, _values: any) => null
|
|
24
|
+
const mockBorderRadius = (_props: any) => null
|
|
25
|
+
|
|
26
|
+
const t = (overrides: Partial<InnerTheme> = {}): InnerTheme => overrides as InnerTheme
|
|
27
|
+
|
|
28
|
+
describe("processDescriptor", () => {
|
|
29
|
+
describe("simple kind", () => {
|
|
30
|
+
const d: PropertyDescriptor = { kind: "simple", css: "display", key: "display" }
|
|
31
|
+
|
|
32
|
+
it("returns CSS declaration when key has a value", () => {
|
|
33
|
+
const result = processDescriptor(
|
|
34
|
+
d,
|
|
35
|
+
t({ display: "flex" }),
|
|
36
|
+
mockCss,
|
|
37
|
+
mockCalc,
|
|
38
|
+
mockEdge as any,
|
|
39
|
+
mockBorderRadius as any,
|
|
40
|
+
)
|
|
41
|
+
expect(result).toBe("display: flex;")
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it("returns empty string when key is null", () => {
|
|
45
|
+
const result = processDescriptor(
|
|
46
|
+
d,
|
|
47
|
+
t({ display: null as any }),
|
|
48
|
+
mockCss,
|
|
49
|
+
mockCalc,
|
|
50
|
+
mockEdge as any,
|
|
51
|
+
mockBorderRadius as any,
|
|
52
|
+
)
|
|
53
|
+
expect(result).toBe("")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("returns empty string when key is undefined", () => {
|
|
57
|
+
const result = processDescriptor(
|
|
58
|
+
d,
|
|
59
|
+
t({}),
|
|
60
|
+
mockCss,
|
|
61
|
+
mockCalc,
|
|
62
|
+
mockEdge as any,
|
|
63
|
+
mockBorderRadius as any,
|
|
64
|
+
)
|
|
65
|
+
expect(result).toBe("")
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe("convert kind", () => {
|
|
70
|
+
const d: PropertyDescriptor = { kind: "convert", css: "width", key: "width" }
|
|
71
|
+
|
|
72
|
+
it("returns converted value through calc function", () => {
|
|
73
|
+
const result = processDescriptor(
|
|
74
|
+
d,
|
|
75
|
+
t({ width: 32 } as any),
|
|
76
|
+
mockCss,
|
|
77
|
+
mockCalc,
|
|
78
|
+
mockEdge as any,
|
|
79
|
+
mockBorderRadius as any,
|
|
80
|
+
)
|
|
81
|
+
expect(result).toBe("width: 2rem;")
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("passes through string values", () => {
|
|
85
|
+
const result = processDescriptor(
|
|
86
|
+
d,
|
|
87
|
+
t({ width: "50%" } as any),
|
|
88
|
+
mockCss,
|
|
89
|
+
mockCalc,
|
|
90
|
+
mockEdge as any,
|
|
91
|
+
mockBorderRadius as any,
|
|
92
|
+
)
|
|
93
|
+
expect(result).toBe("width: 50%;")
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe("convert_fallback kind", () => {
|
|
98
|
+
const d: PropertyDescriptor = {
|
|
99
|
+
kind: "convert_fallback",
|
|
100
|
+
css: "width",
|
|
101
|
+
keys: ["width", "size"] as (keyof InnerTheme)[],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
it("uses first defined key value", () => {
|
|
105
|
+
const result = processDescriptor(
|
|
106
|
+
d,
|
|
107
|
+
t({ width: 16 } as any),
|
|
108
|
+
mockCss,
|
|
109
|
+
mockCalc,
|
|
110
|
+
mockEdge as any,
|
|
111
|
+
mockBorderRadius as any,
|
|
112
|
+
)
|
|
113
|
+
expect(result).toBe("width: 1rem;")
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe("special kind", () => {
|
|
118
|
+
it("returns fullScreen CSS when fullScreen is truthy", () => {
|
|
119
|
+
const d: PropertyDescriptor = { kind: "special", id: "fullScreen" }
|
|
120
|
+
const result = processDescriptor(
|
|
121
|
+
d,
|
|
122
|
+
t({ fullScreen: true } as any),
|
|
123
|
+
mockCss,
|
|
124
|
+
mockCalc,
|
|
125
|
+
mockEdge as any,
|
|
126
|
+
mockBorderRadius as any,
|
|
127
|
+
)
|
|
128
|
+
expect(result).toContain("position: fixed")
|
|
129
|
+
expect(result).toContain("top: 0")
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("returns empty string when fullScreen is falsy", () => {
|
|
133
|
+
const d: PropertyDescriptor = { kind: "special", id: "fullScreen" }
|
|
134
|
+
const result = processDescriptor(
|
|
135
|
+
d,
|
|
136
|
+
t({}),
|
|
137
|
+
mockCss,
|
|
138
|
+
mockCalc,
|
|
139
|
+
mockEdge as any,
|
|
140
|
+
mockBorderRadius as any,
|
|
141
|
+
)
|
|
142
|
+
expect(result).toBe("")
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it("returns backgroundImage CSS when set", () => {
|
|
146
|
+
const d: PropertyDescriptor = { kind: "special", id: "backgroundImage" }
|
|
147
|
+
const result = processDescriptor(
|
|
148
|
+
d,
|
|
149
|
+
t({ backgroundImage: "url.png" } as any),
|
|
150
|
+
mockCss,
|
|
151
|
+
mockCalc,
|
|
152
|
+
mockEdge as any,
|
|
153
|
+
mockBorderRadius as any,
|
|
154
|
+
)
|
|
155
|
+
expect(result).toBe("background-image: url(url.png);")
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it("returns animation CSS when keyframe is set", () => {
|
|
159
|
+
const d: PropertyDescriptor = { kind: "special", id: "animation" }
|
|
160
|
+
const result = processDescriptor(
|
|
161
|
+
d,
|
|
162
|
+
t({ keyframe: "fadeIn", animation: "0.3s ease" } as any),
|
|
163
|
+
mockCss,
|
|
164
|
+
mockCalc,
|
|
165
|
+
mockEdge as any,
|
|
166
|
+
mockBorderRadius as any,
|
|
167
|
+
)
|
|
168
|
+
expect(result).toBe("animation: fadeIn 0.3s ease;")
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it("returns hideEmpty pseudo-selector when hideEmpty is true", () => {
|
|
172
|
+
const d: PropertyDescriptor = { kind: "special", id: "hideEmpty" }
|
|
173
|
+
const result = processDescriptor(
|
|
174
|
+
d,
|
|
175
|
+
t({ hideEmpty: true } as any),
|
|
176
|
+
mockCss,
|
|
177
|
+
mockCalc,
|
|
178
|
+
mockEdge as any,
|
|
179
|
+
mockBorderRadius as any,
|
|
180
|
+
)
|
|
181
|
+
expect(result).toContain("&:empty")
|
|
182
|
+
expect(result).toContain("display: none")
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it("returns clearFix pseudo-element when clearFix is true", () => {
|
|
186
|
+
const d: PropertyDescriptor = { kind: "special", id: "clearFix" }
|
|
187
|
+
const result = processDescriptor(
|
|
188
|
+
d,
|
|
189
|
+
t({ clearFix: true } as any),
|
|
190
|
+
mockCss,
|
|
191
|
+
mockCalc,
|
|
192
|
+
mockEdge as any,
|
|
193
|
+
mockBorderRadius as any,
|
|
194
|
+
)
|
|
195
|
+
expect(result).toContain("&::after")
|
|
196
|
+
expect(result).toContain("clear: both")
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it("returns empty string for unknown special id", () => {
|
|
200
|
+
const d: PropertyDescriptor = { kind: "special", id: "unknown" }
|
|
201
|
+
const result = processDescriptor(
|
|
202
|
+
d,
|
|
203
|
+
t({}),
|
|
204
|
+
mockCss,
|
|
205
|
+
mockCalc,
|
|
206
|
+
mockEdge as any,
|
|
207
|
+
mockBorderRadius as any,
|
|
208
|
+
)
|
|
209
|
+
expect(result).toBe("")
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
describe("edge kind", () => {
|
|
214
|
+
it("delegates to shorthand function", () => {
|
|
215
|
+
const d: PropertyDescriptor = {
|
|
216
|
+
kind: "edge",
|
|
217
|
+
property: "margin",
|
|
218
|
+
keys: {
|
|
219
|
+
full: "margin" as keyof InnerTheme,
|
|
220
|
+
x: "marginX" as keyof InnerTheme,
|
|
221
|
+
y: "marginY" as keyof InnerTheme,
|
|
222
|
+
top: "marginTop" as keyof InnerTheme,
|
|
223
|
+
left: "marginLeft" as keyof InnerTheme,
|
|
224
|
+
bottom: "marginBottom" as keyof InnerTheme,
|
|
225
|
+
right: "marginRight" as keyof InnerTheme,
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
const customEdge = () => "margin: 1rem;"
|
|
229
|
+
const result = processDescriptor(
|
|
230
|
+
d,
|
|
231
|
+
t({ margin: 16 } as any),
|
|
232
|
+
mockCss,
|
|
233
|
+
mockCalc,
|
|
234
|
+
customEdge as any,
|
|
235
|
+
mockBorderRadius as any,
|
|
236
|
+
)
|
|
237
|
+
expect(result).toBe("margin: 1rem;")
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it("returns empty string when shorthand returns null", () => {
|
|
241
|
+
const d: PropertyDescriptor = {
|
|
242
|
+
kind: "edge",
|
|
243
|
+
property: "padding",
|
|
244
|
+
keys: {
|
|
245
|
+
full: "padding" as keyof InnerTheme,
|
|
246
|
+
x: "paddingX" as keyof InnerTheme,
|
|
247
|
+
y: "paddingY" as keyof InnerTheme,
|
|
248
|
+
top: "paddingTop" as keyof InnerTheme,
|
|
249
|
+
left: "paddingLeft" as keyof InnerTheme,
|
|
250
|
+
bottom: "paddingBottom" as keyof InnerTheme,
|
|
251
|
+
right: "paddingRight" as keyof InnerTheme,
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
const result = processDescriptor(
|
|
255
|
+
d,
|
|
256
|
+
t({}),
|
|
257
|
+
mockCss,
|
|
258
|
+
mockCalc,
|
|
259
|
+
mockEdge as any,
|
|
260
|
+
mockBorderRadius as any,
|
|
261
|
+
)
|
|
262
|
+
expect(result).toBe("")
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
describe("border_radius kind", () => {
|
|
267
|
+
it("delegates to borderRadius function", () => {
|
|
268
|
+
const d: PropertyDescriptor = {
|
|
269
|
+
kind: "border_radius",
|
|
270
|
+
keys: {
|
|
271
|
+
full: "borderRadius" as keyof InnerTheme,
|
|
272
|
+
top: "borderRadiusTop" as keyof InnerTheme,
|
|
273
|
+
bottom: "borderRadiusBottom" as keyof InnerTheme,
|
|
274
|
+
left: "borderRadiusLeft" as keyof InnerTheme,
|
|
275
|
+
right: "borderRadiusRight" as keyof InnerTheme,
|
|
276
|
+
topLeft: "borderRadiusTopLeft" as keyof InnerTheme,
|
|
277
|
+
topRight: "borderRadiusTopRight" as keyof InnerTheme,
|
|
278
|
+
bottomLeft: "borderRadiusBottomLeft" as keyof InnerTheme,
|
|
279
|
+
bottomRight: "borderRadiusBottomRight" as keyof InnerTheme,
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
const customBR = () => "border-radius: 4px;"
|
|
283
|
+
const result = processDescriptor(
|
|
284
|
+
d,
|
|
285
|
+
t({ borderRadius: 4 } as any),
|
|
286
|
+
mockCss,
|
|
287
|
+
mockCalc,
|
|
288
|
+
mockEdge as any,
|
|
289
|
+
customBR as any,
|
|
290
|
+
)
|
|
291
|
+
expect(result).toBe("border-radius: 4px;")
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it("returns empty string when borderRadius returns null", () => {
|
|
295
|
+
const d: PropertyDescriptor = {
|
|
296
|
+
kind: "border_radius",
|
|
297
|
+
keys: {
|
|
298
|
+
full: "borderRadius" as keyof InnerTheme,
|
|
299
|
+
top: "borderRadiusTop" as keyof InnerTheme,
|
|
300
|
+
bottom: "borderRadiusBottom" as keyof InnerTheme,
|
|
301
|
+
left: "borderRadiusLeft" as keyof InnerTheme,
|
|
302
|
+
right: "borderRadiusRight" as keyof InnerTheme,
|
|
303
|
+
topLeft: "borderRadiusTopLeft" as keyof InnerTheme,
|
|
304
|
+
topRight: "borderRadiusTopRight" as keyof InnerTheme,
|
|
305
|
+
bottomLeft: "borderRadiusBottomLeft" as keyof InnerTheme,
|
|
306
|
+
bottomRight: "borderRadiusBottomRight" as keyof InnerTheme,
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
const result = processDescriptor(
|
|
310
|
+
d,
|
|
311
|
+
t({}),
|
|
312
|
+
mockCss,
|
|
313
|
+
mockCalc,
|
|
314
|
+
mockEdge as any,
|
|
315
|
+
mockBorderRadius as any,
|
|
316
|
+
)
|
|
317
|
+
expect(result).toBe("")
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
})
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import breakpoints from "../responsive/breakpoints"
|
|
3
|
+
import normalizeTheme from "../responsive/normalizeTheme"
|
|
4
|
+
import optimizeTheme from "../responsive/optimizeTheme"
|
|
5
|
+
import sortBreakpoints from "../responsive/sortBreakpoints"
|
|
6
|
+
import transformTheme from "../responsive/transformTheme"
|
|
7
|
+
|
|
8
|
+
describe("breakpoints", () => {
|
|
9
|
+
it("has expected default config", () => {
|
|
10
|
+
expect(breakpoints.rootSize).toBe(16)
|
|
11
|
+
expect(breakpoints.breakpoints).toHaveProperty("xs")
|
|
12
|
+
expect(breakpoints.breakpoints).toHaveProperty("sm")
|
|
13
|
+
expect(breakpoints.breakpoints).toHaveProperty("md")
|
|
14
|
+
expect(breakpoints.breakpoints).toHaveProperty("lg")
|
|
15
|
+
expect(breakpoints.breakpoints).toHaveProperty("xl")
|
|
16
|
+
expect(breakpoints.breakpoints).toHaveProperty("xxl")
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("has correct pixel values", () => {
|
|
20
|
+
expect(breakpoints.breakpoints.xs).toBe(0)
|
|
21
|
+
expect(breakpoints.breakpoints.sm).toBe(576)
|
|
22
|
+
expect(breakpoints.breakpoints.md).toBe(768)
|
|
23
|
+
expect(breakpoints.breakpoints.lg).toBe(992)
|
|
24
|
+
expect(breakpoints.breakpoints.xl).toBe(1200)
|
|
25
|
+
expect(breakpoints.breakpoints.xxl).toBe(1440)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe("sortBreakpoints", () => {
|
|
30
|
+
it("sorts breakpoints by value ascending, returns keys", () => {
|
|
31
|
+
const bps = { md: 768, xs: 0, xl: 1200, sm: 576 }
|
|
32
|
+
const sorted = sortBreakpoints(bps)
|
|
33
|
+
expect(sorted).toEqual(["xs", "sm", "md", "xl"])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("handles already sorted breakpoints", () => {
|
|
37
|
+
const sorted = sortBreakpoints({ xs: 0, sm: 576, md: 768 })
|
|
38
|
+
expect(sorted).toEqual(["xs", "sm", "md"])
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("handles single breakpoint", () => {
|
|
42
|
+
expect(sortBreakpoints({ xs: 0 })).toEqual(["xs"])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("handles empty object", () => {
|
|
46
|
+
expect(sortBreakpoints({})).toEqual([])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("sorts full default breakpoint set", () => {
|
|
50
|
+
const sorted = sortBreakpoints(breakpoints.breakpoints)
|
|
51
|
+
expect(sorted).toEqual(["xs", "sm", "md", "lg", "xl", "xxl"])
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe("normalizeTheme", () => {
|
|
56
|
+
const bpKeys = ["xs", "sm", "md", "lg", "xl"]
|
|
57
|
+
|
|
58
|
+
it("returns theme as-is when no nested objects/arrays", () => {
|
|
59
|
+
const theme = { color: "red", fontSize: 16 }
|
|
60
|
+
const result = normalizeTheme({ theme, breakpoints: bpKeys })
|
|
61
|
+
expect(result).toEqual(theme)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it("expands array values across breakpoints", () => {
|
|
65
|
+
const theme = { fontSize: [12, 14, 16, 18, 20] }
|
|
66
|
+
const result = normalizeTheme({ theme, breakpoints: bpKeys })
|
|
67
|
+
expect(result.fontSize).toEqual({
|
|
68
|
+
xs: 12,
|
|
69
|
+
sm: 14,
|
|
70
|
+
md: 16,
|
|
71
|
+
lg: 18,
|
|
72
|
+
xl: 20,
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it("array values use last value for extra breakpoints", () => {
|
|
77
|
+
const theme = { fontSize: [12, 14] }
|
|
78
|
+
const result = normalizeTheme({ theme, breakpoints: bpKeys })
|
|
79
|
+
expect((result.fontSize as Record<string, unknown>).xs).toBe(12)
|
|
80
|
+
expect((result.fontSize as Record<string, unknown>).sm).toBe(14)
|
|
81
|
+
expect((result.fontSize as Record<string, unknown>).md).toBe(14)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("expands object values with carry-forward", () => {
|
|
85
|
+
const theme = { fontSize: { xs: 12, md: 16 } }
|
|
86
|
+
const result = normalizeTheme({ theme, breakpoints: bpKeys })
|
|
87
|
+
const fs = result.fontSize as Record<string, unknown>
|
|
88
|
+
expect(fs.xs).toBe(12)
|
|
89
|
+
expect(fs.sm).toBe(12) // carried from xs
|
|
90
|
+
expect(fs.md).toBe(16)
|
|
91
|
+
expect(fs.lg).toBe(16) // carried from md
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it("skips null values", () => {
|
|
95
|
+
const theme = { color: null, fontSize: 16 }
|
|
96
|
+
const result = normalizeTheme({ theme, breakpoints: bpKeys })
|
|
97
|
+
expect(result.color).toBeUndefined()
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
describe("transformTheme", () => {
|
|
102
|
+
const bpKeys = ["xs", "sm", "md"]
|
|
103
|
+
|
|
104
|
+
it("pivots scalar values to first breakpoint", () => {
|
|
105
|
+
const theme = { color: "red" }
|
|
106
|
+
const result = transformTheme({ theme, breakpoints: bpKeys })
|
|
107
|
+
expect(result.xs).toEqual({ color: "red" })
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it("pivots object values to breakpoints", () => {
|
|
111
|
+
const theme = { color: { xs: "red", md: "blue" } }
|
|
112
|
+
const result = transformTheme({ theme, breakpoints: bpKeys })
|
|
113
|
+
expect(result.xs).toEqual({ color: "red" })
|
|
114
|
+
expect(result.md).toEqual({ color: "blue" })
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it("pivots array values by index", () => {
|
|
118
|
+
const theme = { fontSize: [12, 14, 16] }
|
|
119
|
+
const result = transformTheme({ theme, breakpoints: bpKeys })
|
|
120
|
+
expect(result.xs).toEqual({ fontSize: 12 })
|
|
121
|
+
expect(result.sm).toEqual({ fontSize: 14 })
|
|
122
|
+
expect(result.md).toEqual({ fontSize: 16 })
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it("returns empty object for empty theme", () => {
|
|
126
|
+
expect(transformTheme({ theme: {}, breakpoints: bpKeys })).toEqual({})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it("returns empty object for empty breakpoints", () => {
|
|
130
|
+
expect(transformTheme({ theme: { color: "red" }, breakpoints: [] })).toEqual({})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it("filters out unexpected breakpoint keys", () => {
|
|
134
|
+
const theme = { color: { xs: "red", unknown: "green" } }
|
|
135
|
+
const result = transformTheme({ theme, breakpoints: bpKeys })
|
|
136
|
+
expect(result).not.toHaveProperty("unknown")
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe("optimizeTheme", () => {
|
|
141
|
+
const bpKeys = ["xs", "sm", "md", "lg"]
|
|
142
|
+
|
|
143
|
+
it("keeps first breakpoint", () => {
|
|
144
|
+
const theme = {
|
|
145
|
+
xs: { color: "red" },
|
|
146
|
+
sm: { color: "blue" },
|
|
147
|
+
}
|
|
148
|
+
const result = optimizeTheme({ theme, breakpoints: bpKeys })
|
|
149
|
+
expect(result.xs).toEqual({ color: "red" })
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it("removes duplicate breakpoints", () => {
|
|
153
|
+
const theme = {
|
|
154
|
+
xs: { color: "red" },
|
|
155
|
+
sm: { color: "red" },
|
|
156
|
+
md: { color: "blue" },
|
|
157
|
+
}
|
|
158
|
+
const result = optimizeTheme({ theme, breakpoints: bpKeys })
|
|
159
|
+
expect(result.xs).toEqual({ color: "red" })
|
|
160
|
+
expect(result.sm).toBeUndefined()
|
|
161
|
+
expect(result.md).toEqual({ color: "blue" })
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it("keeps breakpoints with different values", () => {
|
|
165
|
+
const theme = {
|
|
166
|
+
xs: { color: "red", fontSize: 12 },
|
|
167
|
+
sm: { color: "red", fontSize: 14 },
|
|
168
|
+
}
|
|
169
|
+
const result = optimizeTheme({ theme, breakpoints: bpKeys })
|
|
170
|
+
expect(result.xs).toBeDefined()
|
|
171
|
+
expect(result.sm).toBeDefined()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it("handles empty theme", () => {
|
|
175
|
+
expect(optimizeTheme({ theme: {}, breakpoints: bpKeys })).toEqual({})
|
|
176
|
+
})
|
|
177
|
+
})
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import styles from "../styles/styles/index"
|
|
3
|
+
|
|
4
|
+
const mockCss = (strings: TemplateStringsArray, ...vals: any[]) => {
|
|
5
|
+
let r = ""
|
|
6
|
+
for (let i = 0; i < strings.length; i++) {
|
|
7
|
+
r += strings[i]
|
|
8
|
+
if (i < vals.length) r += String(vals[i])
|
|
9
|
+
}
|
|
10
|
+
return r
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("styles", () => {
|
|
14
|
+
it("empty theme returns empty string (all fragments are empty)", () => {
|
|
15
|
+
const result = styles({ theme: {}, css: mockCss, rootSize: 16 })
|
|
16
|
+
expect(result).toBe("")
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("single simple property: color", () => {
|
|
20
|
+
const result = styles({ theme: { color: "red" }, css: mockCss, rootSize: 16 })
|
|
21
|
+
expect(result).toContain("color: red;")
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it("simple property: display", () => {
|
|
25
|
+
const result = styles({ theme: { display: "flex" }, css: mockCss, rootSize: 16 })
|
|
26
|
+
expect(result).toContain("display: flex;")
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("convert property: width converts via value() with rootSize", () => {
|
|
30
|
+
// width is a convert_fallback with keys ["width", "size"]
|
|
31
|
+
// 160 / 16 = 10rem
|
|
32
|
+
const result = styles({ theme: { width: 160 }, css: mockCss, rootSize: 16 })
|
|
33
|
+
expect(result).toContain("width:")
|
|
34
|
+
expect(result).toContain("10rem")
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it("convert property: fontSize", () => {
|
|
38
|
+
// 32 / 16 = 2rem
|
|
39
|
+
const result = styles({ theme: { fontSize: 32 }, css: mockCss, rootSize: 16 })
|
|
40
|
+
expect(result).toContain("font-size:")
|
|
41
|
+
expect(result).toContain("2rem")
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it("edge property: margin generates margin shorthand", () => {
|
|
45
|
+
// margin 16 / 16 = 1rem
|
|
46
|
+
const result = styles({ theme: { margin: 16 }, css: mockCss, rootSize: 16 })
|
|
47
|
+
expect(result).toContain("margin:")
|
|
48
|
+
expect(result).toContain("1rem")
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("edge property: padding", () => {
|
|
52
|
+
const result = styles({ theme: { padding: 8 }, css: mockCss, rootSize: 16 })
|
|
53
|
+
expect(result).toContain("padding:")
|
|
54
|
+
expect(result).toContain("0.5rem")
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("border radius: borderRadius generates border-radius", () => {
|
|
58
|
+
// 8 / 16 = 0.5rem
|
|
59
|
+
const result = styles({ theme: { borderRadius: 8 }, css: mockCss, rootSize: 16 })
|
|
60
|
+
expect(result).toContain("border-radius:")
|
|
61
|
+
expect(result).toContain("0.5rem")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it("multiple properties combined", () => {
|
|
65
|
+
const result = styles({
|
|
66
|
+
theme: { color: "blue", display: "flex", fontSize: 16 },
|
|
67
|
+
css: mockCss,
|
|
68
|
+
rootSize: 16,
|
|
69
|
+
})
|
|
70
|
+
expect(result).toContain("color: blue;")
|
|
71
|
+
expect(result).toContain("display: flex;")
|
|
72
|
+
expect(result).toContain("font-size: 1rem;")
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it("special property: fullScreen", () => {
|
|
76
|
+
const result = styles({ theme: { fullScreen: true }, css: mockCss, rootSize: 16 })
|
|
77
|
+
expect(result).toContain("position: fixed;")
|
|
78
|
+
expect(result).toContain("top: 0;")
|
|
79
|
+
expect(result).toContain("left: 0;")
|
|
80
|
+
expect(result).toContain("right: 0;")
|
|
81
|
+
expect(result).toContain("bottom: 0;")
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("special property: fullScreen false produces no output", () => {
|
|
85
|
+
const result = styles({ theme: { fullScreen: false }, css: mockCss, rootSize: 16 })
|
|
86
|
+
expect(result).not.toContain("position: fixed;")
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("special property: backgroundImage", () => {
|
|
90
|
+
const result = styles({
|
|
91
|
+
theme: { backgroundImage: "https://example.com/img.png" },
|
|
92
|
+
css: mockCss,
|
|
93
|
+
rootSize: 16,
|
|
94
|
+
})
|
|
95
|
+
expect(result).toContain("background-image: url(https://example.com/img.png);")
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it("special property: hideEmpty", () => {
|
|
99
|
+
const result = styles({ theme: { hideEmpty: true }, css: mockCss, rootSize: 16 })
|
|
100
|
+
expect(result).toContain("&:empty { display: none; }")
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it("special property: clearFix", () => {
|
|
104
|
+
const result = styles({ theme: { clearFix: true }, css: mockCss, rootSize: 16 })
|
|
105
|
+
expect(result).toContain('&::after { clear: both; content: ""; display: table; }')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it("string values for convert properties pass through", () => {
|
|
109
|
+
const result = styles({ theme: { width: "50%" }, css: mockCss, rootSize: 16 })
|
|
110
|
+
expect(result).toContain("width: 50%;")
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it("uses default rootSize when not provided", () => {
|
|
114
|
+
// default rootSize is undefined, value() defaults to 16
|
|
115
|
+
const result = styles({ theme: { fontSize: 32 }, css: mockCss })
|
|
116
|
+
expect(result).toContain("font-size:")
|
|
117
|
+
expect(result).toContain("2rem")
|
|
118
|
+
})
|
|
119
|
+
})
|