Skip to content

Commit b901810

Browse files
authored
Merge pull request #32 from DaleStudy/25-button-component
25 button component
2 parents fe0d235 + a84896b commit b901810

File tree

6 files changed

+437
-87
lines changed

6 files changed

+437
-87
lines changed

bun.lockb

349 KB
Binary file not shown.

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function App() {
5454
</section>
5555
<section>
5656
<Heading level={2}>섹션 2</Heading>
57-
<Button>클릭</Button>
57+
<Button variant={"solid"}>클릭</Button>
5858
</section>
5959
</main>
6060
<footer>

src/components/Button/Button.stories.ts

Lines changed: 0 additions & 40 deletions
This file was deleted.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { Button } from "./Button";
3+
import { vstack } from "../../../styled-system/patterns";
4+
5+
export default {
6+
component: Button,
7+
parameters: {
8+
layout: "centered",
9+
},
10+
args: {
11+
children: "시작하기",
12+
variant: "solid",
13+
},
14+
} satisfies Meta<typeof Button>;
15+
16+
export const Basic: StoryObj<typeof Button> = {};
17+
18+
export const Variants: StoryObj<typeof Button> = {
19+
render: (args) => {
20+
return (
21+
<div className={vstack({ gap: "4" })}>
22+
<Button {...args} variant="solid">
23+
솔리드 버튼
24+
</Button>
25+
<Button {...args} variant="outline">
26+
아웃라인 버튼
27+
</Button>
28+
</div>
29+
);
30+
},
31+
argTypes: {
32+
children: {
33+
control: false,
34+
},
35+
variant: {
36+
control: false,
37+
},
38+
},
39+
};
40+
41+
export const Tones: StoryObj<typeof Button> = {
42+
render: (args) => {
43+
return (
44+
<div className={vstack({ gap: "4" })}>
45+
<Button {...args} tone="neutral">
46+
중립 색조
47+
</Button>
48+
<Button {...args} tone="accent">
49+
강조 색조
50+
</Button>
51+
<Button {...args} tone="danger">
52+
위험 색조
53+
</Button>
54+
<Button {...args} tone="warning">
55+
경고 색조
56+
</Button>
57+
</div>
58+
);
59+
},
60+
argTypes: {
61+
children: {
62+
control: false,
63+
},
64+
tone: {
65+
control: false,
66+
},
67+
},
68+
};
69+
70+
export const Sizes: StoryObj<typeof Button> = {
71+
render: (args) => {
72+
return (
73+
<div className={vstack({ gap: "4" })}>
74+
<Button {...args} size="sm">
75+
작은 버튼
76+
</Button>
77+
<Button {...args} size="md">
78+
중간 버튼
79+
</Button>
80+
<Button {...args} size="lg">
81+
큰 버튼
82+
</Button>
83+
</div>
84+
);
85+
},
86+
argTypes: {
87+
children: {
88+
control: false,
89+
},
90+
size: {
91+
control: false,
92+
},
93+
},
94+
};
95+
96+
export const Disabled: StoryObj<typeof Button> = {
97+
render: (args) => {
98+
return (
99+
<div className={vstack({ gap: "4" })}>
100+
<Button {...args} disabled>
101+
비활성화 버튼
102+
</Button>
103+
<Button {...args}>활성화 버튼</Button>
104+
</div>
105+
);
106+
},
107+
argTypes: {
108+
children: {
109+
control: false,
110+
},
111+
disabled: {
112+
control: false,
113+
},
114+
},
115+
};
Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,108 @@
1-
import { render, screen } from "@testing-library/react";
2-
import userEvent from "@testing-library/user-event";
3-
import { expect, describe, it, vi } from "vitest";
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 "./Button.stories";
45
import { Button } from "./Button";
56

6-
describe("<Button>", () => {
7-
it("renders a button with text", () => {
8-
render(<Button>Click</Button>);
7+
const { Basic, Variants, Tones, Sizes, Disabled } = composeStories(stories);
98

10-
expect(screen.getByRole("button")).toHaveTextContent("Click");
11-
});
9+
test("renders the button with the correct text content", () => {
10+
render(<Basic>테스트</Basic>);
1211

13-
it("calls onClick handler when clicked", async () => {
14-
const user = userEvent.setup();
15-
const handleClick = vi.fn();
12+
expect(screen.getByText("테스트")).toBeInTheDocument();
13+
});
14+
15+
test("applies the correct variant styles", () => {
16+
render(<Variants />);
17+
18+
expect(screen.getByText("솔리드 버튼")).toHaveClass("bg_bg");
19+
expect(screen.getByText("아웃라인 버튼")).toHaveClass("bd_3px_solid");
20+
});
21+
22+
test("applies the correct tone styles", () => {
23+
render(<Tones />);
24+
25+
expect(screen.getByText("중립 색조")).toHaveClass("bg_bg");
26+
expect(screen.getByText("강조 색조")).toHaveClass("bg_bg.accent");
27+
expect(screen.getByText("위험 색조")).toHaveClass("bg_bg.danger");
28+
expect(screen.getByText("경고 색조")).toHaveClass("bg_bg.warning");
29+
});
30+
31+
test("applies the correct font size based on the size prop", () => {
32+
render(<Sizes />);
33+
34+
expect(screen.getByText("작은 버튼")).toHaveClass("fs_sm");
35+
expect(screen.getByText("중간 버튼")).toHaveClass("fs_md");
36+
expect(screen.getByText("큰 버튼")).toHaveClass("fs_lg");
37+
});
38+
39+
test("applies the correct disabled styles", () => {
40+
render(<Disabled />);
1641

17-
render(<Button onClick={handleClick}>Click</Button>);
42+
expect(screen.getByText("비활성화 버튼")).toBeDisabled();
43+
expect(screen.getByText("활성화 버튼")).toBeEnabled();
44+
expect(screen.getByText("비활성화 버튼")).toHaveClass("[&:disabled]:op_0.5");
45+
});
46+
47+
test("renders a button with type='button' by default", () => {
48+
render(<Basic>Default Button</Basic>);
49+
const button = screen.getByText("Default Button");
50+
expect(button).toHaveAttribute("type", "button");
51+
});
1852

19-
await user.click(screen.getByRole("button"));
53+
test("renders a button with type='button' by default", () => {
54+
render(<Basic variant="solid">Default Button</Basic>);
55+
const button = screen.getByText("Default Button");
56+
expect(button).toHaveAttribute("type", "button");
57+
});
58+
59+
test("renders a button with type='button' when specified", () => {
60+
render(
61+
<Button type="button" variant="solid">
62+
Button Type Button
63+
</Button>
64+
);
65+
const button = screen.getByText("Button Type Button");
66+
expect(button).toHaveAttribute("type", "button");
67+
});
68+
69+
test("renders a button with type='submit' when specified", () => {
70+
render(
71+
<form>
72+
<Button type="submit" variant="solid">
73+
Submit Type Button
74+
</Button>
75+
</form>
76+
);
77+
const button = screen.getByText("Submit Type Button");
78+
expect(button).toHaveAttribute("type", "submit");
79+
});
80+
81+
test("submits the form when type='submit' button is clicked", () => {
82+
const handleSubmit = vi.fn();
83+
render(
84+
<form onSubmit={handleSubmit}>
85+
<Button type="submit" variant="solid">
86+
Submit Button
87+
</Button>
88+
</form>
89+
);
90+
91+
const submitButton = screen.getByText("Submit Button");
92+
fireEvent.click(submitButton);
93+
expect(handleSubmit).toHaveBeenCalledTimes(1);
94+
});
2095

21-
expect(handleClick).toHaveBeenCalledTimes(1);
22-
});
96+
test("does not submit the form when type='button' button is clicked", () => {
97+
const handleSubmit = vi.fn();
98+
render(
99+
<form onSubmit={handleSubmit}>
100+
<Button type="button" variant="solid">
101+
Button Type Button
102+
</Button>
103+
</form>
104+
);
105+
const buttonTypeButton = screen.getByText("Button Type Button");
106+
fireEvent.click(buttonTypeButton);
107+
expect(handleSubmit).toHaveBeenCalledTimes(0);
23108
});

0 commit comments

Comments
 (0)