Skip to content

Commit

Permalink
Add some more tests and fix violations
Browse files Browse the repository at this point in the history
  • Loading branch information
hstandaert committed Apr 15, 2024
1 parent 147dd53 commit 03929a1
Show file tree
Hide file tree
Showing 12 changed files with 146 additions and 61 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
jobs:
quality:
runs-on: ubuntu-latest
name: Check code quality
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -21,6 +22,7 @@ jobs:

tests:
runs-on: ubuntu-latest
name: Run tests
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ dist
dist-ssr
*.local

coverage

# Editor directories and files
.vscode/*
!.vscode/extensions.json
Expand Down
13 changes: 13 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,22 @@ export default {
transform: {
"^.+\\.tsx?$": "ts-jest",
},
moduleDirectories: [
"node_modules",
"test", // a utility folder
],
coveragePathIgnorePatterns: ["test"],
moduleNameMapper: {
"\\.(gif|ttf|eot|svg|png)$": "<rootDir>/test/__mocks__/fileMock.ts",
"^@/(.*)$": "<rootDir>/src/$1",
},
setupFilesAfterEnv: ["./test/setupTests.ts"],
coverageThreshold: {
global: {
branches: 65,
functions: 65,
lines: 65,
statements: 65,
},
},
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@biomejs/biome": "^1.6.4",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^15.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.12",
"@types/jest-axe": "^3.5.9",
"@types/node": "^20.12.7",
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { axe } from "jest-axe";
import { render } from "test-utils";
import { Landing } from "./pages";

describe("App", () => {
it("should have no accessibility violations", async () => {
const { container } = render(<Landing />);
const results = await axe(container);

expect(results).toHaveNoViolations();
});
});
47 changes: 47 additions & 0 deletions src/components/Button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { axe } from "jest-axe";
import { render, screen } from "test-utils";
import Button from "./Button";

describe("Button", () => {
it("should render a button", () => {
render(<Button>Click me</Button>);

expect(screen.getByRole("button")).toBeInTheDocument();
expect(screen.getByRole("button")).toHaveTextContent("Click me");
});

it("should have no accessibility violations", async () => {
const { container } = render(<Button>Click me</Button>);

const results = await axe(container);
expect(results).toHaveNoViolations();
});

it("can be a submit button", () => {
render(<Button type="submit">Submit</Button>);
expect(screen.getByRole("button")).toHaveAttribute("type", "submit");
});

it("should execute the onClick handler when clicked", async () => {
const onClick = jest.fn();
const { user } = render(<Button onClick={onClick}>Click me</Button>);

await user.click(screen.getByRole("button"));

expect(onClick).toHaveBeenCalled();
});

it("can be disabled", async () => {
const onClick = jest.fn();
const { user } = render(
<Button disabled onClick={onClick}>
Click me
</Button>,
);

await user.click(screen.getByRole("button"));

expect(screen.getByRole("button")).toBeDisabled();
expect(onClick).not.toHaveBeenCalled();
});
});
5 changes: 3 additions & 2 deletions src/components/NewsTeaser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ function NewsTeaser({ image, title, summary, href, altText }: NewsTeaserProps) {
</a>
</div>

{/* <a
{/* biome-ignore lint/a11y/useAnchorContent: <explanation> */}
<a
href={href}
aria-hidden
tabIndex={-1}
target="_blank"
rel="noreferrer"
className="absolute inset-0"
/> */}
/>

<img
src={image}
Expand Down
86 changes: 37 additions & 49 deletions src/components/Switch.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { useAppState } from "@/providers/AppStateProvider";
import { useAppState } from "@/providers";
import { type HTMLAttributes, useId } from "react";
import { twMerge } from "tailwind-merge";

