Skip to content

Commit 5c74ffd

Browse files
committed
style:
1 parent 5c10763 commit 5c74ffd

File tree

8 files changed

+579
-669
lines changed

8 files changed

+579
-669
lines changed

frontend/tests/components/ui/button.test.tsx

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,58 @@
99
// region Imports
1010
import { Button } from "@/components/ui/button"
1111
import { render, screen } from "@testing-library/react"
12-
import { type RefObject, createRef } from "react"
13-
import { describe, expect, it } from "vitest"
12+
import React, { type RefObject, createRef } from "react" // Оставляем только то, что нужно для тестов
13+
import { describe, expect, it, vi } from "vitest"
14+
// endregion
15+
16+
// region Mocks
17+
/**
18+
* Mocks the required components from `@chakra-ui/react` for Button tests.
19+
* This approach isolates the test environment, ensuring that only the necessary
20+
* components are mocked for this specific test suite.
21+
*
22+
* NOTE: The mock factory is hoisted by Vitest and runs before other module code.
23+
* To avoid issues with undefined variables, all dependencies (like React)
24+
* must be imported *inside* the factory.
25+
*/
26+
vi.mock("@chakra-ui/react", async () => {
27+
const React = await import("react")
28+
const actual = await vi.importActual<typeof import("@chakra-ui/react")>("@chakra-ui/react")
29+
30+
const ThemeContext = React.createContext({ theme: { _config: {} } })
31+
32+
return {
33+
...actual,
34+
ChakraProvider: ({ children }: { children: React.ReactNode }): React.ReactElement => (
35+
<ThemeContext.Provider value={{ theme: { _config: {} } }}>{children}</ThemeContext.Provider>
36+
),
37+
AbsoluteCenter: (props: React.ComponentPropsWithoutRef<"div">): React.ReactElement => (
38+
<div data-testid="absolute-center" {...props} />
39+
),
40+
Span: React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<"span">>(
41+
(
42+
{
43+
colorPalette,
44+
colorScheme,
45+
...props
46+
}: React.ComponentPropsWithoutRef<"span"> & {
47+
colorPalette?: string
48+
colorScheme?: string
49+
},
50+
ref: React.ForwardedRef<HTMLSpanElement>,
51+
): React.ReactElement => <span data-testid="span" ref={ref} {...props} />,
52+
),
53+
Spinner: (props: React.ComponentPropsWithoutRef<"div">): React.ReactElement => (
54+
<div role="status" data-testid="spinner" {...props} />
55+
),
56+
Button: React.forwardRef<HTMLButtonElement, React.ComponentPropsWithoutRef<"button">>(
57+
(
58+
props: React.ComponentPropsWithoutRef<"button">,
59+
ref: React.ForwardedRef<HTMLButtonElement>,
60+
): React.ReactElement => <button ref={ref} {...props} />,
61+
),
62+
}
63+
})
1464
// endregion
1565

1666
// region Tests

frontend/tests/components/ui/checkbox.test.tsx

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,52 @@
99
// region Imports
1010
import { Checkbox } from "@/components/ui/checkbox"
1111
import { render, screen } from "@testing-library/react"
12-
import { type RefObject, createRef } from "react"
12+
import React, { type RefObject, createRef } from "react"
1313
import { FaCheck } from "react-icons/fa"
14-
import { describe, expect, it } from "vitest"
14+
import { describe, expect, it, vi } from "vitest"
15+
// endregion
16+
17+
// region Mocks
18+
/**
19+
* Mocks the required components from `@chakra-ui/react` for Checkbox tests.
20+
*
21+
* NOTE: The mock factory is hoisted by Vitest and runs before other module code.
22+
* To avoid issues with undefined variables, all dependencies (like React)
23+
* must be imported *inside* the factory.
24+
*/
25+
vi.mock("@chakra-ui/react", async () => {
26+
const React = await import("react")
27+
const actual = await vi.importActual<typeof import("@chakra-ui/react")>("@chakra-ui/react")
28+
29+
const ThemeContext = React.createContext({ theme: { _config: {} } })
1530

31+
return {
32+
...actual,
33+
ChakraProvider: ({ children }: { children: React.ReactNode }): React.ReactElement => (
34+
<ThemeContext.Provider value={{ theme: { _config: {} } }}>{children}</ThemeContext.Provider>
35+
),
36+
Checkbox: {
37+
Root: (props: React.ComponentPropsWithoutRef<"div">): React.ReactElement => (
38+
<div data-testid="checkbox-root" {...props} />
39+
),
40+
HiddenInput: React.forwardRef<HTMLInputElement, React.ComponentPropsWithoutRef<"input">>(
41+
(
42+
props: React.ComponentPropsWithoutRef<"input">,
43+
ref: React.ForwardedRef<HTMLInputElement>,
44+
): React.ReactElement => <input type="checkbox" data-testid="checkbox-input" ref={ref} {...props} />,
45+
),
46+
Control: (props: React.ComponentPropsWithoutRef<"div">): React.ReactElement => (
47+
<div data-testid="checkbox-control" {...props} />
48+
),
49+
Indicator: (props: React.ComponentPropsWithoutRef<"div">): React.ReactElement => (
50+
<div data-testid="checkbox-indicator" {...props} />
51+
),
52+
Label: (props: React.ComponentPropsWithoutRef<"span">): React.ReactElement => (
53+
<span data-testid="checkbox-label" {...props} />
54+
),
55+
},
56+
}
57+
})
1658
// endregion
1759

