Skip to content

Commit 2c6ea65

Browse files
committed
test: checkbox.test.tsx
1 parent d9d00ab commit 2c6ea65

File tree

2 files changed

+125
-8
lines changed

2 files changed

+125
-8
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* @file Tests for the Checkbox component.
3+
* @description These are unit tests for the custom `Checkbox` component. They verify
4+
* its rendering, state handling (checked, disabled), custom icon support, input props,
5+
* ref forwarding, and prop passing in a mocked environment.
6+
* @module CheckboxTests
7+
*/
8+
9+
// region Imports
10+
import { Checkbox } from "@/components/ui/checkbox"
11+
import { render, screen } from "@testing-library/react"
12+
import { type RefObject, createRef } from "react"
13+
import { FaCheck } from "react-icons/fa"
14+
import { describe, expect, it } from "vitest"
15+
16+
// endregion
17+
18+
// region Tests
19+
describe("Checkbox", (): void => {
20+
/**
21+
* Test case: Renders the checkbox with its children.
22+
* It verifies that the checkbox is rendered correctly with the provided label
23+
* and is not checked or disabled by default.
24+
*/
25+
it("renders checkbox with label correctly", (): void => {
26+
render(<Checkbox>Check me</Checkbox>)
27+
const checkbox: HTMLElement = screen.getByTestId("checkbox-input")
28+
const label: HTMLElement = screen.getByTestId("checkbox-label")
29+
expect(checkbox).toBeInTheDocument()
30+
expect(label).toBeInTheDocument()
31+
expect(label).toHaveTextContent("Check me")
32+
expect(checkbox).not.toBeChecked()
33+
expect(checkbox).not.toBeDisabled()
34+
})
35+
36+
/**
37+
* Test case: Handles the checked state.
38+
* It verifies that the checkbox is checked when the `defaultChecked` prop is passed via inputProps.
39+
*/
40+
it("is checked when defaultChecked prop is true", (): void => {
41+
render(<Checkbox inputProps={{ defaultChecked: true }}>Check me</Checkbox>)
42+
const checkbox: HTMLElement = screen.getByTestId("checkbox-input")
43+
expect(checkbox).toBeChecked()
44+
})
45+
46+
/**
47+
* Test case: Handles the disabled state.
48+
* It verifies that the checkbox is disabled when the `disabled` prop is passed via inputProps.
49+
*/
50+
it("is disabled when disabled prop is true", (): void => {
51+
render(<Checkbox inputProps={{ disabled: true }}>Check me</Checkbox>)
52+
const checkbox: HTMLElement = screen.getByTestId("checkbox-input")
53+
expect(checkbox).toBeDisabled()
54+
})
55+
56+
/**
57+
* Test case: Renders custom icon when provided.
58+
* It verifies that the custom icon is rendered instead of the default indicator.
59+
*/
60+
it("renders custom icon when provided", (): void => {
61+
render(<Checkbox icon={<FaCheck data-testid="custom-icon" />}>Check me</Checkbox>)
62+
const customIcon: HTMLElement = screen.getByTestId("custom-icon")
63+
expect(customIcon).toBeInTheDocument()
64+
expect(screen.queryByTestId("checkbox-indicator")).not.toBeInTheDocument()
65+
})
66+
67+
/**
68+
* Test case: Passes inputProps to the hidden input.
69+
* It verifies that additional props (like `name`) are passed to the hidden input.
70+
*/
71+
it("passes inputProps to the hidden input", (): void => {
72+
render(<Checkbox inputProps={{ name: "test-checkbox" }}>Check me</Checkbox>)
73+
const checkbox: HTMLElement = screen.getByTestId("checkbox-input")
74+
expect(checkbox).toHaveAttribute("name", "test-checkbox")
75+
})
76+
77+
/**
78+
* Test case: Forwards refs correctly.
79+
* It verifies that the `ref` is correctly forwarded to the underlying input element.
80+
*/
81+
it("forwards ref to the underlying input element", (): void => {
82+
const ref: RefObject<HTMLInputElement> = createRef<HTMLInputElement>()
83+
render(<Checkbox ref={ref}>Check me</Checkbox>)
84+
expect(ref.current).toBeInstanceOf(HTMLInputElement)
85+
})
86+
87+
/**
88+
* Test case: Passes additional props to the root element.
89+
* It verifies that additional props (like `aria-label`) are passed to the root element.
90+
*/
91+
it("passes additional props to the root element", (): void => {
92+
render(<Checkbox aria-label="Custom Checkbox">Check me</Checkbox>)
93+
const root: HTMLElement = screen.getByTestId("checkbox-root")
94+
expect(root).toHaveAttribute("aria-label", "Custom Checkbox")
95+
})
96+
})
97+
// endregion

