Skip to content
This repository was archived by the owner on May 1, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
"typescript": "4.7.4",
"web-vitals": "2.1.4"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}",
"!src/index.tsx",
"!src/test-utils/*"
]
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
Expand All @@ -47,4 +54,4 @@
"last 1 safari version"
]
}
}
}
14 changes: 9 additions & 5 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { render } from "@testing-library/react";

import { render, screen } from "@testing-library/react";
import { Provider } from "react-redux";
import App from "./App";

test("renders learn react link", () => {
render(<App />);
import { store } from "./store";
test("renders App.tsx", () => {
render(
<Provider store={store}>
<App />
</Provider>
);
});
24 changes: 24 additions & 0 deletions src/components/Todo/Todo.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { render, screen } from "@testing-library/react";
import Todo from "./Todo";
describe("<Todo />", () => {
it("should render without errors", () => {
render(<Todo title={"TODO_TITLE"} done={false} />);
//screen 으로 렌더링된 것을 접근
//getByText throws error when cannot find element
screen.getByText("TODO_TITLE"); // Implicit assertion
const doneButton = screen.getByText("Done"); // Implicit assertion
expect(doneButton).toBeInTheDocument(); // Explicit assertion
});
it("should render done mark when done is true", () => {
render(<Todo title={"TODO_TITLE"} done={true} />);
const title = screen.getByText("TODO_TITLE");
expect(title.classList.contains("done")).toBe(true);
screen.getByText("Undone");
});
it("should render undone mark when done is false", () => {
render(<Todo title={"TODO_TITLE"} done={false} />);
const title = screen.getByText("TODO_TITLE");
expect(title.classList.contains("done")).toBe(false);
screen.getByText("Done");
});
});
52 changes: 52 additions & 0 deletions src/components/TodoDetail/TodoDetail.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { MemoryRouter, Navigate, Route, Routes } from "react-router";
import { renderWithProviders } from "../../test-utils/mocks";
import TodoDetail from "./TodoDetail";
import { screen } from "@testing-library/react";
import axios from "axios";

const renderTodoDetail = () => {
//this is render
renderWithProviders(
<MemoryRouter>
<Routes>
<Route path="/todo-detail/:id" element={<TodoDetail />} />
<Route path="*" element={<Navigate to={"/todo-detail/3"} />} />
</Routes>
</MemoryRouter>,
{
preloadedState: {
todo: {
todos: [
{
id: 3,
title: "title3",
content: "content3",
done: false,
},
],
selectedTodo: null,
},
},
}
);
};

describe("<TodoDetail/>", () => {
it("should render todo", async () => {
jest.spyOn(axios, "get").mockImplementation(() => {
return Promise.resolve({
data: {
id: 3,
title: "title3",
content: "content3",
done: false,
},
});
});
//render todo detail must happen after jest mocking
renderTodoDetail();
await screen.findByText("title3");
//only findbyDisplayValue works on await
await screen.findByText("content3");
});
});
62 changes: 62 additions & 0 deletions src/containers/TodoList/NewTodo/NewTodo.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { fireEvent, screen, waitFor } from "@testing-library/react";
import axios from "axios";
import * as todoSlice from "../../../store/slices/todo";
import { getMockStore, renderWithProviders } from "../../../test-utils/mocks";
import NewTodo from "./NewTodo";

const mockNavigate = jest.fn();

jest.mock("react-router", () => ({
//그래야 NavLink 같은 걸 쓸 수 있다.
...jest.requireActual("react-router"),
Navigate: (props: any) => {
//we need to check navigate to
mockNavigate(props.to);
return null;
},
useNavigate: () => mockNavigate,
}));

describe("the test of NewTodo", () => {
//branch submitted
beforeEach(() => {
jest.clearAllMocks();
});
it("should render NewTodo", () => {
renderWithProviders(<NewTodo />);
screen.getByText("Add a Todo");
});
it("should change inputs on enter", async () => {
//this changes the dispatch return value
axios.post = jest.fn().mockResolvedValueOnce({
//this is the result value
data: {
id: 1,
title: "안녕하세요",
content: "내용 채우기",
done: false,
},
});
renderWithProviders(<NewTodo />);
const titleInput = screen.getByLabelText("Title");
const contentInput = screen.getByLabelText("Content");
const submitButton = screen.getByText("Submit");
fireEvent.change(titleInput, { target: { value: "안녕하세요" } });
fireEvent.change(contentInput, { target: { value: "내용 채우기" } });
await screen.findByDisplayValue("안녕하세요"); //test setstate
await screen.findByDisplayValue("내용 채우기"); //test setstate
fireEvent.click(submitButton);
//this waits for dispatch being called
await waitFor(() => expect(mockNavigate).toHaveBeenCalledWith("/todos"));
});
it("should alert when inputs are empty", async () => {
const mockPostTodo = jest.spyOn(todoSlice, "postTodo");
window.alert = jest.fn();
console.error = jest.fn();
axios.post = jest.fn().mockResolvedValueOnce(new Error());
renderWithProviders(<NewTodo />);
const submitButton = screen.getByText("Submit");
fireEvent.click(submitButton);
await waitFor(() => expect(window.alert).toBeCalled());
});
});
20 changes: 10 additions & 10 deletions src/containers/TodoList/NewTodo/NewTodo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,6 @@ export default function NewTodo() {
const [submitted, setSubmitted] = useState<boolean>(false);
const dispatch = useDispatch<AppDispatch>();

// const navigate = useNavigate()
// const postTodoHandler = () => {
// const data = { title: title, content: content };
// alert("Submitted\n" + data.title + "\n" + data.content);
// setSubmitted(true);
// navigate('/todos')
// };

const postTodoHandler = async () => {
const data = { title: title, content: content };
const result = await dispatch(postTodo(data));
Expand All @@ -38,11 +30,19 @@ export default function NewTodo() {
<h1>Add a Todo</h1>
<label>
Title
<input type="text" value={title} onChange={(event) => setTitle(event.target.value)} />
<input
type="text"
value={title}
onChange={(event) => setTitle(event.target.value)}
/>
</label>
<label>
Content
<textarea rows={4} value={content} onChange={(event) => setContent(event.target.value)} />
<textarea
rows={4}
value={content}
onChange={(event) => setContent(event.target.value)}
/>
</label>
<button onClick={() => postTodoHandler()}>Submit</button>
</div>
Expand Down
123 changes: 123 additions & 0 deletions src/containers/TodoList/TodoList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// containers/TodoList/TodoList.test.tsx
import { IProps as TodoProps } from "../../components/Todo/Todo";
import { fireEvent, render, screen } from "@testing-library/react";
import { Provider } from "react-redux";
import { MemoryRouter, Route, Routes } from "react-router";
import { getMockStore } from "../../test-utils/mocks";
import TodoList from "./TodoList";
import { TodoState } from "../../store/slices/todo";

jest.mock("../../components/Todo/Todo", () => (props: TodoProps) => (
//this data-testid is fake id
<div data-testid="spyTodo">
<div className="title" onClick={props.clickDetail}>
{props.title}
</div>
<button className="doneButton" onClick={props.clickDone}>
done
</button>
<button className="deleteButton" onClick={props.clickDelete}>
delete
</button>
</div>
));

const stubInitialState: TodoState = {
todos: [
{
id: 1,
title: "TODO_TEST_TITLE_1",
content: "TODO_TEST_CONTENT_1",
done: false,
},
{
id: 2,
title: "TODO_TEST_TITLE_2",
content: "TODO_TEST_CONTENT_2",
done: false,
},
{
id: 3,
title: "TODO_TEST_TITLE_3",
content: "TODO_TEST_CONTENT_3",
done: false,
},
],
selectedTodo: null,
};
const mockStore = getMockStore({ todo: stubInitialState });

const mockNavigate = jest.fn();

//외부 dependency useNavigate
jest.mock("react-router", () => ({
//그래야 NavLink 같은 걸 쓸 수 있다.
...jest.requireActual("react-router"),
useNavigate: () => mockNavigate,
}));
const mockDispatch = jest.fn();

//useDispatch mockign
jest.mock("react-redux", () => ({
...jest.requireActual("react-redux"),
//useDispatch만 우리가 mocking
useDispatch: () => mockDispatch,
}));

describe("<TodoList />", () => {
let todoList: JSX.Element;
beforeEach(() => {
//mock들을 다 지우기
jest.clearAllMocks();
todoList = (
<Provider store={mockStore}>
<MemoryRouter>
<Routes>
<Route
path="/"
element={<TodoList title="TODOLIST_TEST_TITLE" />}
/>
</Routes>
</MemoryRouter>
</Provider>
);
});
it("should render TodoList", () => {
const { container } = render(todoList);
expect(container).toBeTruthy();
});
it("should render todos", () => {
render(todoList);
const todos = screen.getAllByTestId("spyTodo");
//initailly 3개 넣기로 했기 때문에 3개
expect(todos).toHaveLength(3);
});
it("should handle clickDetail", () => {
render(todoList);
const todos = screen.getAllByTestId("spyTodo");
const todo = todos[0];
// eslint-disable-next-line testing-library/no-node-access
const title = todo.querySelector(".title");
//querySelector는 class name을 이용해서 받음
fireEvent.click(title!);
expect(mockNavigate).toHaveBeenCalledTimes(1);
});
it("should handle clickDone", () => {
render(todoList);
const todos = screen.getAllByTestId("spyTodo");
const todo = todos[0];
// eslint-disable-next-line testing-library/no-node-access
const doneButton = todo.querySelector(".doneButton");
fireEvent.click(doneButton!);
expect(mockDispatch).toHaveBeenCalled();
});
it("should handle clickDelete", () => {
render(todoList);
const todos = screen.getAllByTestId("spyTodo");
const todo = todos[0];
// eslint-disable-next-line testing-library/no-node-access
const deleteButton = todo.querySelector(".deleteButton");
fireEvent.click(deleteButton!);
expect(mockDispatch).toHaveBeenCalled();
});
});
Loading