type SwitchProps = Omit<HTMLAttributes<HTMLInputElement>, "onChange"> & {
type SwitchProps = Omit<
HTMLAttributes<HTMLButtonElement>,
"onClick" | "onChange"
> & {
label: string;
value?: boolean;
onChange?: (value: boolean) => void;
Expand All @@ -12,40 +15,27 @@ function Switch({ label, value, onChange, className, ...props }: SwitchProps) {
const id = useId();
const { isCompliant } = useAppState();

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange?.(e.target.checked);
const handleChange = () => {
onChange?.(!value);
};

return (
<label
className={twMerge(
"inline-flex",
"items-center",
"cursor-pointer",
className,
)}
>
<input
<div className="inline-flex items-center">
<button
type="button"
role="switch"
aria-checked={value ? "true" : "false"}
onClick={handleChange}
id={id}
type="checkbox"
checked={value}
className="sr-only peer"
onChange={handleChange}
{...props}
/>

<div
aria-labelledby={`${id}-label`}
className={twMerge(
className={twMerge([
// Common styles
"relative",
"w-11",
"h-6",
"bg-white/40",
"peer-focus:outline-none",
"rounded-full",
"peer",
"peer-checked:after:translate-x-full",
"peer-checked:after:border-white",

// Checked indicator
"after:content-['']",
"after:absolute",
"after:top-[2px]",
Expand All @@ -56,30 +46,28 @@ function Switch({ label, value, onChange, className, ...props }: SwitchProps) {
"after:rounded-full",
"after:h-5",
"after:w-5",
"after:transition-all",
"peer-checked:bg-green",

// Checked state
value && ["after:translate-x-full", "after:border-white", "bg-green"],

// Focus
"focus:outline-none",
isCompliant && [
"peer-focus:ring-2",
"peer-focus:ring-outlineColor",
"peer-focus:ring-offset-4",
"peer-focus:ring-offset-blueZodiac",
"focus:ring-2",
"focus:ring-outlineColor",
"focus:ring-offset-4",
"focus:ring-offset-blueZodiac",
],
)}
/>
{isCompliant ? (
<label
className="ms-3 text-sm font-medium"
htmlFor={id}
id={`${id}-label`}
>
{label}
</label>
) : (
<span id={`${id}-label`} className="ms-3 text-sm font-medium">
{label}
</span>
)}
</label>
])}
{...props}
>
<span className="sr-only">off</span>
<span className="sr-only">on</span>
</button>
<label htmlFor={id} className="ms-3 text-sm font-medium cursor-pointer">
{label}
</label>
</div>
);
}

Expand Down
16 changes: 8 additions & 8 deletions src/pages/Landing/MissionStatement.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { axe } from "jest-axe";
import { render, screen } from "../../../test/test-utils";
import { render, screen } from "test-utils";
import MissionStatement from "./MissionStatement";

describe("MissionStatement", () => {
Expand All @@ -13,6 +13,13 @@ describe("MissionStatement", () => {
/>
);

it("should have no accessibility violations", async () => {
const { container } = render(testElement);

const results = await axe(container);
expect(results).toHaveNoViolations();
});

it("should render content", async () => {
render(testElement);

Expand All @@ -22,11 +29,4 @@ describe("MissionStatement", () => {
expect(screen.getByRole("paragraph")).toHaveTextContent("body");
expect(screen.getByRole("blockquote")).toHaveTextContent("quote");
});

it("should have no accessibility violations", async () => {
const { container } = render(testElement);

const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
8 changes: 7 additions & 1 deletion test/test-utils.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type RenderOptions, render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import type { PropsWithChildren, ReactElement } from "react";
import { AppStateProvider } from "../src/providers";

Expand All @@ -16,7 +17,12 @@ const AllTheProviders = ({ children }: PropsWithChildren) => {
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, "wrapper">,
) => render(ui, { wrapper: AllTheProviders, ...options });
) => {
return {
user: userEvent.setup(),
...render(ui, { wrapper: AllTheProviders, ...options }),
};
};

// re-export everything
export * from "@testing-library/react";
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
/* Paths */
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
"@/*": ["./src/*"],
"test-utils": ["./test/test-utils"]
}
},
"include": ["src", "test/setupTests.ts", "test/__mocks__"],
Expand Down

0 comments on commit 03929a1

Please sign in to comment.