frontend/tests/setupTests.tsx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ Object.defineProperty(window, "matchMedia", {
3939
*
4040
* This mock is crucial and solves three fundamental problems when testing
4141
* Chakra UI components in a JSDOM environment:
42-
* 1. **Hoisting Issues:** `vi.mock` is called at the top level, allowing Vitest to
43-
* correctly hoist it before any imports occur.
44-
* 2. **CSS Parsing Errors:** The mock provides a simple `ChakraProvider` that does
45-
* not inject any CSS, bypassing JSDOM's inability to parse modern CSS syntax
46-
* like `@layer`.
47-
* 3. **Context Errors:** Components that rely on Chakra's context (like `useTheme`)
48-
* are replaced with simple, "dumb" versions that do not use `useContext`,
49-
* preventing "context is undefined" errors.
42+
* 1. Hoisting Issues: `vi.mock` is called at the top level, allowing Vitest to
43+
* correctly hoist it before any imports occur.
44+
* 2. CSS Parsing Errors: The mock provides a simple `ChakraProvider` that does
45+
* not inject any CSS, bypassing JSDOM's inability to parse modern CSS syntax
46+
* like `@layer`.
47+
* 3. Context Errors: Components that rely on Chakra's context (like `useTheme`)
48+
* are replaced with simple, "dumb" versions that do not use `useContext`,
49+
* preventing "context is undefined" errors.
5050
*/
5151
vi.mock("@chakra-ui/react", () => ({
5252
ChakraProvider: ({ children }: { children: ReactNode }): React.ReactElement => <>{children}</>,
@@ -62,6 +62,26 @@ vi.mock("@chakra-ui/react", () => ({
6262
<button ref={ref} {...props} />
6363
),
6464
),
65+
Checkbox: {
66+
Root: (props: ComponentPropsWithoutRef<"div">): React.ReactElement => (
67+
<div data-testid="checkbox-root" {...props} />
68+
),
69+
HiddenInput: React.forwardRef<HTMLInputElement, ComponentPropsWithoutRef<"input">>(
70+
(props: ComponentPropsWithoutRef<"input">, ref: ForwardedRef<HTMLInputElement>): React.ReactElement => (
71+
<input type="checkbox" data-testid="checkbox-input" ref={ref} {...props} />
72+
),
73+
),
74+
Control: (props: ComponentPropsWithoutRef<"div">): React.ReactElement => (
75+
<div data-testid="checkbox-control" {...props} />
76+
),
77+
Indicator: (props: ComponentPropsWithoutRef<"div">): React.ReactElement => (
78+
<div data-testid="checkbox-indicator" {...props} />
79+
),
80+
// Replace <label> with <span> to remove the requirement for association with input.
81+
Label: (props: ComponentPropsWithoutRef<"span">): React.ReactElement => (
82+
<span data-testid="checkbox-label" {...props} />
83+
),
84+
},
6585
// Provides a dummy function for `defineRecipe` so that imports in files
6686
// like `theme.tsx` do not fail, even if they are not directly used in a test.
6787
defineRecipe: vi.fn(() => ({})),

0 commit comments

Comments
 (0)