1860
// region Tests

frontend/tests/components/ui/close-button.test.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,40 @@
99
// region Imports
1010
import { CloseButton } from "@/components/ui/close-button"
1111
import { render, screen } from "@testing-library/react"
12-
import { type RefObject, createRef } from "react"
13-
import { describe, expect, it } from "vitest"
12+
import React, { type RefObject, createRef } from "react"
13+
import { describe, expect, it, vi } from "vitest"
14+
// endregion
15+
16+
// region Mocks
17+
/**
18+
* Mocks the required components from `@chakra-ui/react` for CloseButton tests.
19+
* The internal dependency `IconButton` is mocked to prevent it from calling
20+
* context-dependent hooks. `defineRecipe` is also mocked as it's part of
21+
* Chakra's styling system used by real components.
22+
*
23+
* NOTE: The mock factory is hoisted by Vitest. All dependencies (like React)
24+
* must be imported *inside* the factory to avoid hoisting-related errors.
25+
*/
26+
vi.mock("@chakra-ui/react", async () => {
27+
const React = await import("react")
28+
const actual = await vi.importActual<typeof import("@chakra-ui/react")>("@chakra-ui/react")
1429

30+
const ThemeContext = React.createContext({ theme: { _config: {} } })
31+
32+
return {
33+
...actual,
34+
ChakraProvider: ({ children }: { children: React.ReactNode }): React.ReactElement => (
35+
<ThemeContext.Provider value={{ theme: { _config: {} } }}>{children}</ThemeContext.Provider>
36+
),
37+
IconButton: React.forwardRef<HTMLButtonElement, React.ComponentPropsWithoutRef<"button">>(
38+
(
39+
{ "aria-label": ariaLabel, ...props }: React.ComponentPropsWithoutRef<"button">,
40+
ref: React.ForwardedRef<HTMLButtonElement>,
41+
): React.ReactElement => <button ref={ref} aria-label={ariaLabel} {...props} />,
42+
),
43+
defineRecipe: vi.fn(() => ({})),
44+
}
45+
})
1546
// endregion
1647

1748
// region Tests
@@ -85,4 +116,5 @@ describe("CloseButton", (): void => {
85116
expect(button).toHaveAttribute("data-test", "custom-prop")
86117
})
87118
})
119+
88120
// endregion

frontend/tests/components/ui/color-mode.test.tsx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import type React from "react"
2323
import { type RefObject, createRef } from "react"
2424
import type { Dispatch, SetStateAction } from "react"
2525
import { describe, expect, it, vi } from "vitest"
26-
2726
// endregion
2827

2928
// region Type Aliases
@@ -40,6 +39,38 @@ vi.mock("next-themes", () => ({
4039
useTheme: vi.fn(),
4140
}))
4241

42+
/**
43+
* Mocks dependencies from `@chakra-ui/react`.
44+
* - The IconButton mock is updated to correctly handle the `boxSize` prop, preventing React warnings.
45+
*/
46+
vi.mock("@chakra-ui/react", async () => {
47+
const React = await import("react")
48+
// Correctly define types for use within the mock
49+
type ComponentPropsWithoutRef<T extends React.ElementType> = React.ComponentPropsWithoutRef<T>
50+
type ForwardedRef<T> = React.ForwardedRef<T>
51+
type ReactNode = React.ReactNode
52+
53+
return {
54+
...(await vi.importActual<typeof import("@chakra-ui/react")>("@chakra-ui/react")),
55+
ChakraProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
56+
// FIX: The IconButton mock now correctly handles the `boxSize` prop to prevent warnings.
57+
IconButton: React.forwardRef<HTMLButtonElement, ComponentPropsWithoutRef<"button"> & { boxSize?: string }>(
58+
({ boxSize, ...rest }, ref: ForwardedRef<HTMLButtonElement>): React.ReactElement => (
59+
<button ref={ref} {...rest} />
60+
),
61+
),
62+
Span: React.forwardRef<
63+
HTMLSpanElement,
64+
ComponentPropsWithoutRef<"span"> & { colorPalette?: string; colorScheme?: string }
65+
>(
66+
({ colorPalette, colorScheme, ...props }, ref: ForwardedRef<HTMLSpanElement>): React.ReactElement => (
67+
<span data-testid="span" ref={ref} {...props} />
68+
),
69+
),
70+
ClientOnly: ({ children }: { children: ReactNode; fallback?: ReactNode }): React.ReactElement => <>{children}</>,
71+
defineRecipe: vi.fn(() => ({})),
72+
}
73+
})
4374
// endregion
4475

