Skip to content

Commit

Permalink
Add Unit Tests (#6)
Browse files Browse the repository at this point in the history
* Add Unit Tests

* minor

* exotic test

* More
  • Loading branch information
John Richard Chipps-Harding authored Nov 13, 2022
1 parent 714732c commit 73d56bf
Show file tree
Hide file tree
Showing 8 changed files with 13,742 additions and 5,050 deletions.
1 change: 1 addition & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ jobs:
npm ci
npm run build --if-present
npm run lint
npm run test
env:
CI: true
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
npm ci
npm run build --if-present
npm run lint
npm run test
env:
CI: true

Expand Down
11 changes: 11 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @type {jest.ProjectConfig}
*/
module.exports = {
roots: ["<rootDir>/test"],
transform: {
"^.+\\.tsx?$": "ts-jest",
},
setupFilesAfterEnv: ["@testing-library/jest-dom/extend-expect"],
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
};
18,554 changes: 13,516 additions & 5,038 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@phntms/css-components",
"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.",
"version": "0.0.2",
"version": "0.0.3",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"homepage": "https://github.com/phantomstudios/css-components#readme",
Expand All @@ -22,6 +22,9 @@
"build": "tsc",
"build:types": "tsc --emitDeclarationOnly",
"prepublishOnly": "npm run build",
"test": "jest --verbose",
"test:watch": "jest --verbose --watch",
"coverage": "jest --coverage",
"lint": "NODE_ENV=test npm-run-all --parallel lint:*",
"lint:js": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
"lint:format": "prettier \"**/*.{md,html,yaml,yml}\" --check",
Expand All @@ -39,6 +42,9 @@
"devDependencies": {
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@types/jest": "^29.2.2",
"@types/react": "^18.0.25",
"@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1",
Expand All @@ -49,10 +55,13 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ts-jest": "^29.0.3",
"typescript": "^4.8.4"
}
}
30 changes: 19 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { createElement, forwardRef } from "react";

type variantValue = string | number | boolean;
type variantValue = string | number | boolean | string[];

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

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

