Skip to content

Commit 1af65b3

Browse files
committed
test: field.test.tsx
1 parent f95ae65 commit 1af65b3

File tree

2 files changed

+221
-7
lines changed

2 files changed

+221
-7
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/** @file Unit tests for src/components/ui/field.tsx.
2+
* @description Tests the rendering and behavior of the Field component, ensuring it correctly renders
3+
* Chakra UI Field components with provided props and handles conditional rendering.
4+
* @module FieldTests
5+
*/
6+
7+
import { Field } from "@/components/ui/field"
8+
import { render, screen } from "@testing-library/react"
9+
import * as React from "react"
10+
import { describe, expect, it } from "vitest"
11+
12+
// region Main Code
13+
14+
describe("Field Component", (): void => {
15+
/**
16+
* Tests rendering of the Field component with only required props (children).
17+
* @description Ensures the component renders children inside ChakraField.Root without label, helperText, or errorText.
18+
*/
19+
it("renders with only children", (): void => {
20+
const input: React.ReactElement = <input data-testid="input" />
21+
render(<Field>{input}</Field>)
22+
23+
expect(screen.getByTestId("field-root")).toBeInTheDocument()
24+
expect(screen.getByTestId("input")).toBeInTheDocument()
25+
expect(screen.queryByTestId("field-label")).not.toBeInTheDocument()
26+
expect(screen.queryByTestId("field-helper-text")).not.toBeInTheDocument()
27+
expect(screen.queryByTestId("field-error-text")).not.toBeInTheDocument()
28+
expect(screen.queryByTestId("field-required-indicator")).not.toBeInTheDocument()
29+
})
30+
31+
/**
32+
* Tests rendering with label prop.
33+
* @description Ensures the label is rendered inside ChakraField.Label with RequiredIndicator.
34+
*/
35+
it("renders with label", (): void => {
36+
const labelText = "Test Label"
37+
const input: React.ReactElement = <input data-testid="input" />
38+
render(<Field label={labelText}>{input}</Field>)
39+
40+
expect(screen.getByTestId("field-root")).toBeInTheDocument()
41+
expect(screen.getByTestId("field-label")).toHaveTextContent(labelText)
42+
expect(screen.getByTestId("field-required-indicator")).toBeInTheDocument()
43+
expect(screen.getByTestId("input")).toBeInTheDocument()
44+
expect(screen.queryByTestId("field-helper-text")).not.toBeInTheDocument()
45+
expect(screen.queryByTestId("field-error-text")).not.toBeInTheDocument()
46+
})
47+
48+
/**
49+
* Tests rendering with helperText prop.
50+
* @description Ensures helperText is rendered inside ChakraField.HelperText.
51+
*/
52+
it("renders with helperText", (): void => {
53+
const helperText = "Helper text"
54+
const input: React.ReactElement = <input data-testid="input" />
55+
render(<Field helperText={helperText}>{input}</Field>)
56+
57+
expect(screen.getByTestId("field-root")).toBeInTheDocument()
58+
expect(screen.getByTestId("field-helper-text")).toHaveTextContent(helperText)
59+
expect(screen.getByTestId("input")).toBeInTheDocument()
60+
expect(screen.queryByTestId("field-label")).not.toBeInTheDocument()
61+
expect(screen.queryByTestId("field-error-text")).not.toBeInTheDocument()
62+
expect(screen.queryByTestId("field-required-indicator")).not.toBeInTheDocument()
63+
})
64+
65+
/**
66+
* Tests rendering with errorText prop.
67+
* @description Ensures errorText is rendered inside ChakraField.ErrorText.
68+
*/
69+
it("renders with errorText", (): void => {
70+
const errorText = "Error message"
71+
const input: React.ReactElement = <input data-testid="input" />
72+
render(<Field errorText={errorText}>{input}</Field>)
73+
74+
expect(screen.getByTestId("field-root")).toBeInTheDocument()
75+
expect(screen.getByTestId("field-error-text")).toHaveTextContent(errorText)
76+
expect(screen.getByTestId("input")).toBeInTheDocument()
77+
expect(screen.queryByTestId("field-label")).not.toBeInTheDocument()
78+
expect(screen.queryByTestId("field-helper-text")).not.toBeInTheDocument()
79+
expect(screen.queryByTestId("field-required-indicator")).not.toBeInTheDocument()
80+
})
81+
82+
/**
83+
* Tests rendering with optionalText prop when label is present.
84+
* @description Ensures optionalText is passed to RequiredIndicator as fallback.
85+
*/
86+
it("renders with optionalText and label", (): void => {
87+
const labelText = "Test Label"
88+
const optionalText = "Optional"
89+
const input: React.ReactElement = <input data-testid="input" />
90+
render(
91+
<Field label={labelText} optionalText={optionalText}>
92+
{input}
93+
</Field>,
94+
)
95+
96+
expect(screen.getByTestId("field-root")).toBeInTheDocument()
97+
expect(screen.getByTestId("field-label")).toHaveTextContent(labelText)
98+
expect(screen.getByTestId("field-required-indicator")).toHaveTextContent(optionalText)
99+
expect(screen.getByTestId("input")).toBeInTheDocument()
100+
expect(screen.queryByTestId("field-helper-text")).not.toBeInTheDocument()
101+
expect(screen.queryByTestId("field-error-text")).not.toBeInTheDocument()
102+
})
103+
104+
/**
105+
* Tests forwarding of ref to ChakraField.Root.
106+
* @description Ensures the ref is correctly attached to the root div.
107+
*/
108+
it("forwards ref to ChakraField.Root", (): void => {
109+
const ref = React.createRef<HTMLDivElement>()
110+
const input: React.ReactElement = <input data-testid="input" />
111+
render(<Field ref={ref}>{input}</Field>)
112+
113+
expect(ref.current).toBeInstanceOf(HTMLDivElement)
114+
expect(ref.current).toHaveAttribute("data-testid", "field-root")
115+
})
116+
117+
/**
118+
* Tests passing additional props to ChakraField.Root.
119+
* @description Ensures custom props like className are passed to the root element.
120+
*/
121+
it("passes additional props to ChakraField.Root", (): void => {
122+
const className = "custom-class"
123+
const input: React.ReactElement = <input data-testid="input" />
124+
render(<Field className={className}>{input}</Field>)
125+
126+
expect(screen.getByTestId("field-root")).toHaveClass(className)
127+
expect(screen.getByTestId("input")).toBeInTheDocument()
128+
})
129+
130+
/**
131+
* Tests rendering with all props combined.
132+
* @description Ensures the component renders all parts (label, helperText, errorText, optionalText) correctly.
133+
*/
134+
it("renders with all props", (): void => {
135+
const labelText = "Test Label"
136+
const helperText = "Helper text"
137+
const errorText = "Error message"
138+
const optionalText = "Optional"
139+
const input: React.ReactElement = <input data-testid="input" />
140+
render(
141+
<Field label={labelText} helperText={helperText} errorText={errorText} optionalText={optionalText}>
142+
{input}
143+
</Field>,
144+
)
145+
146+
expect(screen.getByTestId("field-root")).toBeInTheDocument()
147+
expect(screen.getByTestId("field-label")).toHaveTextContent(labelText)
148+
expect(screen.getByTestId("field-helper-text")).toHaveTextContent(helperText)
149+
expect(screen.getByTestId("field-error-text")).toHaveTextContent(errorText)
150+
expect(screen.getByTestId("field-required-indicator")).toHaveTextContent(optionalText)
151+
expect(screen.getByTestId("input")).toBeInTheDocument()
152+
})
153+
})
154+
155+
// endregion

