Skip to content

Commit cade39c

Browse files
authored
Add Generics and options arg (#14)
* Add tests for utils * Add stringify generic functions * Fix type warn in test * Refactor file structure, move main functions to root dir, remove old files * Update JSDoc * Refactor helper functions
1 parent a6fb74a commit cade39c

21 files changed

+634
-113
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { describe, expect, it } from "vitest";
2+
import { applyCssUnits } from "../helpers";
3+
import { type CSSUnitMap } from "../types";
4+
5+
describe("applyCssUnits", () => {
6+
it("returns string values as-is", () => {
7+
expect(applyCssUnits("fontSize", "2em")).toBe("2em");
8+
expect(applyCssUnits("color", "red")).toBe("red");
9+
});
10+
11+
it('returns "0" as-is (without unit)', () => {
12+
expect(applyCssUnits("marginTop", 0)).toBe("0");
13+
});
14+
15+
it("applies default px unit to numeric values", () => {
16+
expect(applyCssUnits("marginTop", 10)).toBe("10px");
17+
});
18+
19+
it("uses custom unit string when provided", () => {
20+
expect(applyCssUnits("fontSize", 1.5, "em")).toBe("1.5em");
21+
});
22+
23+
it("uses unit map when provided", () => {
24+
const unitMap: CSSUnitMap = {
25+
fontSize: "rem",
26+
marginTop: "%",
27+
};
28+
expect(applyCssUnits("fontSize", 2, unitMap)).toBe("2rem");
29+
expect(applyCssUnits("marginTop", 5, unitMap)).toBe("5%");
30+
});
31+
32+
it("falls back to default px if unit not found in map", () => {
33+
expect(applyCssUnits("paddingLeft", 8, {})).toBe("8px");
34+
});
35+
36+
it("omits unit for known unitless properties", () => {
37+
// @emotion/unitless is used to define unitless properties
38+
expect(applyCssUnits("lineHeight", 1.2)).toBe("1.2");
39+
expect(applyCssUnits("zIndex", 2)).toBe("2");
40+
expect(applyCssUnits("flex", 1)).toBe("1");
41+
});
42+
43+
it("throws if value is not string or number", () => {
44+
// @ts-expect-error - testing invalid input
45+
expect(() => applyCssUnits("fontSize", null)).toThrowError();
46+
// @ts-expect-error - testing invalid input
47+
expect(() => applyCssUnits("fontSize", {})).toThrowError();
48+
});
49+
});

src/__tests__/camelToKebab.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, expect, it } from "vitest";
2+
import { camelToKebab } from "../helpers";
3+
4+
describe("camelToKebab", () => {
5+
it("converts camelCase to kebab-case", () => {
6+
expect(camelToKebab("backgroundColor")).toBe("background-color");
7+
expect(camelToKebab("fontSize")).toBe("font-size");
8+
expect(camelToKebab("borderTopLeftRadius")).toBe("border-top-left-radius");
9+
expect(camelToKebab("WebkitBorderBeforeWidth")).toBe(
10+
"-webkit-border-before-width"
11+
);
12+
});
13+
14+
it("returns the same string if there are no uppercase letters", () => {
15+
expect(camelToKebab("color")).toBe("color");
16+
expect(camelToKebab("display")).toBe("display");
17+
});
18+
19+
it("handles empty string", () => {
20+
expect(camelToKebab("")).toBe("");
21+
});
22+
23+
it("handles single uppercase character", () => {
24+
expect(camelToKebab("A")).toBe("-a");
25+
});
26+
});

src/__tests__/stringifyCSSProperties.test.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect } from "vitest";
22

3-
import { stringifyCSSProperties } from "../stringifyCSSProperties";
3+
import { stringifyCSSProperties } from "../stringify-react-styles";
44

