Skip to content

Commit 73d56bf

Browse files
author
John Richard Chipps-Harding
authored
Add Unit Tests (#6)
* Add Unit Tests * minor * exotic test * More
1 parent 714732c commit 73d56bf

File tree

8 files changed

+13742
-5050
lines changed

8 files changed

+13742
-5050
lines changed

.github/workflows/pr.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ jobs:
2626
npm ci
2727
npm run build --if-present
2828
npm run lint
29+
npm run test
2930
env:
3031
CI: true

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ jobs:
2525
npm ci
2626
npm run build --if-present
2727
npm run lint
28+
npm run test
2829
env:
2930
CI: true
3031

jest.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* @type {jest.ProjectConfig}
3+
*/
4+
module.exports = {
5+
roots: ["<rootDir>/test"],
6+
transform: {
7+
"^.+\\.tsx?$": "ts-jest",
8+
},
9+
setupFilesAfterEnv: ["@testing-library/jest-dom/extend-expect"],
10+
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
11+
};

package-lock.json

Lines changed: 13516 additions & 5038 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@phntms/css-components",
33
"description": "At its core, css-components is a simple wrapper around standard CSS. It allows you to write your CSS how you wish then compose them into a component ready to be used in React.",
4-
"version": "0.0.2",
4+
"version": "0.0.3",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",
77
"homepage": "https://github.com/phantomstudios/css-components#readme",
@@ -22,6 +22,9 @@
2222
"build": "tsc",
2323
"build:types": "tsc --emitDeclarationOnly",
2424
"prepublishOnly": "npm run build",
25+
"test": "jest --verbose",
26+
"test:watch": "jest --verbose --watch",
27+
"coverage": "jest --coverage",
2528
"lint": "NODE_ENV=test npm-run-all --parallel lint:*",
2629
"lint:js": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
2730
"lint:format": "prettier \"**/*.{md,html,yaml,yml}\" --check",
@@ -39,6 +42,9 @@
3942
"devDependencies": {
4043
"@babel/preset-env": "^7.20.2",
4144
"@babel/preset-typescript": "^7.18.6",
45+
"@testing-library/jest-dom": "^5.16.5",
46+
"@testing-library/react": "^13.4.0",
47+
"@types/jest": "^29.2.2",
4248
"@types/react": "^18.0.25",
4349
"@typescript-eslint/eslint-plugin": "^5.42.1",
4450
"@typescript-eslint/parser": "^5.42.1",
@@ -49,10 +55,13 @@
4955
"eslint-plugin-prettier": "^4.2.1",
5056
"eslint-plugin-react": "^7.31.10",
5157
"eslint-plugin-react-hooks": "^4.6.0",
58+
"jest": "^29.3.1",
59+
"jest-environment-jsdom": "^29.3.1",
5260
"npm-run-all": "^4.1.5",
5361
"prettier": "^2.7.1",
5462
"react": "^18.2.0",
5563
"react-dom": "^18.2.0",
64+
"ts-jest": "^29.0.3",
5665
"typescript": "^4.8.4"
5766
}
5867
}

src/index.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { createElement, forwardRef } from "react";
22

3-
type variantValue = string | number | boolean;
3+
type variantValue = string | number | boolean | string[];
44

55
// An object of variants, and how they map to CSS styles
6-
type variantsType = {
6+
type variantsType = Partial<{
77
[key: string]: { [key: string | number]: string | string[] };
8-
};
8+
}>;
99