export const styled = <V extends variantsType, E extends React.ElementType>(
export const styled = <
V extends variantsType | object,
E extends React.ElementType
>(
element: E,
baseClassName: string | string[],
baseClassName?: string | string[],
variants?: V,
compoundVariants?: compoundVariantType[]
) => {
Expand All @@ -56,15 +59,19 @@ export const styled = <V extends variantsType, E extends React.ElementType>(
if (ref) componentProps.ref = ref;

// Add the base style(s)
componentStyles.push(
Array.isArray(baseClassName) ? baseClassName.join(" ") : baseClassName
);
if (baseClassName)
componentStyles.push(
Array.isArray(baseClassName) ? baseClassName.join(" ") : baseClassName
);

// Apply any variant styles
Object.keys(props).forEach((key) => {
if (variants && variants.hasOwnProperty(key)) {
if (variants[key].hasOwnProperty(props[key])) {
const selector = variants[key][props[key]];
const variant = variants[key as keyof typeof variants];
if (variant && variant.hasOwnProperty(props[key])) {
const selector = variant[props[key] as keyof typeof variant] as
| string
| string[];
componentStyles.push(
Array.isArray(selector) ? selector.join(" ") : selector
);
Expand All @@ -89,12 +96,13 @@ export const styled = <V extends variantsType, E extends React.ElementType>(

componentProps.className = componentStyles.join(" ");
styledComponent.displayName = element.toString();
// console.log(componentProps);
return createElement(element, componentProps);
}
);

return styledComponent as React.FC<
PropsOf<E> & {
React.ComponentProps<E> & {
[Property in keyof V]?: BooleanIfStringBoolean<keyof V[Property]>;
}
>;
Expand Down
178 changes: 178 additions & 0 deletions test/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* @jest-environment jsdom
*/

import React from "react";

import { render } from "@testing-library/react";

import { styled } from "../src";

describe("Basic functionality", () => {
it("it should output the correct type of DOM node", async () => {
const Button = styled("button");
const { container } = render(<Button />);
expect(container.firstChild?.nodeName).toEqual("BUTTON");
});

it("it should apply the base class", async () => {
const Button = styled("button", "root");
const { container } = render(<Button />);
expect(container.firstChild).toHaveClass("root");
});

it("should pass through children", async () => {
const Paragraph = styled("p");
const { container } = render(<Paragraph>Hello</Paragraph>);
expect(container.firstChild).toHaveTextContent("Hello");
});

it("should pass provide typescript support for built in types", async () => {
const Input = styled("input");
const onChange = jest.fn();
const { container } = render(<Input value={"test"} onChange={onChange} />);
expect(container.firstChild).toHaveAttribute("value", "test");
});
});

describe("supports variants and compound variants", () => {
it("should work with a single boolean variant but not set", async () => {
const Button = styled("button", "root", {
primary: { true: "primary" },
});

const { container } = render(<Button />);

expect(container.firstChild).toHaveClass("root");
expect(container.firstChild).not.toHaveClass("primary");
});

it("should work with a single boolean variant", async () => {
const Button = styled("button", "root", {
primary: { true: "primary" },
});

const { container } = render(<Button primary />);

expect(container.firstChild).toHaveClass("root");
expect(container.firstChild).toHaveClass("primary");
});

it("should work with a multiple variants", async () => {
const PageTitle = styled("h2", "root", {
highlighted: { true: "highlighted" },
size: { 0: "size0", 1: "size1" },
align: { left: "left", center: "center", right: "right" },
});

const { container } = render(
<PageTitle highlighted size={0} align="left" />
);

expect(container.firstChild).toHaveClass("root");
expect(container.firstChild).toHaveClass("highlighted");
expect(container.firstChild).toHaveClass("size0");
expect(container.firstChild).toHaveClass("left");
expect(container.firstChild).not.toHaveClass("center");
expect(container.firstChild).not.toHaveClass("right");
expect(container.firstChild).not.toHaveClass("size1");
});

it("should work with compound variants", async () => {
const Button = styled(
"button",
"root",
{
border: {
true: "borderTrue",
},
color: {
primary: "colorPrimary",
secondary: "colorSecondary",
},
},
[
{
border: true,
color: "primary",
css: "borderPrimary",
},
{
border: true,
color: "secondary",
css: "borderSecondary",
},
]
);

const { container } = render(<Button border color={"primary"} />);

expect(container.firstChild).toHaveClass("root");
expect(container.firstChild).toHaveClass("borderTrue");
expect(container.firstChild).toHaveClass("colorPrimary");
expect(container.firstChild).toHaveClass("borderPrimary");
expect(container.firstChild).not.toHaveClass("borderSecondary");
expect(container.firstChild).not.toHaveClass("colorSecondary");
});
});

describe("supports array styles", () => {
it("should should apply the base classes", async () => {
const Button = styled("button", ["baseButton", "button"]);
const { container } = render(<Button />);
expect(container.firstChild).toHaveClass("baseButton");
expect(container.firstChild).toHaveClass("button");
});

it("should should apply the base classes", async () => {
const Button = styled(
"button",
["baseButton", "button"],
{
primary: { true: ["primary", "bold"] },
big: { true: ["big"] },
},
[
{
primary: true,
big: true,
css: ["primaryBig", "primaryBigBold"],
},
]
);
const { container } = render(<Button primary big />);
expect(container.firstChild).toHaveClass("baseButton");
expect(container.firstChild).toHaveClass("button");
expect(container.firstChild).toHaveClass("primary");
expect(container.firstChild).toHaveClass("bold");
expect(container.firstChild).toHaveClass("big");
expect(container.firstChild).toHaveClass("primaryBig");
expect(container.firstChild).toHaveClass("primaryBigBold");
});
});

describe("supports more exotic setups", () => {
it("should be able to style nested react components", async () => {
const BaseButton = styled("button", "baseButton", {
size: { big: "big", small: "small" },
});
const Button = styled(BaseButton, "button", {
color: { primary: "colorPrimary", secondary: "colorSecondary" },
});
const { container } = render(<Button size="big" color="primary" />);

expect(container.firstChild?.nodeName).toEqual("BUTTON");
expect(container.firstChild).toHaveClass("baseButton");
expect(container.firstChild).toHaveClass("button");
expect(container.firstChild).toHaveClass("big");
expect(container.firstChild).toHaveClass("colorPrimary");
});

it("should pass down refs", async () => {
const Button = styled("button");
const ref = jest.fn();
const { container } = render(<Button ref={ref} />);
expect(container.firstChild?.nodeName).toEqual("BUTTON");
expect(ref).toBeCalled();
});
});
6 changes: 6 additions & 0 deletions tsconfig.test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"target": "es6"
}
}

0 comments on commit 73d56bf

Please sign in to comment.