frontend/tests/setupTests.tsx

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,29 @@ type CloseButtonProps = {
7070
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
7171
} & ComponentPropsWithoutRef<"button">
7272

73+
/** Props for ChakraField components */
74+
type ChakraFieldRootProps = {
75+
children: ReactNode
76+
ref?: ForwardedRef<HTMLDivElement>
77+
} & ComponentPropsWithoutRef<"div">
78+
79+
type ChakraFieldLabelProps = {
80+
children: ReactNode
81+
htmlFor?: string
82+
} & ComponentPropsWithoutRef<"label">
83+
84+
type ChakraFieldHelperTextProps = {
85+
children: ReactNode
86+
} & ComponentPropsWithoutRef<"div">
87+
88+
type ChakraFieldErrorTextProps = {
89+
children: ReactNode
90+
} & ComponentPropsWithoutRef<"div">
91+
92+
type ChakraFieldRequiredIndicatorProps = {
93+
fallback?: ReactNode
94+
} & ComponentPropsWithoutRef<"span">
95+
7396
/** Dialog context type */
7497
type DialogContextType = {
7598
onClose: () => void
@@ -118,6 +141,7 @@ const mockChakraUI = (): void => {
118141
const ThemeContext = createContext({ theme: { _config: {} } })
119142

120143
// region Mock Components
144+
121145
const CloseButton = React.forwardRef<HTMLButtonElement, CloseButtonProps>(
122146
(
123147
{ children, size, onClick, ...props }: CloseButtonProps,
@@ -266,6 +290,38 @@ const mockChakraUI = (): void => {
266290
},
267291
)
268292

293+
const FieldRoot = React.forwardRef<HTMLDivElement, ChakraFieldRootProps>(
294+
({ children, ...props }: ChakraFieldRootProps, ref: ForwardedRef<HTMLDivElement>): React.ReactElement => (
295+
<div data-testid="field-root" ref={ref} {...props}>
296+
{children}
297+
</div>
298+
),
299+
)
300+
301+
const FieldLabel = ({ children, htmlFor, ...props }: ChakraFieldLabelProps): React.ReactElement => (
302+
<label data-testid="field-label" htmlFor={htmlFor} {...props}>
303+
{children}
304+
</label>
305+
)
306+
307+
const FieldHelperText = ({ children, ...props }: ChakraFieldHelperTextProps): React.ReactElement => (
308+
<div data-testid="field-helper-text" {...props}>
309+
{children}
310+
</div>
311+
)
312+
313+
const FieldErrorText = ({ children, ...props }: ChakraFieldErrorTextProps): React.ReactElement => (
314+
<div data-testid="field-error-text" {...props}>
315+
{children}
316+
</div>
317+
)
318+
319+
const FieldRequiredIndicator = ({ fallback, ...props }: ChakraFieldRequiredIndicatorProps): React.ReactElement => (
320+
<span data-testid="field-required-indicator" {...props}>
321+
{fallback || "*"}
322+
</span>
323+
)
324+
269325
// endregion
270326

271327
return {
@@ -290,6 +346,13 @@ const mockChakraUI = (): void => {
290346
</div>
291347
),
292348
CloseButton,
349+
Field: {
350+
Root: FieldRoot,
351+
Label: FieldLabel,
352+
HelperText: FieldHelperText,
353+
ErrorText: FieldErrorText,
354+
RequiredIndicator: FieldRequiredIndicator,
355+
},
293356
Dialog: {
294357
Root: ({ children, defaultOpen, open, onOpenChange, ...props }: DialogRootProps): React.ReactElement => {
295358
const [isOpen, setIsOpen] = useState(defaultOpen ?? open ?? false)
@@ -429,7 +492,7 @@ const mockChakraUI = (): void => {
429492
const [isOpen, setIsOpen] = useState(defaultOpen ?? open ?? false)
430493
const isControlled = open !== undefined
431494
const effectiveOpen = isControlled ? open : isOpen
432-
const handleOpenChange = (newOpen: boolean) => {
495+
const handleOpenChange = (newOpen: boolean): void => {
433496
if (!isControlled) setIsOpen(newOpen)
434497
onOpenChange?.({ open: newOpen })
435498
}
@@ -438,9 +501,7 @@ const mockChakraUI = (): void => {
438501
<div data-testid="drawer-root" {...props}>
439502
<DialogContext.Provider value={{ onClose: () => handleOpenChange(false), isOpen: effectiveOpen }}>
440503
{React.Children.map(children, (child) => {
441-
if (!React.isValidElement(child)) {
442-
return child
443-
}
504+
if (!React.isValidElement(child)) return child
444505
const childType =
445506
typeof child.type !== "string" && "displayName" in child.type ? child.type.displayName : undefined
446507
if (childType === "DrawerTrigger") {
@@ -478,9 +539,7 @@ const mockChakraUI = (): void => {
478539
</DialogCloseTrigger>
479540
)
480541
}
481-
if (childType === "DrawerContent" && !effectiveOpen) {
482-
return null
483-
}
542+
if (childType === "DrawerContent" && !effectiveOpen) return null
484543
return child
485544
})}
486545
</DialogContext.Provider>

0 commit comments

Comments
 (0)