diff --git a/public/icons/SearchIcon.tsx b/public/icons/SearchIcon.tsx new file mode 100644 index 00000000..81a0d250 --- /dev/null +++ b/public/icons/SearchIcon.tsx @@ -0,0 +1,28 @@ +import { SVGProps } from 'react'; + +interface SearchIconProps extends SVGProps { + width?: number; + height?: number; +} + +function SearchIcon({ width = 12, height = 12, ...props }: SearchIconProps) { + return ( + + + + ); +} + +export default SearchIcon; diff --git a/src/components/search-box/SearchBox.stories.tsx b/src/components/search-box/SearchBox.stories.tsx new file mode 100644 index 00000000..d4a9446c --- /dev/null +++ b/src/components/search-box/SearchBox.stories.tsx @@ -0,0 +1,22 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import SearchBox from './SearchBox'; + +const meta = { + title: 'Components/SearchBox', + component: SearchBox, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + value: '', + onChange: (e) => console.log('Search value:', e.target.value), + placeholder: '검색어를 입력해주세요', + }, +}; diff --git a/src/components/search-box/SearchBox.test.tsx b/src/components/search-box/SearchBox.test.tsx new file mode 100644 index 00000000..5e990f2b --- /dev/null +++ b/src/components/search-box/SearchBox.test.tsx @@ -0,0 +1,44 @@ +import '@testing-library/jest-dom'; +import { render, screen, fireEvent } from '@testing-library/react'; +import SearchBox from './SearchBox'; + +describe('SearchBox', () => { + const mockOnChange = jest.fn(); + const defaultProps = { + value: '', + onChange: mockOnChange, + }; + + beforeEach(() => { + mockOnChange.mockClear(); + }); + + it('기본 placeholder 텍스트가 올바르게 렌더링되는지 확인', () => { + render(); + expect( + screen.getByPlaceholderText('검색어를 입력해주세요'), + ).toBeInTheDocument(); + }); + + it('사용자 정의 placeholder가 올바르게 렌더링되는지 확인', () => { + const customPlaceholder = 'placeholder 테스트'; + render(); + expect(screen.getByPlaceholderText(customPlaceholder)).toBeInTheDocument(); + }); + + it('입력값이 변경될 때 onChange 핸들러가 호출되는지 확인', () => { + render(); + const input = screen.getByRole('textbox'); + + fireEvent.change(input, { target: { value: '테스트 검색어' } }); + expect(mockOnChange).toHaveBeenCalledTimes(1); + }); + + it('초기 value prop이 올바르게 설정되는지 확인', () => { + const initialValue = '초기 검색어'; + render(); + + const input = screen.getByRole('textbox') as HTMLInputElement; + expect(input.value).toBe(initialValue); + }); +}); diff --git a/src/components/search-box/SearchBox.tsx b/src/components/search-box/SearchBox.tsx new file mode 100644 index 00000000..93a3b78e --- /dev/null +++ b/src/components/search-box/SearchBox.tsx @@ -0,0 +1,31 @@ +import React, { ChangeEvent } from 'react'; +import SearchIcon from '../../../public/icons/SearchIcon'; + +interface SearchBoxProps { + value: string; + onChange: (e: ChangeEvent) => void; + placeholder?: string; +} + +const SearchBox = ({ + value, + onChange, + placeholder = '검색어를 입력해주세요', +}: SearchBoxProps) => { + return ( +
+
+ +
+ +
+ ); +}; + +export default SearchBox;