From 4b400d2a7892c87e07b42c83b24047d059616733 Mon Sep 17 00:00:00 2001 From: hwookim Date: Thu, 7 Jan 2021 22:56:20 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Filter=20class=20=EA=B5=AC=ED=98=84,=20?= =?UTF-8?q?Filter=20=EA=B4=80=EB=A0=A8=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20ts=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존의 상수화된 FILTER에서 Filter 객체를 갖고 있도록 변경 및 패키지 이전 --- src/_testUtils/render.js | 2 +- src/component/TodoFilter.test.jsx | 19 --------- src/component/TodoFilter.test.tsx | 22 ++++++++++ .../{TodoFilter.jsx => TodoFilter.tsx} | 15 +++++-- src/component/TodoFilterItem.jsx | 24 ----------- ...rItem.test.jsx => TodoFilterItem.test.tsx} | 28 ++++++++----- src/component/TodoFilterItem.tsx | 32 +++++++++++++++ src/component/TodoListContainer.test.jsx | 6 +-- src/domain/Filter.ts | 41 +++++++++++++++++++ src/state/filterState.js | 2 +- src/state/todoState.js | 3 +- src/utils/filter.js | 23 ----------- 12 files changed, 130 insertions(+), 87 deletions(-) delete mode 100644 src/component/TodoFilter.test.jsx create mode 100644 src/component/TodoFilter.test.tsx rename src/component/{TodoFilter.jsx => TodoFilter.tsx} (56%) delete mode 100644 src/component/TodoFilterItem.jsx rename src/component/{TodoFilterItem.test.jsx => TodoFilterItem.test.tsx} (59%) create mode 100644 src/component/TodoFilterItem.tsx create mode 100644 src/domain/Filter.ts delete mode 100644 src/utils/filter.js diff --git a/src/_testUtils/render.js b/src/_testUtils/render.js index ba5efcf..e1fed63 100644 --- a/src/_testUtils/render.js +++ b/src/_testUtils/render.js @@ -5,7 +5,7 @@ import { filterState } from "../state/filterState"; import { render } from "@testing-library/react"; -import { FILTER } from "../utils/filter"; +import { FILTER } from "../domain/Filter"; export function renderWithRecoil( ui, diff --git a/src/component/TodoFilter.test.jsx b/src/component/TodoFilter.test.jsx deleted file mode 100644 index ecc77dc..0000000 --- a/src/component/TodoFilter.test.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; -import { render } from "@testing-library/react"; - -import TodoFilter from "./TodoFilter"; -import { FILTER } from "../utils/filter"; - -describe("TodoFilter", () => { - function renderFilter({ selected, onSelect }) { - return render(); - } - - it("render all filterState btn", () => { - const { container } = renderFilter({ selected: FILTER.ALL }); - - Object.values(FILTER).forEach((filter) => - expect(container).toHaveTextContent(filter.text), - ); - }); -}); diff --git a/src/component/TodoFilter.test.tsx b/src/component/TodoFilter.test.tsx new file mode 100644 index 0000000..d2e3b13 --- /dev/null +++ b/src/component/TodoFilter.test.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { render } from "@testing-library/react"; + +import TodoFilter, { Props } from "./TodoFilter"; +import Filter, { FILTER } from "../domain/Filter"; + +describe("TodoFilter", () => { + function renderFilter({ selected, onSelect }: Props) { + return render(); + } + + it("render all filterState btn", () => { + const { container } = renderFilter({ + selected: FILTER.ALL, + onSelect: jest.fn(), + }); + + Object.values(FILTER).forEach((filter: Filter) => + expect(container).toHaveTextContent(filter.getText()), + ); + }); +}); diff --git a/src/component/TodoFilter.jsx b/src/component/TodoFilter.tsx similarity index 56% rename from src/component/TodoFilter.jsx rename to src/component/TodoFilter.tsx index 99f56d8..20e1ab5 100644 --- a/src/component/TodoFilter.jsx +++ b/src/component/TodoFilter.tsx @@ -2,14 +2,19 @@ import React from "react"; import TodoFilterItem from "./TodoFilterItem"; -import { FILTER } from "../utils/filter"; +import Filter, { FILTER } from "../domain/Filter"; -export default function TodoFilter({ selected, onSelect }) { +export interface Props { + selected: Filter; + onSelect: Function; +} + +const TodoFilter: React.FC = ({ selected, onSelect }) => { return (
    {Object.values(FILTER).map((filter) => ( ); -} +}; + +export default TodoFilter; diff --git a/src/component/TodoFilterItem.jsx b/src/component/TodoFilterItem.jsx deleted file mode 100644 index 8b78cc6..0000000 --- a/src/component/TodoFilterItem.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; - -import { findFilterByState } from "../utils/filter"; - -export default function TodoFilterItem({ - filter: { state, text, href }, - isSelected, - onSelect, -}) { - const className = [state, isSelected ? "selected" : ""].join(" ").trim(); - - const handleSelectFilter = () => { - const filter = findFilterByState(state); - onSelect(filter); - }; - - return ( -
  • - - {text} - -
  • - ); -} diff --git a/src/component/TodoFilterItem.test.jsx b/src/component/TodoFilterItem.test.tsx similarity index 59% rename from src/component/TodoFilterItem.test.jsx rename to src/component/TodoFilterItem.test.tsx index 57b24f0..163bb56 100644 --- a/src/component/TodoFilterItem.test.jsx +++ b/src/component/TodoFilterItem.test.tsx @@ -1,12 +1,14 @@ import React from "react"; import { render, fireEvent } from "@testing-library/react"; +// @ts-ignore import context from "jest-plugin-context"; import TodoFilterItem from "./TodoFilterItem"; -import { FILTER, findFilterByState } from "../utils/filter"; +import Filter, { FILTER } from "../domain/Filter"; +import { Props } from "./TodoFilterItem"; describe("TodoFilterItem", () => { - function renderItem({ filter, isSelected, onSelect }) { + function renderItem({ filter, isSelected, onSelect }: Props) { return render( { context("when selected", () => { const filter = FILTER.ALL; const isSelected = true; + const onSelect = jest.fn(); it("render filterState btn with selected class", () => { - const { getByText } = renderItem({ filter, isSelected }); - const $btn = getByText(filter.text); + const { getByText } = renderItem({ + filter, + isSelected, + onSelect, + }); + const $btn = getByText(filter.getText()); - expect($btn).toHaveClass(filter.state); + expect($btn).toHaveClass(filter.getState()); expect($btn).toHaveClass("selected"); }); }); @@ -32,12 +39,13 @@ describe("TodoFilterItem", () => { context("when not selected", () => { const filter = FILTER.ALL; const isSelected = false; + const onSelect = jest.fn(); it("render filterState btn without selected class", () => { - const { getByText } = renderItem({ filter, isSelected }); - const $btn = getByText(filter.text); + const { getByText } = renderItem({ filter, isSelected, onSelect }); + const $btn = getByText(filter.getText()); - expect($btn).toHaveClass(filter.state); + expect($btn).toHaveClass(filter.getState()); expect($btn).not.toHaveClass("selected"); }); }); @@ -47,10 +55,10 @@ describe("TodoFilterItem", () => { const isSelected = false; const onSelect = jest.fn(); const { getByText } = renderItem({ filter, isSelected, onSelect }); - const $btn = getByText(filter.text); + const $btn = getByText(filter.getText()); fireEvent.click($btn); - expect(onSelect).toBeCalledWith(findFilterByState(filter.state)); + expect(onSelect).toBeCalledWith(Filter.findFilter(filter)); }); }); diff --git a/src/component/TodoFilterItem.tsx b/src/component/TodoFilterItem.tsx new file mode 100644 index 0000000..3ee4e70 --- /dev/null +++ b/src/component/TodoFilterItem.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import Filter from "../domain/Filter"; + +export interface Props { + filter: Filter; + isSelected: boolean; + onSelect: Function; +} + +const TodoFilterItem: React.FC = ({ filter, isSelected, onSelect }) => { + const className = [filter.getState(), isSelected ? "selected" : ""] + .join(" ") + .trim(); + + const handleSelectFilter = () => { + const selected = Filter.findFilter(filter); + onSelect(selected); + }; + + return ( +
  • + + {filter.getText()} + +
  • + ); +}; + +export default TodoFilterItem; diff --git a/src/component/TodoListContainer.test.jsx b/src/component/TodoListContainer.test.jsx index a8eb158..188f250 100644 --- a/src/component/TodoListContainer.test.jsx +++ b/src/component/TodoListContainer.test.jsx @@ -5,7 +5,7 @@ import context from "jest-plugin-context"; import { renderWithRecoil } from "../_testUtils/render"; import TodoListContainer from "./TodoListContainer"; -import { FILTER } from "../utils/filter"; +import { FILTER } from "../domain/Filter"; const INCOMPLETE_TODO = { id: 1, content: "incomplete", completed: false }; const COMPLETED_TODO = { id: 2, content: "completed", completed: true }; @@ -87,7 +87,7 @@ describe("TodoListContainer", () => { it("render only complete todo", () => { const { container, getByText } = renderContainer({ todos }); - const $completedFilterBtn = getByText(filter.text); + const $completedFilterBtn = getByText(filter.getText()); fireEvent.click($completedFilterBtn); @@ -102,7 +102,7 @@ describe("TodoListContainer", () => { it("render only complete todo", () => { const { container, getByText } = renderContainer({ todos }); - const $activeFilterBtn = getByText(filter.text); + const $activeFilterBtn = getByText(filter.getText()); fireEvent.click($activeFilterBtn); diff --git a/src/domain/Filter.ts b/src/domain/Filter.ts new file mode 100644 index 0000000..8afad8d --- /dev/null +++ b/src/domain/Filter.ts @@ -0,0 +1,41 @@ +export default class Filter { + constructor( + private state: string, + private text: string, + private href: string, + ) {} + + public static findFilter(target: Filter) { + return Object.values(FILTER).find((filter: Filter) => + filter.isSame(target), + ); + } + + private isSame(target: Filter) { + return this === target; + } + + public getState() { + return this.state; + } + + public getText() { + return this.text; + } + + public getHref() { + return this.href; + } +} + +const ALL: Filter = new Filter("all", "전체보기", "/#"); +const ACTIVE: Filter = new Filter("active", "해야할 일", "/#active"); +const COMPLETED: Filter = new Filter("completed", "완료한 일", "/#completed"); + +const FILTER = { + ALL, + ACTIVE, + COMPLETED, +}; + +export { FILTER }; diff --git a/src/state/filterState.js b/src/state/filterState.js index e86fe5d..b1adff1 100644 --- a/src/state/filterState.js +++ b/src/state/filterState.js @@ -1,5 +1,5 @@ import { atom } from "recoil"; -import { FILTER } from "../utils/filter"; +import { FILTER } from "../domain/Filter"; export const filterState = atom({ key: "filterState", diff --git a/src/state/todoState.js b/src/state/todoState.js index 8899396..76efb67 100644 --- a/src/state/todoState.js +++ b/src/state/todoState.js @@ -1,7 +1,6 @@ import { atom, selector } from "recoil"; import { filterState } from "./filterState"; - -import { FILTER } from "../utils/filter"; +import { FILTER } from "../domain/Filter"; export const todoState = atom({ key: "todoState", diff --git a/src/utils/filter.js b/src/utils/filter.js deleted file mode 100644 index 26faa57..0000000 --- a/src/utils/filter.js +++ /dev/null @@ -1,23 +0,0 @@ -const FILTER = { - ALL: { - state: "all", - text: "전체보기", - href: "/#", - }, - ACTIVE: { - state: "active", - text: "해야할 일", - href: "/#active", - }, - COMPLETED: { - state: "completed", - text: "완료한 일", - href: "/#completed", - }, -}; - -function findFilterByState(target) { - return Object.values(FILTER).find(({ state }) => state === target); -} - -export { FILTER, findFilterByState };