Skip to content

Commit

Permalink
검색에 관한 설정과 전체 게시글 초기 설정 변경 (#547)
Browse files Browse the repository at this point in the history
* feat: (#530) 페이지 진입 시 전체, 최신순으로 정렬 옵션이 설정되도록 수정

* feat: (#534) 아무런 값이 없으면 검색이 되지 않도록 구현
앞 뒤 공백을 제거하여 검색하도록 구현
검색어를 검색창에 남기도록 수정

* fix: (#530) 왼쪽 카테고리 탭에서 검색어를 보여주던 기능 삭제

* feat: (#530) 검색어와 공백을 제거한 검색어가 다를 때 공백을 제거한 검색어로 state값 변경

* feat: (#530) 검색어 자동완성 기능 해제

* fix: (#530) onKeywordChange에서 handleKeywordChange로 이름 변경

* fix: (#530) onSearchSubmit에서 handleSearchSubmit로 이름 변경

* refactor: (#530) 검색어에서 중간의 공백이 많은 경우도 고려하여 중간 공백 제거 기능 구현

* feat: (#530) 검색어 100글자까지 입력 가능하도록 설정

* refactor: (#530) 기존 hook을 이용하여 기능이 동작하도록 코드 변경
  • Loading branch information
Gilpop8663 authored Sep 12, 2023
1 parent 6663251 commit 0be300b
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 81 deletions.
46 changes: 0 additions & 46 deletions frontend/__test__/getSelectedState.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ describe('getSelectedState 사용했을 때 현재 유저에게 어떤 게시글
const state: SelectedState = {
postType: 'category',
categoryId,
keyword: '',
categoryList: MOCK_CATEGORY_LIST,
};

Expand All @@ -18,53 +17,10 @@ describe('getSelectedState 사용했을 때 현재 유저에게 어떤 게시글
expect(result).toBe(MOCK_CATEGORY_LIST[0].name);
});

test('현재 검색을 한 상태이면, 검색 키워드를 반환한다.', () => {
const keyword = '갤럭시';
const state: SelectedState = {
postType: 'search',
categoryId: 0,
keyword,
categoryList: MOCK_CATEGORY_LIST,
};

const result = getSelectedState(state);

expect(result).toBe(keyword);
});

test('검색어를 길게 설정한 경우 10자만 보여주고 ...으로 표시해서 보여준다.', () => {
const keyword = '아이폰갤럭시뉴진스아이브세븐틴슈퍼주니어임';
const state: SelectedState = {
postType: 'search',
categoryId: 0,
keyword,
categoryList: MOCK_CATEGORY_LIST,
};

const result = getSelectedState(state);

expect(result).toBe('아이폰갤럭시뉴진스아...');
});

test('검색어를 10글자인 경우 10글자 전부 표시해서 보여준다.', () => {
const keyword = '아이폰갤럭시뉴진스아';
const state: SelectedState = {
postType: 'search',
categoryId: 0,
keyword,
categoryList: MOCK_CATEGORY_LIST,
};

const result = getSelectedState(state);

expect(result).toBe('아이폰갤럭시뉴진스아');
});

test('현재 홈 화면에 있다면, "전체"를 반환한다', () => {
const state: SelectedState = {
postType: 'posts',
categoryId: 0,
keyword: '',
categoryList: MOCK_CATEGORY_LIST,
};

Expand All @@ -77,7 +33,6 @@ describe('getSelectedState 사용했을 때 현재 유저에게 어떤 게시글
const state: SelectedState = {
postType: 'myPost',
categoryId: 0,
keyword: '',
categoryList: MOCK_CATEGORY_LIST,
};

Expand All @@ -90,7 +45,6 @@ describe('getSelectedState 사용했을 때 현재 유저에게 어떤 게시글
const state: SelectedState = {
postType: 'myVote',
categoryId: 0,
keyword: '',
categoryList: MOCK_CATEGORY_LIST,
};

Expand Down
15 changes: 15 additions & 0 deletions frontend/__test__/getTrimmedWord.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { getTrimmedWord } from '@utils/getTrimmedWord';

test.each([
['검색어 입니다', '검색어 입니다'],
[' 완전히 갤럭시 임 ', '완전히 갤럭시 임'],
[' ', ''],
['', ''],
])(
'getTrimmedWord 함수에서 단어를 입력했을 때 중복된 공백을 제거한 단어를 반환한다.',
(word, expectedWord) => {
const result = getTrimmedWord(word);

expect(result).toBe(expectedWord);
}
);
41 changes: 41 additions & 0 deletions frontend/__test__/hooks/useSearch.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';

import { fireEvent, render, renderHook, screen } from '@testing-library/react';

import { useSearch } from '@hooks/useSearch';

describe('useSearch 훅이 검색을 하는지 확인한다.', () => {
test('초기 값이 없다면 keyword는 빈 문자열이다.', () => {
const { result } = renderHook(() => useSearch(), { wrapper: MemoryRouter });

const { keyword } = result.current;

expect(keyword).toBe('');
});

test('초기 값이 있다면 keyword 값에 설정된다.', () => {
const KEYWORD = '갤럭시';

const { result } = renderHook(() => useSearch(KEYWORD), { wrapper: MemoryRouter });

const { keyword } = result.current;

expect(keyword).toBe(KEYWORD);
});

test('onChange 이벤트를 한다면 keyword 값에 설정된다.', () => {
const KEYWORD = '갤럭시';

const { result } = renderHook(() => useSearch(KEYWORD), { wrapper: MemoryRouter });
const { keyword, handleKeywordChange } = result.current;

render(<input value={keyword} aria-label="search-input" onChange={handleKeywordChange} />);

const input = screen.getByLabelText('search-input');

fireEvent.change(input, { target: { value: KEYWORD } });

expect(result.current.keyword).toBe(KEYWORD);
});
});
2 changes: 1 addition & 1 deletion frontend/__test__/hooks/useSelect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { PostStatus } from '@components/post/PostListPage/types';
const INIT_SELECTED_OPTION = 'progress';
const CHANGE_SELECTED_OPTION = 'closed';

describe('usePostList 훅이 전체 게시글 목록을 불러오는지 확인한다', () => {
describe('useSelect 훅이 전체 게시글 목록을 불러오는지 확인한다', () => {
test('초기 값이 설정 되었는지 확인한다.', () => {
const { result } = renderHook(() => useSelect<PostStatus>(INIT_SELECTED_OPTION));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ export default function CategorySection() {
const categoryListFallback = categoryList ?? [];

const { postOptionalOption, postType } = usePostRequestInfo();
const { categoryId, keyword } = postOptionalOption;
const { categoryId } = postOptionalOption;

const selectedState = getSelectedState({
categoryId,
keyword,
categoryList: categoryListFallback,
postType,
});
Expand Down
16 changes: 14 additions & 2 deletions frontend/src/components/common/SearchBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { HTMLAttributes } from 'react';

import { Size } from '@type/style';

import { useCurrentKeyword } from '@hooks/useCurrentKeyword';
import { useSearch } from '@hooks/useSearch';

import { PATH } from '@constants/path';
import { SEARCH_KEYWORD } from '@constants/post';
import { SEARCH_KEYWORD, SEARCH_KEYWORD_MAX_LENGTH } from '@constants/post';

import searchIcon from '@assets/search_black.svg';

Expand All @@ -15,11 +18,20 @@ interface SearchBarProps extends HTMLAttributes<HTMLInputElement> {
}

export default function SearchBar({ size, isOpen, ...rest }: SearchBarProps) {
const { currentKeyword } = useCurrentKeyword();
const { keyword, handleKeywordChange, handleSearchSubmit, searchInputRef } =
useSearch(currentKeyword);

return (
<S.Form size={size} action={PATH.SEARCH}>
<S.Form size={size} action={PATH.SEARCH} onSubmit={handleSearchSubmit}>
<S.Input
ref={searchInputRef}
maxLength={SEARCH_KEYWORD_MAX_LENGTH + 1}
aria-label="게시글 제목 및 내용 검색창"
type="search"
value={keyword}
onChange={handleKeywordChange}
autoComplete="off"
name={SEARCH_KEYWORD}
{...rest}
/>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/context/postOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface PostOptionContextProps {
export default function PostOptionProvider({ children }: PropsWithChildren) {
const [postOption, setPostOption] = useState<PostOption>({
sorting: SORTING.LATEST,
status: STATUS.PROGRESS,
status: STATUS.ALL,
});

return (
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/hooks/useCurrentKeyword.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useSearchParams } from 'react-router-dom';

import { DEFAULT_KEYWORD, SEARCH_KEYWORD, SEARCH_KEYWORD_MAX_LENGTH } from '@constants/post';

import { getTrimmedWord } from '@utils/getTrimmedWord';

export const useCurrentKeyword = () => {
const [searchParams] = useSearchParams();
const currentKeyword =
searchParams.get(SEARCH_KEYWORD)?.toString().slice(0, SEARCH_KEYWORD_MAX_LENGTH) ??
DEFAULT_KEYWORD;

return {
currentKeyword:
currentKeyword !== DEFAULT_KEYWORD ? getTrimmedWord(currentKeyword) : currentKeyword,
};
};
20 changes: 7 additions & 13 deletions frontend/src/hooks/usePostRequestInfo.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { useLocation, useParams, useSearchParams } from 'react-router-dom';
import { useLocation, useParams } from 'react-router-dom';

import { PostRequestKind } from '@components/post/PostListPage/types';

import { PATH } from '@constants/path';
import {
DEFAULT_CATEGORY_ID,
DEFAULT_KEYWORD,
POST_TYPE,
SEARCH_KEYWORD,
SEARCH_KEYWORD_MAX_LENGTH,
} from '@constants/post';
import { DEFAULT_CATEGORY_ID, POST_TYPE } from '@constants/post';

import { getPathFragment } from '@utils/getPathFragment';

import { useCurrentKeyword } from './useCurrentKeyword';

const REQUEST_URL: Record<string, PostRequestKind> = {
[PATH.HOME]: POST_TYPE.ALL,
[PATH.POST_CATEGORY]: POST_TYPE.CATEGORY,
Expand All @@ -23,19 +19,17 @@ const REQUEST_URL: Record<string, PostRequestKind> = {

export const usePostRequestInfo = () => {
const params = useParams<{ categoryId?: string }>();
const [searchParams] = useSearchParams();
const { currentKeyword } = useCurrentKeyword();
const { pathname } = useLocation();

const categoryId = Number(params.categoryId ?? DEFAULT_CATEGORY_ID);
const keyword =
searchParams.get(SEARCH_KEYWORD)?.toString().slice(0, SEARCH_KEYWORD_MAX_LENGTH) ??
DEFAULT_KEYWORD;

const convertedPathname = getPathFragment(pathname);
const postType = REQUEST_URL[convertedPathname];

const postOptionalOption = {
categoryId,
keyword,
keyword: currentKeyword,
};

if (!postType) {
Expand Down
42 changes: 42 additions & 0 deletions frontend/src/hooks/useSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ChangeEvent, FormEvent, useRef } from 'react';
import { useNavigate } from 'react-router-dom';

import { SEARCH_KEYWORD_MAX_LENGTH } from '@constants/post';

import { getTrimmedWord } from '@utils/getTrimmedWord';

import { useText } from './useText';

export const useSearch = (initialKeyword = '') => {
const navigate = useNavigate();
const searchInputRef = useRef<HTMLInputElement>(null);
const { text: keyword, setText: setKeyword, handleTextChange } = useText(initialKeyword);

const handleKeywordChange = (event: ChangeEvent<HTMLInputElement>) => {
if (!searchInputRef.current) return;

handleTextChange(event, { MAX_LENGTH: SEARCH_KEYWORD_MAX_LENGTH, MIN_LENGTH: 0 });
};

const handleSearchSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();

if (!searchInputRef.current) return;

const trimmedKeyword = getTrimmedWord(keyword);

if (keyword !== trimmedKeyword) {
setKeyword(trimmedKeyword);
}

if (trimmedKeyword === '') {
searchInputRef.current.setCustomValidity('검색어를 입력해주세요');
searchInputRef.current.reportValidity();
return;
}

navigate(`/search?keyword=${trimmedKeyword}`);
};

return { keyword, handleKeywordChange, handleSearchSubmit, searchInputRef };
};
2 changes: 1 addition & 1 deletion frontend/src/hooks/useText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ export const useText = (originalText: string) => {
setText('');
};

return { text, handleTextChange, resetText };
return { text, setText, handleTextChange, resetText };
};
7 changes: 7 additions & 0 deletions frontend/src/utils/getTrimmedWord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const getTrimmedWord = (word: string) => {
return word
.split(' ')
.map(word => word.trim())
.filter(word => word !== '')
.join(' ');
};
16 changes: 1 addition & 15 deletions frontend/src/utils/post/getSelectedState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,16 @@ import { PostRequestKind } from '@components/post/PostListPage/types';
export interface SelectedState {
postType: PostRequestKind;
categoryId: number;
keyword: string;
categoryList: Category[];
}

const SLICED_LENGTH_NUMBER = 10;

export const getSelectedState = ({
postType,
categoryId,
keyword,
categoryList,
}: SelectedState) => {
export const getSelectedState = ({ postType, categoryId, categoryList }: SelectedState) => {
if (postType === 'category') {
const selectedCategory = categoryList.find(category => category.id === categoryId);

return selectedCategory?.name ?? '전체';
}

if (postType === 'search') {
return keyword.length > SLICED_LENGTH_NUMBER
? `${keyword.slice(0, SLICED_LENGTH_NUMBER)}...`
: keyword;
}

if (postType === 'myPost') {
return '내가 작성한 글';
}
Expand Down

0 comments on commit 0be300b

Please sign in to comment.