@pyreon/unistyle 0.11.1 → 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.
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/unistyle",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/pyreon/pyreon",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"!lib/**/*.map",
|
|
25
25
|
"!lib/analysis",
|
|
26
26
|
"README.md",
|
|
27
|
-
"LICENSE"
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"src"
|
|
28
29
|
],
|
|
29
30
|
"engines": {
|
|
30
31
|
"node": ">= 22"
|
|
@@ -43,13 +44,13 @@
|
|
|
43
44
|
"typecheck": "tsc --noEmit"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
|
-
"@pyreon/core": "^0.11.
|
|
47
|
-
"@pyreon/reactivity": "^0.11.
|
|
48
|
-
"@pyreon/ui-core": "^0.11.
|
|
49
|
-
"@pyreon/styler": "^0.11.
|
|
47
|
+
"@pyreon/core": "^0.11.2",
|
|
48
|
+
"@pyreon/reactivity": "^0.11.2",
|
|
49
|
+
"@pyreon/ui-core": "^0.11.2",
|
|
50
|
+
"@pyreon/styler": "^0.11.2"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@vitus-labs/tools-rolldown": "^1.15.3",
|
|
53
|
-
"@pyreon/typescript": "^0.11.
|
|
54
|
+
"@pyreon/typescript": "^0.11.2"
|
|
54
55
|
}
|
|
55
56
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import alignContent from "../styles/alignContent"
|
|
3
|
+
|
|
4
|
+
describe("alignContent", () => {
|
|
5
|
+
it("returns null for empty attrs", () => {
|
|
6
|
+
expect(alignContent({} as any)).toBeNull()
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it("returns null when direction is missing", () => {
|
|
10
|
+
expect(alignContent({ alignX: "left", alignY: "top" } as any)).toBeNull()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it("returns null when alignX is missing", () => {
|
|
14
|
+
expect(alignContent({ direction: "inline", alignY: "top" } as any)).toBeNull()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it("returns null when alignY is missing", () => {
|
|
18
|
+
expect(alignContent({ direction: "inline", alignX: "left" } as any)).toBeNull()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe("inline direction (reverted)", () => {
|
|
22
|
+
it("returns row with align-items from Y and justify-content from X", () => {
|
|
23
|
+
const result = alignContent({ direction: "inline", alignX: "left", alignY: "top" })
|
|
24
|
+
expect(result).toBe(
|
|
25
|
+
"flex-direction: row; align-items: flex-start; justify-content: flex-start;",
|
|
26
|
+
)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("maps right/bottom correctly", () => {
|
|
30
|
+
const result = alignContent({ direction: "inline", alignX: "right", alignY: "bottom" })
|
|
31
|
+
expect(result).toBe("flex-direction: row; align-items: flex-end; justify-content: flex-end;")
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("maps center/center", () => {
|
|
35
|
+
const result = alignContent({ direction: "inline", alignX: "center", alignY: "center" })
|
|
36
|
+
expect(result).toBe("flex-direction: row; align-items: center; justify-content: center;")
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("maps spaceBetween/spaceAround", () => {
|
|
40
|
+
const result = alignContent({
|
|
41
|
+
direction: "inline",
|
|
42
|
+
alignX: "spaceBetween",
|
|
43
|
+
alignY: "spaceAround",
|
|
44
|
+
})
|
|
45
|
+
expect(result).toBe(
|
|
46
|
+
"flex-direction: row; align-items: space-around; justify-content: space-between;",
|
|
47
|
+
)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it("maps block/block", () => {
|
|
51
|
+
const result = alignContent({ direction: "inline", alignX: "block", alignY: "block" })
|
|
52
|
+
expect(result).toBe("flex-direction: row; align-items: stretch; justify-content: stretch;")
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe("reverseInline direction (reverted)", () => {
|
|
57
|
+
it("returns row-reverse with align-items from Y and justify-content from X", () => {
|
|
58
|
+
const result = alignContent({ direction: "reverseInline", alignX: "left", alignY: "top" })
|
|
59
|
+
expect(result).toBe(
|
|
60
|
+
"flex-direction: row-reverse; align-items: flex-start; justify-content: flex-start;",
|
|
61
|
+
)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it("maps center/bottom", () => {
|
|
65
|
+
const result = alignContent({
|
|
66
|
+
direction: "reverseInline",
|
|
67
|
+
alignX: "center",
|
|
68
|
+
alignY: "bottom",
|
|
69
|
+
})
|
|
70
|
+
expect(result).toBe(
|
|
71
|
+
"flex-direction: row-reverse; align-items: flex-end; justify-content: center;",
|
|
72
|
+
)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe("rows direction (non-reverted)", () => {
|
|
77
|
+
it("returns column with align-items from X and justify-content from Y", () => {
|
|
78
|
+
const result = alignContent({ direction: "rows", alignX: "left", alignY: "top" })
|
|
79
|
+
expect(result).toBe(
|
|
80
|
+
"flex-direction: column; align-items: flex-start; justify-content: flex-start;",
|
|
81
|
+
)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("maps right/bottom correctly", () => {
|
|
85
|
+
const result = alignContent({ direction: "rows", alignX: "right", alignY: "bottom" })
|
|
86
|
+
expect(result).toBe(
|
|
87
|
+
"flex-direction: column; align-items: flex-end; justify-content: flex-end;",
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("maps center/spaceBetween", () => {
|
|
92
|
+
const result = alignContent({ direction: "rows", alignX: "center", alignY: "spaceBetween" })
|
|
93
|
+
expect(result).toBe(
|
|
94
|
+
"flex-direction: column; align-items: center; justify-content: space-between;",
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it("maps spaceAround/block", () => {
|
|
99
|
+
const result = alignContent({ direction: "rows", alignX: "spaceAround", alignY: "block" })
|
|
100
|
+
expect(result).toBe(
|
|
101
|
+
"flex-direction: column; align-items: space-around; justify-content: stretch;",
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe("reverseRows direction (non-reverted)", () => {
|
|
107
|
+
it("returns column-reverse with align-items from X and justify-content from Y", () => {
|
|
108
|
+
const result = alignContent({ direction: "reverseRows", alignX: "left", alignY: "top" })
|
|
109
|
+
expect(result).toBe(
|
|
110
|
+
"flex-direction: column-reverse; align-items: flex-start; justify-content: flex-start;",
|
|
111
|
+
)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it("maps block/center", () => {
|
|
115
|
+
const result = alignContent({ direction: "reverseRows", alignX: "block", alignY: "center" })
|
|
116
|
+
expect(result).toBe(
|
|
117
|
+
"flex-direction: column-reverse; align-items: stretch; justify-content: center;",
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
})
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import borderRadius from "../styles/shorthands/borderRadius"
|
|
3
|
+
|
|
4
|
+
const empty = {
|
|
5
|
+
full: undefined,
|
|
6
|
+
top: undefined,
|
|
7
|
+
bottom: undefined,
|
|
8
|
+
left: undefined,
|
|
9
|
+
right: undefined,
|
|
10
|
+
topLeft: undefined,
|
|
11
|
+
topRight: undefined,
|
|
12
|
+
bottomLeft: undefined,
|
|
13
|
+
bottomRight: undefined,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const br = borderRadius()
|
|
17
|
+
|
|
18
|
+
describe("borderRadius", () => {
|
|
19
|
+
it("returns null when no values provided", () => {
|
|
20
|
+
expect(br(empty)).toBeNull()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe("full shorthand", () => {
|
|
24
|
+
it("all same value produces single-value shorthand", () => {
|
|
25
|
+
expect(br({ ...empty, full: 16 })).toBe("border-radius: 1rem;")
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("zero is a valid value", () => {
|
|
29
|
+
expect(br({ ...empty, full: 0 })).toBe("border-radius: 0;")
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("string values pass through", () => {
|
|
33
|
+
expect(br({ ...empty, full: "50%" })).toBe("border-radius: 50%;")
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe("two-value shorthand", () => {
|
|
38
|
+
it("tl===br and tr===bl produces two-value shorthand", () => {
|
|
39
|
+
const result = br({
|
|
40
|
+
...empty,
|
|
41
|
+
topLeft: 16,
|
|
42
|
+
topRight: 32,
|
|
43
|
+
bottomRight: 16,
|
|
44
|
+
bottomLeft: 32,
|
|
45
|
+
})
|
|
46
|
+
expect(result).toBe("border-radius: 1rem 2rem;")
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe("three-value shorthand", () => {
|
|
51
|
+
it("tl, tr===bl, br produces three-value shorthand", () => {
|
|
52
|
+
const result = br({
|
|
53
|
+
...empty,
|
|
54
|
+
topLeft: 16,
|
|
55
|
+
topRight: 32,
|
|
56
|
+
bottomRight: 48,
|
|
57
|
+
bottomLeft: 32,
|
|
58
|
+
})
|
|
59
|
+
expect(result).toBe("border-radius: 1rem 2rem 3rem;")
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe("four-value shorthand", () => {
|
|
64
|
+
it("all different produces four-value shorthand", () => {
|
|
65
|
+
const result = br({
|
|
66
|
+
...empty,
|
|
67
|
+
topLeft: 16,
|
|
68
|
+
topRight: 32,
|
|
69
|
+
bottomRight: 48,
|
|
70
|
+
bottomLeft: 64,
|
|
71
|
+
})
|
|
72
|
+
expect(result).toBe("border-radius: 1rem 2rem 3rem 4rem;")
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe("per-side values", () => {
|
|
77
|
+
it("top sets topLeft and topRight", () => {
|
|
78
|
+
const result = br({ ...empty, top: 16, bottom: 32 })
|
|
79
|
+
expect(result).toBe("border-radius: 1rem 1rem 2rem 2rem;")
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it("left sets topLeft and bottomLeft", () => {
|
|
83
|
+
const result = br({ ...empty, left: 16, right: 32 })
|
|
84
|
+
expect(result).toBe("border-radius: 1rem 2rem 2rem 1rem;")
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe("individual corners override sides", () => {
|
|
89
|
+
it("topLeft overrides top and left", () => {
|
|
90
|
+
const result = br({ ...empty, top: 16, topLeft: 32, bottom: 16 })
|
|
91
|
+
// tl=32, tr=16, br=16, bl=16 → tr===bl so 3-value shorthand
|
|
92
|
+
expect(result).toBe("border-radius: 2rem 1rem 1rem;")
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("bottomRight overrides bottom and right", () => {
|
|
96
|
+
const result = br({ ...empty, full: 16, bottomRight: 32 })
|
|
97
|
+
// tl=16, tr=16, br=32, bl=16 → tr===bl so 3-value shorthand
|
|
98
|
+
expect(result).toBe("border-radius: 1rem 1rem 2rem;")
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe("individual properties when not all corners have values", () => {
|
|
103
|
+
it("only topLeft is set", () => {
|
|
104
|
+
const result = br({ ...empty, topLeft: 16 })
|
|
105
|
+
expect(result).toBe("border-top-left-radius: 1rem;")
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it("topLeft and bottomRight are set", () => {
|
|
109
|
+
const result = br({ ...empty, topLeft: 16, bottomRight: 32 })
|
|
110
|
+
expect(result).toBe("border-top-left-radius: 1rem;border-bottom-right-radius: 2rem;")
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it("top only sets topLeft and topRight individual properties", () => {
|
|
114
|
+
const result = br({ ...empty, top: 16 })
|
|
115
|
+
expect(result).toBe("border-top-left-radius: 1rem;border-top-right-radius: 1rem;")
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe("custom rootSize", () => {
|
|
120
|
+
it("uses custom rootSize for conversion", () => {
|
|
121
|
+
const brCustom = borderRadius(10)
|
|
122
|
+
expect(brCustom({ ...empty, full: 20 })).toBe("border-radius: 2rem;")
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import camelToKebab from "../styles/styles/camelToKebab"
|
|
3
|
+
|
|
4
|
+
describe("camelToKebab", () => {
|
|
5
|
+
it("converts camelCase to kebab-case", () => {
|
|
6
|
+
expect(camelToKebab("backgroundColor")).toBe("background-color")
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it("converts multiple uppercase letters", () => {
|
|
10
|
+
expect(camelToKebab("borderTopLeftRadius")).toBe("border-top-left-radius")
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it("returns lowercase strings unchanged", () => {
|
|
14
|
+
expect(camelToKebab("color")).toBe("color")
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it("returns empty string for empty input", () => {
|
|
18
|
+
expect(camelToKebab("")).toBe("")
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it("handles single uppercase letter", () => {
|
|
22
|
+
expect(camelToKebab("A")).toBe("-a")
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it("handles leading lowercase", () => {
|
|
26
|
+
expect(camelToKebab("fontSize")).toBe("font-size")
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("handles consecutive uppercase letters individually", () => {
|
|
30
|
+
expect(camelToKebab("msOverflowStyle")).toBe("ms-overflow-style")
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("handles boxSizing", () => {
|
|
34
|
+
expect(camelToKebab("boxSizing")).toBe("box-sizing")
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it("handles flexDirection", () => {
|
|
38
|
+
expect(camelToKebab("flexDirection")).toBe("flex-direction")
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("handles justifyContent", () => {
|
|
42
|
+
expect(camelToKebab("justifyContent")).toBe("justify-content")
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
vi.mock("@pyreon/ui-core", () => {
|
|
4
|
+
const mockContext = { id: Symbol("test-context") }
|
|
5
|
+
return {
|
|
6
|
+
Provider: vi.fn((props: any) => props.children ?? null),
|
|
7
|
+
config: {
|
|
8
|
+
css: (strings: TemplateStringsArray, ...vals: any[]) => {
|
|
9
|
+
let r = ""
|
|
10
|
+
for (let i = 0; i < strings.length; i++) {
|
|
11
|
+
r += strings[i]
|
|
12
|
+
if (i < vals.length) r += String(vals[i])
|
|
13
|
+
}
|
|
14
|
+
return r
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
context: mockContext,
|
|
18
|
+
isEmpty: (val: unknown) =>
|
|
19
|
+
val == null || (typeof val === "object" && Object.keys(val as object).length === 0),
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
import { Provider as CoreProvider } from "@pyreon/ui-core"
|
|
24
|
+
import Provider, { context } from "../context"
|
|
25
|
+
|
|
26
|
+
const mockCoreProvider = CoreProvider as ReturnType<typeof vi.fn>
|
|
27
|
+
|
|
28
|
+
/** Extract the first argument from the first mock call. */
|
|
29
|
+
const firstCallArg = () => {
|
|
30
|
+
const calls = mockCoreProvider.mock.calls
|
|
31
|
+
expect(calls.length).toBeGreaterThan(0)
|
|
32
|
+
return (calls[0] as any[])[0] as any
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("Provider", () => {
|
|
36
|
+
it("exports context from @pyreon/ui-core", () => {
|
|
37
|
+
expect(context).toBeDefined()
|
|
38
|
+
expect(context).toHaveProperty("id")
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("calls CoreProvider with enriched theme", () => {
|
|
42
|
+
mockCoreProvider.mockClear()
|
|
43
|
+
|
|
44
|
+
Provider({
|
|
45
|
+
theme: { rootSize: 16 },
|
|
46
|
+
children: null,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
expect(mockCoreProvider).toHaveBeenCalledTimes(1)
|
|
50
|
+
const calledWith = firstCallArg()
|
|
51
|
+
expect(calledWith.theme).toHaveProperty("__PYREON__")
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("when theme has no breakpoints: __PYREON__ has undefined sortedBreakpoints and media", () => {
|
|
55
|
+
mockCoreProvider.mockClear()
|
|
56
|
+
|
|
57
|
+
Provider({
|
|
58
|
+
theme: { rootSize: 16 },
|
|
59
|
+
children: null,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const enrichedTheme = firstCallArg().theme
|
|
63
|
+
expect(enrichedTheme.__PYREON__.sortedBreakpoints).toBeUndefined()
|
|
64
|
+
expect(enrichedTheme.__PYREON__.media).toBeUndefined()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("when theme has empty breakpoints: __PYREON__ has undefined sortedBreakpoints and media", () => {
|
|
68
|
+
mockCoreProvider.mockClear()
|
|
69
|
+
|
|
70
|
+
Provider({
|
|
71
|
+
theme: { rootSize: 16, breakpoints: {} },
|
|
72
|
+
children: null,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const enrichedTheme = firstCallArg().theme
|
|
76
|
+
expect(enrichedTheme.__PYREON__.sortedBreakpoints).toBeUndefined()
|
|
77
|
+
expect(enrichedTheme.__PYREON__.media).toBeUndefined()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it("when theme has breakpoints: __PYREON__ has sortedBreakpoints and media objects", () => {
|
|
81
|
+
mockCoreProvider.mockClear()
|
|
82
|
+
|
|
83
|
+
Provider({
|
|
84
|
+
theme: {
|
|
85
|
+
rootSize: 16,
|
|
86
|
+
breakpoints: { xs: 0, sm: 576, md: 768 },
|
|
87
|
+
},
|
|
88
|
+
children: null,
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const enrichedTheme = firstCallArg().theme
|
|
92
|
+
expect(enrichedTheme.__PYREON__.sortedBreakpoints).toEqual(["xs", "sm", "md"])
|
|
93
|
+
expect(enrichedTheme.__PYREON__.media).toBeDefined()
|
|
94
|
+
expect(Object.keys(enrichedTheme.__PYREON__.media)).toEqual(["xs", "sm", "md"])
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it("passes children through to CoreProvider", () => {
|
|
98
|
+
mockCoreProvider.mockClear()
|
|
99
|
+
|
|
100
|
+
const mockChild = { type: "div" } as any
|
|
101
|
+
Provider({
|
|
102
|
+
theme: { rootSize: 16 },
|
|
103
|
+
children: mockChild,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const calledWith = firstCallArg()
|
|
107
|
+
expect(calledWith.children).toBe(mockChild)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it("preserves other theme properties in enriched theme", () => {
|
|
111
|
+
mockCoreProvider.mockClear()
|
|
112
|
+
|
|
113
|
+
Provider({
|
|
114
|
+
theme: { rootSize: 16, customProp: "hello" } as any,
|
|
115
|
+
children: null,
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const enrichedTheme = firstCallArg().theme
|
|
119
|
+
expect(enrichedTheme.rootSize).toBe(16)
|
|
120
|
+
expect(enrichedTheme.customProp).toBe("hello")
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it("media functions are callable tagged template functions", () => {
|
|
124
|
+
mockCoreProvider.mockClear()
|
|
125
|
+
|
|
126
|
+
Provider({
|
|
127
|
+
theme: {
|
|
128
|
+
rootSize: 16,
|
|
129
|
+
breakpoints: { xs: 0, sm: 576 },
|
|
130
|
+
},
|
|
131
|
+
children: null,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const media = firstCallArg().theme.__PYREON__.media
|
|
135
|
+
expect(typeof media.xs).toBe("function")
|
|
136
|
+
expect(typeof media.sm).toBe("function")
|
|
137
|
+
|
|
138
|
+
// xs (value 0) should pass through
|
|
139
|
+
const xsResult = media.xs`color: red;`
|
|
140
|
+
expect(xsResult).toContain("color: red;")
|
|
141
|
+
expect(xsResult).not.toContain("@media")
|
|
142
|
+
|
|
143
|
+
// sm (value 576) should wrap in media query
|
|
144
|
+
const smResult = media.sm`color: blue;`
|
|
145
|
+
expect(smResult).toContain("@media only screen and (min-width: 36em)")
|
|
146
|
+
})
|
|
147
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import createMediaQueries from "../responsive/createMediaQueries"
|
|
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("createMediaQueries", () => {
|
|
14
|
+
it("returns an object with keys matching breakpoint names", () => {
|
|
15
|
+
const result = createMediaQueries({
|
|
16
|
+
breakpoints: { xs: 0, sm: 576, md: 768 },
|
|
17
|
+
rootSize: 16,
|
|
18
|
+
css: mockCss,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
expect(Object.keys(result)).toEqual(["xs", "sm", "md"])
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it("multiple breakpoints produce correct number of keys", () => {
|
|
25
|
+
const result = createMediaQueries({
|
|
26
|
+
breakpoints: { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200 },
|
|
27
|
+
rootSize: 16,
|
|
28
|
+
css: mockCss,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
expect(Object.keys(result)).toHaveLength(5)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("for breakpoint with value 0: passes through to css directly (no @media wrapper)", () => {
|
|
35
|
+
const result = createMediaQueries({
|
|
36
|
+
breakpoints: { xs: 0 },
|
|
37
|
+
rootSize: 16,
|
|
38
|
+
css: mockCss,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const output = result.xs`color: red;`
|
|
42
|
+
expect(output).toBe("color: red;")
|
|
43
|
+
expect(output).not.toContain("@media")
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("for breakpoint with non-zero value: wraps in @media with em calculation", () => {
|
|
47
|
+
const result = createMediaQueries({
|
|
48
|
+
breakpoints: { sm: 576 },
|
|
49
|
+
rootSize: 16,
|
|
50
|
+
css: mockCss,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const output = result.sm`color: blue;`
|
|
54
|
+
// 576 / 16 = 36
|
|
55
|
+
expect(output).toContain("@media only screen and (min-width: 36em)")
|
|
56
|
+
expect(output).toContain("color: blue;")
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("calculates em size as breakpointValue / rootSize", () => {
|
|
60
|
+
const result = createMediaQueries({
|
|
61
|
+
breakpoints: { md: 768 },
|
|
62
|
+
rootSize: 16,
|
|
63
|
+
css: mockCss,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const output = result.md`font-size: 1rem;`
|
|
67
|
+
// 768 / 16 = 48
|
|
68
|
+
expect(output).toContain("48em")
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("respects custom rootSize", () => {
|
|
72
|
+
const result = createMediaQueries({
|
|
73
|
+
breakpoints: { lg: 992 },
|
|
74
|
+
rootSize: 10,
|
|
75
|
+
css: mockCss,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const output = result.lg`display: flex;`
|
|
79
|
+
// 992 / 10 = 99.2
|
|
80
|
+
expect(output).toContain("99.2em")
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it("handles mixed zero and non-zero breakpoints", () => {
|
|
84
|
+
const result = createMediaQueries({
|
|
85
|
+
breakpoints: { xs: 0, sm: 576, md: 768 },
|
|
86
|
+
rootSize: 16,
|
|
87
|
+
css: mockCss,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const xsOutput = result.xs`color: red;`
|
|
91
|
+
const smOutput = result.sm`color: blue;`
|
|
92
|
+
const mdOutput = result.md`color: green;`
|
|
93
|
+
|
|
94
|
+
expect(xsOutput).not.toContain("@media")
|
|
95
|
+
expect(smOutput).toContain("@media only screen and (min-width: 36em)")
|
|
96
|
+
expect(mdOutput).toContain("@media only screen and (min-width: 48em)")
|
|
97
|
+
})
|
|
98
|
+
})
|