daleui 0.0.0 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/dependabot.yml +21 -0
- package/.github/workflows/chromatic.yml +13 -1
- package/bun.lock +2178 -9705
- package/package.json +31 -20
- package/panda.config.ts +3 -0
- package/src/components/Button/Button.stories.tsx +4 -4
- package/src/components/Button/Button.tsx +26 -27
- package/src/components/Checkbox/Checkbox.stories.tsx +129 -0
- package/src/components/Checkbox/Checkbox.test.tsx +169 -0
- package/src/components/Checkbox/Checkbox.tsx +190 -0
- package/src/components/Heading/Heading.stories.tsx +2 -2
- package/src/components/Icon/Icon.stories.tsx +3 -3
- package/src/components/Text/Text.stories.tsx +2 -2
- package/src/tokens/spacing.ts +65 -0
- package/bun.lockb +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "daleui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"repository": "github:DaleStudy/daleui",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://www.daleui.com",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"design-system",
|
|
10
|
+
"ui-components",
|
|
11
|
+
"components-library",
|
|
12
|
+
"react",
|
|
13
|
+
"storybook"
|
|
14
|
+
],
|
|
5
15
|
"scripts": {
|
|
6
16
|
"dev": "vite",
|
|
7
17
|
"build": "tsc -b && vite build",
|
|
@@ -15,42 +25,43 @@
|
|
|
15
25
|
"prepare": "panda codegen"
|
|
16
26
|
},
|
|
17
27
|
"dependencies": {
|
|
28
|
+
"@radix-ui/react-checkbox": "^1.1.4",
|
|
18
29
|
"axe-playwright": "^2.0.3",
|
|
19
30
|
"chromatic": "^11.25.1",
|
|
20
|
-
"lucide-react": "^0.
|
|
31
|
+
"lucide-react": "^0.486.0",
|
|
21
32
|
"react": "^19.0.0",
|
|
22
33
|
"react-dom": "^19.0.0"
|
|
23
34
|
},
|
|
24
35
|
"devDependencies": {
|
|
25
|
-
"@chromatic-com/storybook": "^3.2.
|
|
26
|
-
"@eslint/js": "^9.
|
|
36
|
+
"@chromatic-com/storybook": "^3.2.6",
|
|
37
|
+
"@eslint/js": "^9.23.0",
|
|
27
38
|
"@faker-js/faker": "^9.4.0",
|
|
28
|
-
"@pandacss/dev": "^0.
|
|
29
|
-
"@storybook/addon-a11y": "^8.
|
|
30
|
-
"@storybook/addon-essentials": "^8.
|
|
31
|
-
"@storybook/addon-interactions": "^8.
|
|
32
|
-
"@storybook/addon-links": "^8.
|
|
33
|
-
"@storybook/addon-themes": "^8.
|
|
34
|
-
"@storybook/blocks": "^8.
|
|
35
|
-
"@storybook/react": "^8.
|
|
36
|
-
"@storybook/react-vite": "^8.
|
|
37
|
-
"@storybook/test": "^8.
|
|
38
|
-
"@storybook/test-runner": "^0.
|
|
39
|
+
"@pandacss/dev": "^0.53.3",
|
|
40
|
+
"@storybook/addon-a11y": "^8.6.11",
|
|
41
|
+
"@storybook/addon-essentials": "^8.6.11",
|
|
42
|
+
"@storybook/addon-interactions": "^8.6.11",
|
|
43
|
+
"@storybook/addon-links": "^8.6.11",
|
|
44
|
+
"@storybook/addon-themes": "^8.6.11",
|
|
45
|
+
"@storybook/blocks": "^8.6.11",
|
|
46
|
+
"@storybook/react": "^8.6.11",
|
|
47
|
+
"@storybook/react-vite": "^8.6.11",
|
|
48
|
+
"@storybook/test": "^8.6.11",
|
|
49
|
+
"@storybook/test-runner": "^0.22.0",
|
|
39
50
|
"@testing-library/jest-dom": "^6.6.3",
|
|
40
51
|
"@testing-library/react": "^16.2.0",
|
|
41
52
|
"@testing-library/user-event": "^14.6.1",
|
|
42
53
|
"@types/react": "^19.0.8",
|
|
43
54
|
"@types/react-dom": "^19.0.3",
|
|
44
55
|
"@vitejs/plugin-react": "^4.3.4",
|
|
45
|
-
"eslint": "^9.
|
|
56
|
+
"eslint": "^9.23.0",
|
|
46
57
|
"eslint-plugin-react-hooks": "^5.1.0",
|
|
47
58
|
"eslint-plugin-react-refresh": "^0.4.18",
|
|
48
59
|
"eslint-plugin-storybook": "^0.11.2",
|
|
49
|
-
"globals": "^
|
|
50
|
-
"happy-dom": "^
|
|
51
|
-
"storybook": "^8.
|
|
60
|
+
"globals": "^16.0.0",
|
|
61
|
+
"happy-dom": "^17.4.4",
|
|
62
|
+
"storybook": "^8.6.11",
|
|
52
63
|
"typescript": "^5.7.3",
|
|
53
|
-
"typescript-eslint": "^8.
|
|
64
|
+
"typescript-eslint": "^8.28.0",
|
|
54
65
|
"vite": "^6.0.11",
|
|
55
66
|
"vite-plugin-svgr": "^4.3.0",
|
|
56
67
|
"vitest": "^3.0.4"
|
package/panda.config.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineConfig } from "@pandacss/dev";
|
|
2
2
|
import { globalCss } from "./src/styles/globalCss";
|
|
3
3
|
import { colors, semanticColors } from "./src/tokens/colors";
|
|
4
|
+
import { spacing, semanticSpacing } from "./src/tokens/spacing";
|
|
4
5
|
import {
|
|
5
6
|
textStyles,
|
|
6
7
|
fonts,
|
|
@@ -49,9 +50,11 @@ export default defineConfig({
|
|
|
49
50
|
fontSizes,
|
|
50
51
|
letterSpacings,
|
|
51
52
|
lineHeights,
|
|
53
|
+
spacing,
|
|
52
54
|
},
|
|
53
55
|
semanticTokens: {
|
|
54
56
|
colors: semanticColors,
|
|
57
|
+
spacing: semanticSpacing,
|
|
55
58
|
},
|
|
56
59
|
},
|
|
57
60
|
},
|
|
@@ -18,7 +18,7 @@ export const Basic: StoryObj<typeof Button> = {};
|
|
|
18
18
|
export const Variants: StoryObj<typeof Button> = {
|
|
19
19
|
render: (args) => {
|
|
20
20
|
return (
|
|
21
|
-
<div className={vstack({ gap: "
|
|
21
|
+
<div className={vstack({ gap: "gap.sm" })}>
|
|
22
22
|
<Button {...args} variant="solid">
|
|
23
23
|
솔리드 버튼
|
|
24
24
|
</Button>
|
|
@@ -41,7 +41,7 @@ export const Variants: StoryObj<typeof Button> = {
|
|
|
41
41
|
export const Tones: StoryObj<typeof Button> = {
|
|
42
42
|
render: (args) => {
|
|
43
43
|
return (
|
|
44
|
-
<div className={vstack({ gap: "
|
|
44
|
+
<div className={vstack({ gap: "gap.sm" })}>
|
|
45
45
|
<Button {...args} tone="neutral">
|
|
46
46
|
중립 색조
|
|
47
47
|
</Button>
|
|
@@ -70,7 +70,7 @@ export const Tones: StoryObj<typeof Button> = {
|
|
|
70
70
|
export const Sizes: StoryObj<typeof Button> = {
|
|
71
71
|
render: (args) => {
|
|
72
72
|
return (
|
|
73
|
-
<div className={vstack({ gap: "
|
|
73
|
+
<div className={vstack({ gap: "gap.sm" })}>
|
|
74
74
|
<Button {...args} size="sm">
|
|
75
75
|
작은 버튼
|
|
76
76
|
</Button>
|
|
@@ -96,7 +96,7 @@ export const Sizes: StoryObj<typeof Button> = {
|
|
|
96
96
|
export const Disabled: StoryObj<typeof Button> = {
|
|
97
97
|
render: (args) => {
|
|
98
98
|
return (
|
|
99
|
-
<div className={vstack({ gap: "
|
|
99
|
+
<div className={vstack({ gap: "gap.sm" })}>
|
|
100
100
|
<Button {...args} disabled>
|
|
101
101
|
비활성화 버튼
|
|
102
102
|
</Button>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { type HTMLAttributes } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { cva } from "../../../styled-system/css";
|
|
3
3
|
import type { Tone } from "../../tokens/colors";
|
|
4
4
|
|
|
5
5
|
type ButtonVariant = "solid" | "outline";
|
|
@@ -42,7 +42,7 @@ export const Button = ({
|
|
|
42
42
|
}: ButtonProps) => {
|
|
43
43
|
return (
|
|
44
44
|
<button
|
|
45
|
-
className={
|
|
45
|
+
className={styles({ tone, variant, size })}
|
|
46
46
|
type={type}
|
|
47
47
|
onClick={onClick}
|
|
48
48
|
disabled={disabled}
|
|
@@ -53,43 +53,42 @@ export const Button = ({
|
|
|
53
53
|
);
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
const baseStyles = {
|
|
57
|
-
appearance: "none",
|
|
58
|
-
margin: "0",
|
|
59
|
-
fontWeight: 500,
|
|
60
|
-
textAlign: "center",
|
|
61
|
-
textDecoration: "none",
|
|
62
|
-
display: "flex",
|
|
63
|
-
alignItems: "center",
|
|
64
|
-
justifyContent: "center",
|
|
65
|
-
width: ["auto", "100%"],
|
|
66
|
-
borderRadius: "10px",
|
|
67
|
-
cursor: "pointer",
|
|
68
|
-
transition: "0.2s",
|
|
69
|
-
lineHeight: "1",
|
|
70
|
-
outline: "0",
|
|
71
|
-
"&:disabled": {
|
|
72
|
-
opacity: 0.5,
|
|
73
|
-
cursor: "not-allowed",
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
|
|
77
56
|
const styles = cva({
|
|
78
57
|
base: {
|
|
79
|
-
|
|
58
|
+
appearance: "none",
|
|
59
|
+
margin: "0",
|
|
60
|
+
fontWeight: 500,
|
|
61
|
+
textAlign: "center",
|
|
62
|
+
textDecoration: "none",
|
|
63
|
+
display: "flex",
|
|
64
|
+
alignItems: "center",
|
|
65
|
+
justifyContent: "center",
|
|
66
|
+
width: ["auto", "100%"],
|
|
67
|
+
borderRadius: "10px",
|
|
68
|
+
cursor: "pointer",
|
|
69
|
+
transition: "0.2s",
|
|
70
|
+
lineHeight: "1",
|
|
71
|
+
outline: "0",
|
|
72
|
+
"&:disabled": {
|
|
73
|
+
opacity: 0.5,
|
|
74
|
+
cursor: "not-allowed",
|
|
75
|
+
},
|
|
80
76
|
},
|
|
81
77
|
variants: {
|
|
82
78
|
size: {
|
|
83
79
|
sm: {
|
|
84
|
-
|
|
80
|
+
px: "px.sm",
|
|
81
|
+
py: "py.sm",
|
|
85
82
|
fontSize: "sm",
|
|
86
83
|
},
|
|
87
84
|
md: {
|
|
88
|
-
|
|
85
|
+
px: "px.md",
|
|
86
|
+
py: "py.md",
|
|
89
87
|
fontSize: "md",
|
|
90
88
|
},
|
|
91
89
|
lg: {
|
|
92
|
-
|
|
90
|
+
px: "px.lg",
|
|
91
|
+
py: "py.lg",
|
|
93
92
|
fontSize: "lg",
|
|
94
93
|
},
|
|
95
94
|
},
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Checkbox } from "./Checkbox";
|
|
3
|
+
import { vstack } from "../../../styled-system/patterns";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
component: Checkbox,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: "centered",
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
id: "checkbox",
|
|
12
|
+
label: "기본 체크박스",
|
|
13
|
+
},
|
|
14
|
+
} satisfies Meta<typeof Checkbox>;
|
|
15
|
+
|
|
16
|
+
export const Basic: StoryObj<typeof Checkbox> = {};
|
|
17
|
+
|
|
18
|
+
export const Tones: StoryObj<typeof Checkbox> = {
|
|
19
|
+
render: (args) => {
|
|
20
|
+
return (
|
|
21
|
+
<div className={vstack({ gap: "gap.sm" })}>
|
|
22
|
+
<Checkbox {...args} id="neutral" label="중립 색조" tone="neutral" />
|
|
23
|
+
<Checkbox {...args} id="accent" label="강조 색조" tone="accent" />
|
|
24
|
+
<Checkbox {...args} id="danger" label="위험 색조" tone="danger" />
|
|
25
|
+
<Checkbox {...args} id="warning" label="경고 색조" tone="warning" />
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
argTypes: {
|
|
30
|
+
label: {
|
|
31
|
+
control: false,
|
|
32
|
+
},
|
|
33
|
+
tone: {
|
|
34
|
+
control: false,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const States: StoryObj<typeof Checkbox> = {
|
|
40
|
+
render: (args) => {
|
|
41
|
+
return (
|
|
42
|
+
<div className={vstack({ gap: "gap.sm" })}>
|
|
43
|
+
<Checkbox {...args} id="checked" label="체크된 상태" checked={true} />
|
|
44
|
+
<Checkbox
|
|
45
|
+
{...args}
|
|
46
|
+
id="unchecked"
|
|
47
|
+
label="체크되지 않은 상태"
|
|
48
|
+
checked={false}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
},
|
|
53
|
+
argTypes: {
|
|
54
|
+
label: {
|
|
55
|
+
control: false,
|
|
56
|
+
},
|
|
57
|
+
checked: {
|
|
58
|
+
control: false,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Disabled: StoryObj<typeof Checkbox> = {
|
|
64
|
+
render: (args) => {
|
|
65
|
+
return (
|
|
66
|
+
<div className={vstack({ gap: "gap.sm" })}>
|
|
67
|
+
<Checkbox
|
|
68
|
+
{...args}
|
|
69
|
+
id="disabled-checked"
|
|
70
|
+
label="비활성화 & 체크된 상태"
|
|
71
|
+
disabled
|
|
72
|
+
checked
|
|
73
|
+
/>
|
|
74
|
+
<Checkbox
|
|
75
|
+
{...args}
|
|
76
|
+
id="disabled-unchecked"
|
|
77
|
+
label="비활성화 & 체크되지 않은 상태"
|
|
78
|
+
disabled
|
|
79
|
+
/>
|
|
80
|
+
<Checkbox {...args} id="enabled" label="활성화 상태" />
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
argTypes: {
|
|
85
|
+
label: {
|
|
86
|
+
control: false,
|
|
87
|
+
},
|
|
88
|
+
disabled: {
|
|
89
|
+
control: false,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const Required: StoryObj<typeof Checkbox> = {
|
|
95
|
+
render: (args) => {
|
|
96
|
+
return (
|
|
97
|
+
<div className={vstack({ gap: "gap.sm" })}>
|
|
98
|
+
<Checkbox {...args} id="required" label="필수 체크박스" required />
|
|
99
|
+
<Checkbox {...args} id="optional" label="선택 체크박스" />
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
argTypes: {
|
|
104
|
+
label: {
|
|
105
|
+
control: false,
|
|
106
|
+
},
|
|
107
|
+
required: {
|
|
108
|
+
control: false,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const WithValue: StoryObj<typeof Checkbox> = {
|
|
114
|
+
render: (args) => {
|
|
115
|
+
return (
|
|
116
|
+
<div className={vstack({ gap: "gap.sm" })}>
|
|
117
|
+
<Checkbox
|
|
118
|
+
{...args}
|
|
119
|
+
id="value-example"
|
|
120
|
+
label="값이 있는 체크박스"
|
|
121
|
+
value="checkbox-value"
|
|
122
|
+
onChange={(checked, value) =>
|
|
123
|
+
console.log(`체크박스 상태: ${checked}, 값: ${value}`)
|
|
124
|
+
}
|
|
125
|
+
/>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { composeStories } from "@storybook/react";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { expect, test, vi } from "vitest";
|
|
4
|
+
import * as stories from "./Checkbox.stories";
|
|
5
|
+
import { Checkbox } from "./Checkbox";
|
|
6
|
+
|
|
7
|
+
const { Basic, Tones, States, Disabled, Required } = composeStories(stories);
|
|
8
|
+
|
|
9
|
+
test("renders the checkbox with the correct label", () => {
|
|
10
|
+
render(<Basic />);
|
|
11
|
+
expect(screen.getByText("기본 체크박스")).toBeInTheDocument();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("applies the correct tone styles when checked", () => {
|
|
15
|
+
render(<Tones />);
|
|
16
|
+
|
|
17
|
+
const neutralCheckbox = screen.getByLabelText("중립 색조");
|
|
18
|
+
const accentCheckbox = screen.getByLabelText("강조 색조");
|
|
19
|
+
const dangerCheckbox = screen.getByLabelText("위험 색조");
|
|
20
|
+
const warningCheckbox = screen.getByLabelText("경고 색조");
|
|
21
|
+
|
|
22
|
+
// Simulate checking each checkbox
|
|
23
|
+
fireEvent.click(neutralCheckbox);
|
|
24
|
+
fireEvent.click(accentCheckbox);
|
|
25
|
+
fireEvent.click(dangerCheckbox);
|
|
26
|
+
fireEvent.click(warningCheckbox);
|
|
27
|
+
|
|
28
|
+
// Check for data-state attribute which indicates checked state
|
|
29
|
+
expect(neutralCheckbox).toHaveAttribute("data-state", "checked");
|
|
30
|
+
expect(accentCheckbox).toHaveAttribute("data-state", "checked");
|
|
31
|
+
expect(dangerCheckbox).toHaveAttribute("data-state", "checked");
|
|
32
|
+
expect(warningCheckbox).toHaveAttribute("data-state", "checked");
|
|
33
|
+
|
|
34
|
+
// Check for correct background colors based on tone
|
|
35
|
+
expect(neutralCheckbox).toHaveClass("[&[data-state='checked']]:bg-c_bg");
|
|
36
|
+
expect(accentCheckbox).toHaveClass(
|
|
37
|
+
"[&[data-state='checked']]:bg-c_bg.accent"
|
|
38
|
+
);
|
|
39
|
+
expect(dangerCheckbox).toHaveClass(
|
|
40
|
+
"[&[data-state='checked']]:bg-c_bg.danger"
|
|
41
|
+
);
|
|
42
|
+
expect(warningCheckbox).toHaveClass(
|
|
43
|
+
"[&[data-state='checked']]:bg-c_bg.warning"
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("renders checked and unchecked states correctly", () => {
|
|
48
|
+
render(<States />);
|
|
49
|
+
|
|
50
|
+
const checkedCheckbox = screen.getByLabelText("체크된 상태");
|
|
51
|
+
const uncheckedCheckbox = screen.getByLabelText("체크되지 않은 상태");
|
|
52
|
+
|
|
53
|
+
expect(checkedCheckbox).toHaveAttribute("data-state", "checked");
|
|
54
|
+
expect(uncheckedCheckbox).toHaveAttribute("data-state", "unchecked");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("applies the correct disabled styles", () => {
|
|
58
|
+
render(<Disabled />);
|
|
59
|
+
|
|
60
|
+
const disabledCheckedCheckbox =
|
|
61
|
+
screen.getByLabelText("비활성화 & 체크된 상태");
|
|
62
|
+
const disabledUncheckedCheckbox =
|
|
63
|
+
screen.getByLabelText("비활성화 & 체크되지 않은 상태");
|
|
64
|
+
const enabledCheckbox = screen.getByLabelText("활성화 상태");
|
|
65
|
+
|
|
66
|
+
expect(disabledCheckedCheckbox).toBeDisabled();
|
|
67
|
+
expect(disabledUncheckedCheckbox).toBeDisabled();
|
|
68
|
+
expect(enabledCheckbox).not.toBeDisabled();
|
|
69
|
+
|
|
70
|
+
// Check for opacity class that indicates disabled state
|
|
71
|
+
expect(disabledCheckedCheckbox).toHaveClass("[&:disabled]:op_0.5");
|
|
72
|
+
expect(disabledUncheckedCheckbox).toHaveClass("[&:disabled]:op_0.5");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("displays required indicator correctly", () => {
|
|
76
|
+
render(<Required />);
|
|
77
|
+
|
|
78
|
+
const requiredLabel = screen.getByText("필수 체크박스").parentElement;
|
|
79
|
+
const optionalLabel = screen.getByText("선택 체크박스");
|
|
80
|
+
|
|
81
|
+
// Check for required indicator (asterisk)
|
|
82
|
+
expect(requiredLabel).toContainHTML("*");
|
|
83
|
+
expect(optionalLabel).not.toContainHTML("*");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("calls onChange handler when checkbox is clicked", () => {
|
|
87
|
+
const handleChange = vi.fn();
|
|
88
|
+
|
|
89
|
+
render(
|
|
90
|
+
<Checkbox
|
|
91
|
+
id="test-checkbox"
|
|
92
|
+
label="테스트 체크박스"
|
|
93
|
+
onChange={handleChange}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const checkbox = screen.getByLabelText("테스트 체크박스");
|
|
98
|
+
|
|
99
|
+
// Initially unchecked
|
|
100
|
+
expect(checkbox).toHaveAttribute("data-state", "unchecked");
|
|
101
|
+
|
|
102
|
+
// Click to check
|
|
103
|
+
fireEvent.click(checkbox);
|
|
104
|
+
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
105
|
+
expect(handleChange).toHaveBeenCalledWith(true, undefined);
|
|
106
|
+
|
|
107
|
+
// Click again to uncheck
|
|
108
|
+
fireEvent.click(checkbox);
|
|
109
|
+
expect(handleChange).toHaveBeenCalledTimes(2);
|
|
110
|
+
expect(handleChange).toHaveBeenCalledWith(false, undefined);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("passes value to onChange handler when provided", () => {
|
|
114
|
+
const handleChange = vi.fn();
|
|
115
|
+
|
|
116
|
+
render(
|
|
117
|
+
<Checkbox
|
|
118
|
+
id="value-checkbox"
|
|
119
|
+
label="값이 있는 체크박스"
|
|
120
|
+
value="test-value"
|
|
121
|
+
onChange={handleChange}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const checkbox = screen.getByLabelText("값이 있는 체크박스");
|
|
126
|
+
|
|
127
|
+
// Click to check
|
|
128
|
+
fireEvent.click(checkbox);
|
|
129
|
+
expect(handleChange).toHaveBeenCalledWith(true, "test-value");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("correctly handles required attribute", () => {
|
|
133
|
+
render(
|
|
134
|
+
<Checkbox id="required-checkbox" label="필수 체크박스" required={true} />
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// 정규 표현식을 사용하여 라벨 찾기 (별표가 있어도 일치)
|
|
138
|
+
const checkbox = screen.getByRole("checkbox", { name: /필수 체크박스/ });
|
|
139
|
+
|
|
140
|
+
// aria-required 속성 확인
|
|
141
|
+
expect(checkbox).toHaveAttribute("aria-required", "true");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("toggles checked state when clicked", () => {
|
|
145
|
+
render(<Basic />);
|
|
146
|
+
|
|
147
|
+
const checkbox = screen.getByLabelText("기본 체크박스");
|
|
148
|
+
|
|
149
|
+
// Initially unchecked
|
|
150
|
+
expect(checkbox).toHaveAttribute("data-state", "unchecked");
|
|
151
|
+
|
|
152
|
+
// Click to check
|
|
153
|
+
fireEvent.click(checkbox);
|
|
154
|
+
expect(checkbox).toHaveAttribute("data-state", "checked");
|
|
155
|
+
|
|
156
|
+
// Click again to uncheck
|
|
157
|
+
fireEvent.click(checkbox);
|
|
158
|
+
expect(checkbox).toHaveAttribute("data-state", "unchecked");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("adds asterisk to label when required is true", () => {
|
|
162
|
+
render(
|
|
163
|
+
<Checkbox id="required-checkbox" label="필수 체크박스" required={true} />
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const requiredIndicator = screen.getByText("*");
|
|
167
|
+
expect(requiredIndicator).toBeInTheDocument();
|
|
168
|
+
expect(requiredIndicator).toHaveClass("c_text.danger");
|
|
169
|
+
});
|