1010
type compoundVariantType = {
1111
[key: string]: variantValue;
@@ -37,9 +37,12 @@ export type PropsOf<
3737
C extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>
3838
> = JSX.LibraryManagedAttributes<C, React.ComponentPropsWithoutRef<C>>;
3939

40-
export const styled = <V extends variantsType, E extends React.ElementType>(
40+
export const styled = <
41+
V extends variantsType | object,
42+
E extends React.ElementType
43+
>(
4144
element: E,
42-
baseClassName: string | string[],
45+
baseClassName?: string | string[],
4346
variants?: V,
4447
compoundVariants?: compoundVariantType[]
4548
) => {
@@ -56,15 +59,19 @@ export const styled = <V extends variantsType, E extends React.ElementType>(
5659
if (ref) componentProps.ref = ref;
5760

5861
// Add the base style(s)
59-
componentStyles.push(
60-
Array.isArray(baseClassName) ? baseClassName.join(" ") : baseClassName
61-
);
62+
if (baseClassName)
63+
componentStyles.push(
64+
Array.isArray(baseClassName) ? baseClassName.join(" ") : baseClassName
65+
);
6266

6367
// Apply any variant styles
6468
Object.keys(props).forEach((key) => {
6569
if (variants && variants.hasOwnProperty(key)) {
66-
if (variants[key].hasOwnProperty(props[key])) {
67-
const selector = variants[key][props[key]];
70+
const variant = variants[key as keyof typeof variants];
71+
if (variant && variant.hasOwnProperty(props[key])) {
72+
const selector = variant[props[key] as keyof typeof variant] as
73+
| string
74+
| string[];
6875
componentStyles.push(
6976
Array.isArray(selector) ? selector.join(" ") : selector
7077
);
@@ -89,12 +96,13 @@ export const styled = <V extends variantsType, E extends React.ElementType>(
8996

9097
componentProps.className = componentStyles.join(" ");
9198
styledComponent.displayName = element.toString();
99+
// console.log(componentProps);
92100
return createElement(element, componentProps);
93101
}
94102
);
95103

96104
return styledComponent as React.FC<
97-
PropsOf<E> & {
105+
React.ComponentProps<E> & {
98106
[Property in keyof V]?: BooleanIfStringBoolean<keyof V[Property]>;
99107
}
100108
>;

test/index.test.tsx

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
5+
import React from "react";
6+
7+
import { render } from "@testing-library/react";
8+
9+
import { styled } from "../src";
10+
11+
describe("Basic functionality", () => {
12+
it("it should output the correct type of DOM node", async () => {
13+
const Button = styled("button");
14+
const { container } = render(<Button />);
15+
expect(container.firstChild?.nodeName).toEqual("BUTTON");
16+
});
17+
18+
it("it should apply the base class", async () => {
19+
const Button = styled("button", "root");
20+
const { container } = render(<Button />);
21+
expect(container.firstChild).toHaveClass("root");
22+
});
23+
24+
it("should pass through children", async () => {
25+
const Paragraph = styled("p");
26+
const { container } = render(<Paragraph>Hello</Paragraph>);
27+
expect(container.firstChild).toHaveTextContent("Hello");
28+
});
29+
30+
it("should pass provide typescript support for built in types", async () => {
31+
const Input = styled("input");
32+
const onChange = jest.fn();
33+
const { container } = render(<Input value={"test"} onChange={onChange} />);
34+
expect(container.firstChild).toHaveAttribute("value", "test");
35+
});
36+
});
37+
38+
describe("supports variants and compound variants", () => {
39+
it("should work with a single boolean variant but not set", async () => {
40+
const Button = styled("button", "root", {
41+
primary: { true: "primary" },
42+
});
43+
44+
const { container } = render(<Button />);
45+
46+
expect(container.firstChild).toHaveClass("root");
47+
expect(container.firstChild).not.toHaveClass("primary");
48+
});
49+
50+
it("should work with a single boolean variant", async () => {
51+
const Button = styled("button", "root", {
52+
primary: { true: "primary" },
53+
});
54+
55+
const { container } = render(<Button primary />);
56+
57+
expect(container.firstChild).toHaveClass("root");
58+
expect(container.firstChild).toHaveClass("primary");
59+
});
60+
61+
it("should work with a multiple variants", async () => {
62+
const PageTitle = styled("h2", "root", {
63+
highlighted: { true: "highlighted" },
64+
size: { 0: "size0", 1: "size1" },
65+
align: { left: "left", center: "center", right: "right" },
66+
});
67+
68+
const { container } = render(
69+
<PageTitle highlighted size={0} align="left" />
70+
);
71+
72+
expect(container.firstChild).toHaveClass("root");
73+
expect(container.firstChild).toHaveClass("highlighted");
74+
expect(container.firstChild).toHaveClass("size0");
75+
expect(container.firstChild).toHaveClass("left");
76+
expect(container.firstChild).not.toHaveClass("center");
77+
expect(container.firstChild).not.toHaveClass("right");
78+
expect(container.firstChild).not.toHaveClass("size1");
79+
});
80+
81+
it("should work with compound variants", async () => {
82+
const Button = styled(
83+
"button",
84+
"root",
85+
{
86+
border: {
87+
true: "borderTrue",
88+
},
89+
color: {
90+
primary: "colorPrimary",
91+
secondary: "colorSecondary",
92+
},
93+
},
94+
[
95+
{
96+
border: true,
97+
color: "primary",
98+
css: "borderPrimary",
99+
},
100+
{
101+
border: true,
102+
color: "secondary",
103+
css: "borderSecondary",
104+
},
105+
]
106+
);
107+
108+
const { container } = render(<Button border color={"primary"} />);
109+
110+
expect(container.firstChild).toHaveClass("root");
111+
expect(container.firstChild).toHaveClass("borderTrue");
112+
expect(container.firstChild).toHaveClass("colorPrimary");
113+
expect(container.firstChild).toHaveClass("borderPrimary");
114+
expect(container.firstChild).not.toHaveClass("borderSecondary");
115+
expect(container.firstChild).not.toHaveClass("colorSecondary");
116+
});
117+
});
118+
119+
describe("supports array styles", () => {
120+
it("should should apply the base classes", async () => {
121+
const Button = styled("button", ["baseButton", "button"]);
122+
const { container } = render(<Button />);
123+
expect(container.firstChild).toHaveClass("baseButton");
124+
expect(container.firstChild).toHaveClass("button");
125+
});
126+
127+
it("should should apply the base classes", async () => {
128+
const Button = styled(
129+
"button",
130+
["baseButton", "button"],
131+
{
132+
primary: { true: ["primary", "bold"] },
133+
big: { true: ["big"] },
134+
},
135+
[
136+
{
137+
primary: true,
138+
big: true,
139+
css: ["primaryBig", "primaryBigBold"],
140+
},
141+
]
142+
);
143+
const { container } = render(<Button primary big />);
144+
expect(container.firstChild).toHaveClass("baseButton");
145+
expect(container.firstChild).toHaveClass("button");
146+
expect(container.firstChild).toHaveClass("primary");
147+
expect(container.firstChild).toHaveClass("bold");
148+
expect(container.firstChild).toHaveClass("big");
149+
expect(container.firstChild).toHaveClass("primaryBig");
150+
expect(container.firstChild).toHaveClass("primaryBigBold");
151+
});
152+
});
153+
154+
describe("supports more exotic setups", () => {
155+
it("should be able to style nested react components", async () => {
156+
const BaseButton = styled("button", "baseButton", {
157+
size: { big: "big", small: "small" },
158+
});
159+
const Button = styled(BaseButton, "button", {
160+
color: { primary: "colorPrimary", secondary: "colorSecondary" },
161+
});
162+
const { container } = render(<Button size="big" color="primary" />);
163+
164+
expect(container.firstChild?.nodeName).toEqual("BUTTON");
165+
expect(container.firstChild).toHaveClass("baseButton");
166+
expect(container.firstChild).toHaveClass("button");
167+
expect(container.firstChild).toHaveClass("big");
168+
expect(container.firstChild).toHaveClass("colorPrimary");
169+
});
170+
171+
it("should pass down refs", async () => {
172+
const Button = styled("button");
173+
const ref = jest.fn();
174+
const { container } = render(<Button ref={ref} />);
175+
expect(container.firstChild?.nodeName).toEqual("BUTTON");
176+
expect(ref).toBeCalled();
177+
});
178+
});

tsconfig.test.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"target": "es6"
5+
}
6+
}

0 commit comments

Comments
 (0)