4576
// region Tests

frontend/tests/components/ui/dialog.test.tsx

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,155 @@ import { act, render, screen } from "@testing-library/react"
2525
import userEvent from "@testing-library/user-event"
2626
import type React from "react"
2727
import { type ForwardedRef, type ReactNode, type RefObject, createRef, useState } from "react"
28-
import { describe, expect, it } from "vitest"
28+
import { describe, expect, it, vi } from "vitest"
29+
// endregion
30+
31+
// region Mocks
32+
vi.mock("@chakra-ui/react", async () => {
33+
const React = await import("react")
34+
const { useState, createContext, useContext } = React
35+
36+
// region Type Aliases (Copied EXACTLY from original TestSetup)
37+
type ComponentPropsWithoutRef<T extends React.ElementType> = React.ComponentPropsWithoutRef<T>
38+
type DialogRootProps = {
39+
children: ReactNode
40+
open?: boolean
41+
onOpenChange?: (details: { open: boolean }) => void
42+
} & ComponentPropsWithoutRef<"div">
43+
type DialogTriggerProps = {
44+
children: ReactNode
45+
isOpen?: boolean
46+
onOpen?: () => void
47+
} & ComponentPropsWithoutRef<"button">
48+
type DialogContentProps = {
49+
children: ReactNode
50+
portalled?: boolean
51+
backdrop?: boolean
52+
ref?: ForwardedRef<HTMLDivElement>
53+
} & ComponentPropsWithoutRef<"div">
54+
type CloseButtonProps = { children?: ReactNode } & ComponentPropsWithoutRef<"button">
55+
type DialogCloseTriggerProps = {
56+
children?: ReactNode
57+
asChild?: boolean
58+
insetEnd?: string
59+
top?: string
60+
position?: string
61+
ref?: ForwardedRef<HTMLButtonElement>
62+
} & ComponentPropsWithoutRef<"button">
63+
type DialogContextType = { onClose: () => void; isOpen: boolean }
64+
interface RefElement extends React.ReactElement {
65+
ref?: ForwardedRef<HTMLButtonElement>
66+
}
67+
// endregion
68+
69+
const DialogContext = createContext<DialogContextType | undefined>(undefined)
70+
const ThemeContext = createContext({ theme: { _config: {} } })
71+
72+
// region Mock Components (Copied EXACTLY from original TestSetup)
73+
const MockCloseButton = React.forwardRef<HTMLButtonElement, CloseButtonProps>(({ children, ...props }, ref) => (
74+
<button ref={ref} data-testid="close-button" {...props}>
75+
{children}
76+
</button>
77+
))
78+
79+
const MockDialogCloseTrigger = React.forwardRef<HTMLButtonElement, DialogCloseTriggerProps>(
80+
({ children, asChild, insetEnd, top, position, ...props }, ref) => {
81+
const context = useContext(DialogContext)
82+
const buttonProps = { ...props, position, top, insetend: insetEnd, "data-testid": "close-button" }
83+
84+
if (asChild && React.isValidElement(children)) {
85+
return React.cloneElement(children, {
86+
...buttonProps,
87+
// @ts-ignore
88+
ref,
89+
onClick: (e: React.MouseEvent<HTMLButtonElement>) => {
90+
if (children.props.onClick) children.props.onClick(e)
91+
if (!e.defaultPrevented) context?.onClose?.()
92+
},
93+
})
94+
}
95+
return (
96+
<MockCloseButton
97+
ref={ref}
98+
onClick={(e) => {
99+
if (!e.defaultPrevented) context?.onClose?.()
100+
}}
101+
{...buttonProps}
102+
>
103+
{children}
104+
</MockCloseButton>
105+
)
106+
},
107+
)
108+
// endregion
29109

110+
return {
111+
...(await vi.importActual<typeof import("@chakra-ui/react")>("@chakra-ui/react")),
112+
ChakraProvider: ({ children }: { children: ReactNode }): React.ReactElement => (
113+
<ThemeContext.Provider value={{ theme: { _config: {} } }}>{children}</ThemeContext.Provider>
114+
),
115+
Portal: ({ children, disabled }: { children: ReactNode; disabled?: boolean }): React.ReactElement =>
116+
disabled ? <>{children}</> : <div data-testid="portal">{children}</div>,
117+
CloseButton: MockCloseButton,
118+
IconButton: React.forwardRef<HTMLButtonElement, ComponentPropsWithoutRef<"button">>(
119+
(props, ref): React.ReactElement => <button data-testid="icon-button" ref={ref} {...props} />,
120+
),
121+
defineRecipe: vi.fn(() => ({})),
122+
Dialog: {
123+
Root: ({ children, open, onOpenChange, ...props }: DialogRootProps) => {
124+
const [isOpen, setIsOpen] = useState(open ?? false)
125+
const isControlled = open !== undefined
126+
const effectiveOpen = isControlled ? open : isOpen
127+
const handleOpenChange = (newOpen: boolean) => {
128+
if (!isControlled) setIsOpen(newOpen)
129+
onOpenChange?.({ open: newOpen })
130+
}
131+
return (
132+
<div data-testid="dialog-root" {...props}>
133+
<DialogContext.Provider value={{ onClose: () => handleOpenChange(false), isOpen: effectiveOpen }}>
134+
{React.Children.map(children, (child) => {
135+
if (!React.isValidElement(child)) return child
136+
const childType =
137+
typeof child.type !== "string" && "displayName" in child.type ? child.type.displayName : undefined
138+
if (childType === "DialogTrigger") {
139+
return React.cloneElement(child as React.ReactElement<DialogTriggerProps>, {
140+
isOpen: effectiveOpen,
141+
onOpen: () => handleOpenChange(true),
142+
})
143+
}
144+
if (childType === "DialogCloseTrigger") {
145+
const closeTriggerChild = child as RefElement
146+
return <MockDialogCloseTrigger ref={closeTriggerChild.ref} asChild {...closeTriggerChild.props} />
147+
}
148+
if (childType === "DialogContent" && !effectiveOpen) return null
149+
return child
150+
})}
151+
</DialogContext.Provider>
152+
</div>
153+
)
154+
},
155+
Trigger: ({ children, isOpen, onOpen, ...props }: DialogTriggerProps) => (
156+
<button data-testid="dialog-trigger" onClick={() => !isOpen && onOpen?.()} {...props}>
157+
{children}
158+
</button>
159+
),
160+
Positioner: ({ children }: { children: ReactNode }) => <div data-testid="dialog-positioner">{children}</div>,
161+
Backdrop: (props: ComponentPropsWithoutRef<"div">) => <div data-testid="dialog-backdrop" {...props} />,
162+
Content: React.forwardRef<HTMLDivElement, DialogContentProps>(({ portalled, backdrop, ...props }, ref) => (
163+
<div data-testid="dialog-content" ref={ref} {...props} />
164+
)),
165+
CloseTrigger: MockDialogCloseTrigger,
166+
Footer: (props: ComponentPropsWithoutRef<"div">) => <div data-testid="dialog-footer" {...props} />,
167+
Header: (props: ComponentPropsWithoutRef<"div">) => <div data-testid="dialog-header" {...props} />,
168+
Body: (props: ComponentPropsWithoutRef<"div">) => <div data-testid="dialog-body" {...props} />,
169+
Title: (props: ComponentPropsWithoutRef<"h2">) => <h2 data-testid="dialog-title" {...props} />,
170+
Description: (props: ComponentPropsWithoutRef<"p">) => <p data-testid="dialog-description" {...props} />,
171+
ActionTrigger: (props: ComponentPropsWithoutRef<"button">) => (
172+
<button data-testid="dialog-action-trigger" {...props} />
173+
),
174+
},
175+
}
176+
})
30177
// endregion
31178

32179
// region Type Aliases
@@ -188,7 +335,9 @@ describe("Dialog", (): void => {
188335
render(
189336
<Wrapper>
190337
<DialogRoot open>
191-
<DialogCloseTrigger data-testid="close-trigger">Close</DialogCloseTrigger>
338+
<DialogContent>
339+
<DialogCloseTrigger data-testid="close-trigger">Close</DialogCloseTrigger>
340+
</DialogContent>
192341
</DialogRoot>
193342
</Wrapper>,
194343
)
@@ -358,4 +507,5 @@ describe("Dialog", (): void => {
358507
expect(actionTrigger).toHaveTextContent("Action")
359508
})
360509
})
510+
361511
// endregion

0 commit comments

Comments
 (0)