55
describe("stringifyCSSProperties", () => {
66
it("returns string", () => {
@@ -14,14 +14,14 @@ describe("stringifyCSSProperties", () => {
1414
it("throws error for string input", () => {
1515
//@ts-ignore
1616
expect(() => stringifyCSSProperties("")).toThrowError(
17-
"Invalid input: 'cssProperties' must be an object."
17+
"[stringifyCSSProperties]: Expected 'cssProperties' to be a non-null object, but received (type:string)."
1818
);
1919
});
2020

2121
it("throws error for 'null' input", () => {
2222
//@ts-ignore
2323
expect(() => stringifyCSSProperties(null)).toThrowError(
24-
"Invalid input: 'cssProperties' must be an object."
24+
"[stringifyCSSProperties]: Expected 'cssProperties' to be a non-null object, but received null (type:object)."
2525
);
2626
});
2727

@@ -99,3 +99,47 @@ describe("stringifyCSSProperties", () => {
9999
expect(actual).toBe(expected);
100100
});
101101
});
102+
103+
describe("stringifyStyleMap accepts 'options' object", () => {
104+
it("applies !important when the flag is set", () => {
105+
expect(
106+
stringifyCSSProperties(
107+
{
108+
display: "flex",
109+
top: 100,
110+
},
111+
{
112+
important: true,
113+
}
114+
)
115+
).toBe("display:flex!important;top:100px!important;");
116+
});
117+
118+
it("uses a global unit string when specified", () => {
119+
expect(
120+
stringifyCSSProperties(
121+
{
122+
top: 100,
123+
},
124+
{
125+
unit: "rem",
126+
}
127+
)
128+
).toBe("top:100rem;");
129+
});
130+
131+
it("uses per-property unit map when provided (with 'px' fallback)", () => {
132+
expect(
133+
stringifyCSSProperties(
134+
{
135+
paddingBlock: 20,
136+
paddingInline: 30,
137+
top: 100,
138+
},
139+
{
140+
unit: { paddingBlock: "vh", paddingInline: "vw" },
141+
}
142+
)
143+
).toBe("padding-block:20vh;padding-inline:30vw;top:100px;");
144+
});
145+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { describe, it, expect } from "vitest";
2+
3+
import { stringifyStyleDeclaration } from "../stringifyStyleDeclaration";
4+
5+
describe("stringifyStyleDeclaration", () => {
6+
it("converts a basic style declaration to a CSS string", () => {
7+
expect(
8+
stringifyStyleDeclaration({
9+
display: "flex",
10+
fontSize: 16,
11+
})
12+
).toBe("display:flex;font-size:16px;");
13+
});
14+
15+
it("applies !important when the flag is set", () => {
16+
expect(
17+
stringifyStyleDeclaration(
18+
{ color: "red", marginTop: 8 },
19+
{ important: true }
20+
)
21+
).toBe("color:red!important;margin-top:8px!important;");
22+
});
23+
24+
it("uses a global unit string when specified", () => {
25+
expect(stringifyStyleDeclaration({ padding: 10 }, { unit: "rem" })).toBe(
26+
"padding:10rem;"
27+
);
28+
});
29+
30+
it("uses per-property unit map when provided (with 'px' fallback)", () => {
31+
expect(
32+
stringifyStyleDeclaration(
33+
{ fontSize: 2, marginLeft: 5, marginBottom: 10 },
34+
{ unit: { fontSize: "em", marginLeft: "%" } }
35+
)
36+
).toBe("font-size:2em;margin-left:5%;margin-bottom:10px;");
37+
});
38+
39+
it("uses px as default unit if none is specified", () => {
40+
expect(stringifyStyleDeclaration({ top: 100 })).toBe("top:100px;");
41+
});
42+
43+
it("omits units for unitless properties", () => {
44+
expect(stringifyStyleDeclaration({ lineHeight: 1.5 })).toBe(
45+
"line-height:1.5;"
46+
);
47+
});
48+
49+
it("filters out invalid property values (e.g., null, undefined)", () => {
50+
expect(
51+
stringifyStyleDeclaration({
52+
margin: { top: 10 },
53+
fontSize: 14,
54+
color: undefined,
55+
background: null,
56+
})
57+
).toBe("font-size:14px;");
58+
});
59+
60+
it("throws an error if styleDeclaration is not an object", () => {
61+
// @ts-expect-error - invalid input
62+
expect(() => stringifyStyleDeclaration(null)).toThrowError();
63+
// @ts-expect-error - invalid input
64+
expect(() => stringifyStyleDeclaration(123)).toThrowError();
65+
});
66+
67+
it("returns an empty string for an empty object", () => {
68+
expect(stringifyStyleDeclaration({})).toBe("");
69+
});
70+
});

src/__tests__/stringifyStyleMap.test.ts

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from "vitest";
22

3-
import { stringifyStyleMap } from "../stringifyStyleMap";
3+
import { stringifyStyleMap } from "../stringify-react-styles";
44

55
describe("stringifyStyleMap", () => {
66
const cssProperties = { color: "teal" };
@@ -16,14 +16,14 @@ describe("stringifyStyleMap", () => {
1616
it("throws error for string input", () => {
1717
//@ts-ignore
1818
expect(() => stringifyStyleMap("")).toThrowError(
19-
"Invalid input: 'styleMap' must be an object."
19+
"[stringifyStyleMap]: Expected 'styleMap' to be a non-null object, but received (type:string)."
2020
);
2121
});
2222

2323
it("throws error for 'null' input", () => {
2424
//@ts-ignore
2525
expect(() => stringifyStyleMap(null)).toThrowError(
26-
"Invalid input: 'styleMap' must be an object."
26+
"[stringifyStyleMap]: Expected 'styleMap' to be a non-null object, but received null (type:object)."
2727
);
2828
});
2929

@@ -78,3 +78,65 @@ describe("stringifyStyleMap", () => {
7878
expect(actual).toBe(expected);
7979
});
8080
});
81+
82+
describe("stringifyStyleMap accepts 'options' object", () => {
83+
it("applies !important when the flag is set", () => {
84+
expect(
85+
stringifyStyleMap(
86+
{
87+
".class-1": {
88+
display: "flex",
89+
},
90+
".class-2": {
91+
display: "flex",
92+
},
93+
},
94+
{
95+
important: true,
96+
}
97+
)
98+
).toBe(
99+
".class-1{display:flex!important;}.class-2{display:flex!important;}"
100+
);
101+
});
102+
103+
it("uses a global unit string when specified", () => {
104+
expect(
105+
stringifyStyleMap(
106+
{
107+
".class-1": {
108+
margin: 10,
109+
},
110+
".class-2": {
111+
padding: 20,
112+
},
113+
},
114+
{
115+
unit: "rem",
116+
}
117+
)
118+
).toBe(".class-1{margin:10rem;}.class-2{padding:20rem;}");
119+
});
120+
121+
it("uses per-property unit map when provided (with 'px' fallback)", () => {
122+
expect(
123+
stringifyStyleMap(
124+
{
125+
".class-1": {
126+
marginBlock: 30,
127+
},
128+
".class-2": {
129+
top: 100,
130+
paddingBlock: 20,
131+
paddingInline: 10,
132+
},
133+
},
134+
{
135+
unit: { paddingBlock: "vh", paddingInline: "vw" },
136+
}
137+
)
138+
).toBe(
139+
".class-1{margin-block:30px;}.class-2{top:100px;padding-block:20vh;padding-inline:10vw;}"
140+
);
141+
});
142+
});

0 commit comments

Comments
 (0)