Skip to content

Commit 77ffe27

Browse files
committed
๐Ÿงช ๋žœ๋”ฉํŽ˜์ด์ง€ ๊ฒ€์ƒ‰ ๊ด€๋ จ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€
1 parent 8903cf0 commit 77ffe27

2 files changed

Lines changed: 189 additions & 1 deletion

File tree

โ€Ž.storybook/preview.tsxโ€Ž

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { implAuthUsecase } from '../src/feature/auth/usecase/auth-usecase';
1010
import { QueryContext } from '../src/feature/shared/context/query-context';
1111
import { TokenContext } from '../src/feature/shared/context/token-context';
1212
import { UsecaseContext } from '../src/feature/shared/context/usecase-context';
13+
import { useStoreQuery } from '../src/feature/store/application/store-query';
14+
import { implStoreUsecase } from '../src/feature/store/usecase/store-usecase';
1315
import { externalCall, implApi } from '../src/infrastructure/api';
1416
import { implTokenRepository } from '../src/infrastructure/token/token-repository';
1517
import { handlers } from '../src/mocks/handlers';
@@ -30,9 +32,11 @@ const StoryAuthProviders = ({ children }: { children: ReactNode }) => {
3032
const tokenRepository = implTokenRepository({ setToken });
3133
const authUsecase = implAuthUsecase({ api, tokenRepository });
3234
const authQuery = useAuthQuery({ authUsecase });
35+
const storeUsecase = implStoreUsecase({ api });
36+
const storeQuery = useStoreQuery({ storeUsecase });
3337

3438
return (
35-
<QueryContext.Provider value={{ authQuery }}>
39+
<QueryContext.Provider value={{ authQuery, storeQuery }}>
3640
<TokenContext.Provider value={{ token }}>
3741
<UsecaseContext.Provider value={{ authUsecase }}>
3842
{children}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite';
2+
import { HttpResponse, http } from 'msw';
3+
import { useState } from 'react';
4+
import { expect, userEvent, waitFor, within } from 'storybook/test';
5+
6+
import { ReviewContext } from '@/feature/shared/context/review-context';
7+
import { StoreSearch } from '@/widgets/landing/ui/store-search';
8+
9+
const STORE_NAME = '๊ธฐ์ ˆ์ดˆํ’์™•์ˆœ๋Œ€';
10+
const EVENT_NAME = '์‹ ๊ทœ ๋ฉ”๋‰ด ๋ถˆ๋‹ญ๋ณถ์Œ๋ฉด ๋จน๊ณ  ๋ฆฌ๋ทฐ ๋‹ฌ๊ธฐ';
11+
12+
const StoreSearchWithReview = () => {
13+
const [storeId, setStoreId] = useState<string | null>(null);
14+
const [eventId, setEventId] = useState<string | null>(null);
15+
return (
16+
<ReviewContext.Provider
17+
value={{
18+
storeId,
19+
eventId,
20+
setStore: (id) => {
21+
setStoreId(id);
22+
setEventId(null);
23+
},
24+
setEvent: setEventId,
25+
}}
26+
>
27+
<StoreSearch />
28+
</ReviewContext.Provider>
29+
);
30+
};
31+
32+
const meta: Meta<typeof StoreSearchWithReview> = {
33+
title: 'Landing/StoreSearch',
34+
component: StoreSearchWithReview,
35+
};
36+
37+
export default meta;
38+
type Story = StoryObj<typeof StoreSearchWithReview>;
39+
40+
const selectStore = async (
41+
canvas: ReturnType<typeof within>,
42+
storeName = STORE_NAME
43+
) => {
44+
const storeCard = await canvas.findByText(storeName);
45+
await userEvent.click(storeCard);
46+
};
47+
48+
// โ”€โ”€โ”€ ์„ฑ๊ณต ์ผ€์ด์Šค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
49+
50+
export const Default: Story = {
51+
name: '200 ๊ฐ€๊ฒŒ ๋ชฉ๋ก ๋กœ๋“œ',
52+
play: async ({ canvasElement }) => {
53+
const canvas = within(canvasElement);
54+
await expect(canvas.findByText(STORE_NAME)).resolves.toBeInTheDocument();
55+
await expect(
56+
canvas.findByText('์–ด์ €๊ตฌ ๋ฒ ์ด์ปค๋ฆฌ')
57+
).resolves.toBeInTheDocument();
58+
},
59+
};
60+
61+
export const SelectStore: Story = {
62+
name: '200 ๊ฐ€๊ฒŒ ์„ ํƒ ํ›„ ์ด๋ฒคํŠธ ํ‘œ์‹œ',
63+
play: async ({ canvasElement }) => {
64+
const canvas = within(canvasElement);
65+
await selectStore(canvas);
66+
await expect(canvas.findByText(EVENT_NAME)).resolves.toBeInTheDocument();
67+
},
68+
};
69+
70+
export const SelectStoreAndEvent: Story = {
71+
name: '200 ๊ฐ€๊ฒŒยท์ด๋ฒคํŠธ ์„ ํƒ ํ›„ ๋‹ค์Œ ๋‹จ๊ณ„ ํ™œ์„ฑํ™”',
72+
play: async ({ canvasElement }) => {
73+
const canvas = within(canvasElement);
74+
await selectStore(canvas);
75+
const eventCard = await canvas.findByText(EVENT_NAME);
76+
await userEvent.click(eventCard);
77+
const button = canvas.getByRole('button', { name: '๋‹ค์Œ ๋‹จ๊ณ„' });
78+
expect(button).not.toBeDisabled();
79+
},
80+
};
81+
82+
export const SearchFilter: Story = {
83+
name: '200 ๊ฒ€์ƒ‰์–ด๋กœ ๊ฐ€๊ฒŒ ํ•„ํ„ฐ๋ง',
84+
play: async ({ canvasElement }) => {
85+
const canvas = within(canvasElement);
86+
await expect(
87+
canvas.findByText('์–ด์ €๊ตฌ ๋ฒ ์ด์ปค๋ฆฌ')
88+
).resolves.toBeInTheDocument();
89+
const searchInput = canvas.getByPlaceholderText('๊ฐ€๊ฒŒ ์ด๋ฆ„์„ ๊ฒ€์ƒ‰ํ•˜์„ธ์š”');
90+
await userEvent.type(searchInput, '์ˆœ๋Œ€');
91+
// 1๋‹จ๊ณ„: ๋””๋ฐ”์šด์Šค(300ms) ํ›„ ์ด์ „ ๋ชฉ๋ก์ด ์‚ฌ๋ผ์งˆ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
92+
await waitFor(
93+
() =>
94+
expect(canvas.queryByText('์–ด์ €๊ตฌ ๋ฒ ์ด์ปค๋ฆฌ')).not.toBeInTheDocument(),
95+
{ timeout: 2000 }
96+
);
97+
// 2๋‹จ๊ณ„: ์ฟผ๋ฆฌ ์™„๋ฃŒ ํ›„ ํ•„ํ„ฐ๋ง ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ (์ด ์‹œ์ ์— "๊ฒ€์ƒ‰ ์ค‘..." ์ƒํƒœ์ผ ์ˆ˜ ์žˆ์Œ)
98+
await waitFor(
99+
() => expect(canvas.getByText(STORE_NAME)).toBeInTheDocument(),
100+
{ timeout: 2000 }
101+
);
102+
expect(canvas.queryByText('์–ด์ €๊ตฌ ๋ฒ ์ด์ปค๋ฆฌ')).not.toBeInTheDocument();
103+
},
104+
};
105+
106+
export const NoSearchResults: Story = {
107+
name: '๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์—†์Œ',
108+
play: async ({ canvasElement }) => {
109+
const canvas = within(canvasElement);
110+
await canvas.findByText(STORE_NAME);
111+
const searchInput = canvas.getByPlaceholderText('๊ฐ€๊ฒŒ ์ด๋ฆ„์„ ๊ฒ€์ƒ‰ํ•˜์„ธ์š”');
112+
await userEvent.type(searchInput, '์—†๋Š”๊ฐ€๊ฒŒ์ด๋ฆ„');
113+
await expect(
114+
canvas.findByText('๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.')
115+
).resolves.toBeInTheDocument();
116+
},
117+
};
118+
119+
// โ”€โ”€โ”€ ์—๋Ÿฌ ์ผ€์ด์Šค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
120+
121+
export const ErrorGetStoresFailed: Story = {
122+
name: '500 ๊ฐ€๊ฒŒ ๋ชฉ๋ก ์กฐํšŒ ์‹คํŒจ',
123+
parameters: {
124+
msw: {
125+
handlers: [
126+
http.get('*/api/store', () =>
127+
HttpResponse.json(
128+
{ code: 'SERVER_ERROR', message: 'Internal server error' },
129+
{ status: 500 }
130+
)
131+
),
132+
],
133+
},
134+
},
135+
play: async ({ canvasElement }) => {
136+
const canvas = within(canvasElement);
137+
await expect(
138+
canvas.findByText('๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.')
139+
).resolves.toBeInTheDocument();
140+
},
141+
};
142+
143+
export const ErrorGetStoreEventsFailed: Story = {
144+
name: '404 ์ด๋ฒคํŠธ ๋ชฉ๋ก ์กฐํšŒ ์‹คํŒจ',
145+
parameters: {
146+
msw: {
147+
handlers: [
148+
// events: [] ๋กœ ๋ฐ˜ํ™˜ํ•ด fallback๋„ ๋น„์–ด์žˆ๋„๋ก ์„ค์ •
149+
http.get('*/api/store', () =>
150+
HttpResponse.json({
151+
stores: [
152+
{
153+
id: 'store-001',
154+
name: STORE_NAME,
155+
address: '์„œ์šธํŠน๋ณ„์‹œ ๊ด€์•…๊ตฌ ๋ด‰์ฒœ๋™ 1620-38',
156+
category: 'RESTAURANT',
157+
description: '์ด ์ˆœ๋Œ€๋ฅผ ๋จน์œผ๋ฉด ๊ธฐ์ ˆํ•ด์š”.',
158+
events: [],
159+
totalEventCount: 0,
160+
},
161+
],
162+
totalCount: 1,
163+
currentPage: 0,
164+
totalPages: 1,
165+
hasNext: false,
166+
})
167+
),
168+
http.get('*/api/store/:storeId/events', () =>
169+
HttpResponse.json(
170+
{ code: 'STORE_001', message: 'Store not found' },
171+
{ status: 404 }
172+
)
173+
),
174+
],
175+
},
176+
},
177+
play: async ({ canvasElement }) => {
178+
const canvas = within(canvasElement);
179+
await selectStore(canvas);
180+
await expect(
181+
canvas.findByText('๋“ฑ๋ก๋œ ์ด๋ฒคํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.')
182+
).resolves.toBeInTheDocument();
183+
},
184+
};

0 commit comments

Comments
ย (0)