From e86acd01137866db4bf27d5201e76994eb713f24 Mon Sep 17 00:00:00 2001 From: Hello Date: Fri, 14 Jul 2023 21:05:48 +0800 Subject: [PATCH] Feature/add frontend board redux test (#107) * test: add frontend board redux test * test: add nextFrame function --------- Co-authored-by: ChunRen Zhang --- .../app/routes/game/store/board.slice.ts | 7 +- .../app/routes/game/store/board.test.tsx | 191 ++++++++++++++++++ apps/frontend/test/utils/index.tsx | 6 + 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 apps/frontend/app/routes/game/store/board.test.tsx diff --git a/apps/frontend/app/routes/game/store/board.slice.ts b/apps/frontend/app/routes/game/store/board.slice.ts index cb6f12c..99d5a25 100644 --- a/apps/frontend/app/routes/game/store/board.slice.ts +++ b/apps/frontend/app/routes/game/store/board.slice.ts @@ -10,7 +10,8 @@ import type { RootState } from "."; const adapter = createEntityAdapter({ selectId: (placement) => Vec.id(placement.position), }); -export default createSlice({ + +const slice = createSlice({ name: "board", initialState: adapter.getInitialState(), reducers: { @@ -32,6 +33,10 @@ export default createSlice({ }, }, }); +export default slice; + +// actions +export const { add, remove } = slice.actions; // selectors const selectors = adapter.getSelectors((state) => state.board); diff --git a/apps/frontend/app/routes/game/store/board.test.tsx b/apps/frontend/app/routes/game/store/board.test.tsx new file mode 100644 index 0000000..2a41881 --- /dev/null +++ b/apps/frontend/app/routes/game/store/board.test.tsx @@ -0,0 +1,191 @@ +import { renderWithProviders, nextFrame } from "test-utils"; +import { useSelector } from "react-redux"; +import * as Board from "./board.slice"; +import { Vec } from "@packages/domain"; +import { act, screen, within } from "@testing-library/react"; +import { PathCard } from "@packages/domain"; +import { setupStore } from "."; + +function View() { + const boards = useSelector(Board.selectBoard); + return ( +
    + {boards.map((board) => ( +
  • +
      + {board.position.map((item, index) => ( +
    • {item}
    • + ))} +
    +
    {board.card}
    +
  • + ))} +
+ ); +} + +describe("board state", () => { + test("initial board state with empty", () => { + renderWithProviders(, { + store: setupStore({ + board: { + ids: [], + entities: {}, + }, + }), + }); + + expect(screen.queryAllByTestId("position")).toHaveLength(0); + expect(screen.queryAllByTestId("card")).toHaveLength(0); + }); + + test("board state with one card", async () => { + const store = setupStore({ + board: { + ids: [Vec.id([0, 0])], + entities: { + [Vec.id([0, 0])]: { + position: [0, 0], + card: PathCard.START, + }, + }, + }, + }); + renderWithProviders(, { store }); + + // verify nodes exist + const cards = screen.queryAllByTestId("card"); + const positions = screen.queryAllByTestId("position"); + + // expect to find one card + expect(cards).toHaveLength(1); + // expect to find one position + expect(positions).toHaveLength(1); + + // verify the card + expect(cards[0]).toHaveTextContent("start"); + // verify the position + const positionItems1 = within(positions[0]).queryAllByRole("listitem"); + expect(positionItems1).toHaveLength(2); + expect(positionItems1[0]).toHaveTextContent("0"); + expect(positionItems1[1]).toHaveTextContent("0"); + }); + + test("board state with multiple card", async () => { + const store = setupStore({ + board: { + ids: [Vec.id([0, 0]), Vec.id([0, 1])], + entities: { + [Vec.id([0, 0])]: { + position: [0, 0], + card: PathCard.START, + }, + [Vec.id([0, 1])]: { + position: [0, 1], + card: PathCard.CONNECTED_TOP_BOTTOM, + }, + }, + }, + }); + renderWithProviders(, { store }); + + // verify nodes exist + const cards = screen.queryAllByTestId("card"); + const positions = screen.queryAllByTestId("position"); + + // expect to find two cards + expect(cards).toHaveLength(2); + // expect to find two positions + expect(positions).toHaveLength(2); + + // verify the first card + expect(cards[0]).toHaveTextContent("start"); + // verify the first position + const positionItems1 = within(positions[0]).queryAllByRole("listitem"); + expect(positionItems1).toHaveLength(2); + expect(positionItems1[0]).toHaveTextContent("0"); + expect(positionItems1[1]).toHaveTextContent("0"); + + // verify the second card + expect(cards[1]).toHaveTextContent("connected top bottom"); + + // verify the second position + const positionItems2 = within(positions[1]).queryAllByRole("listitem"); + expect(positionItems2).toHaveLength(2); + expect(positionItems2[0]).toHaveTextContent("0"); + expect(positionItems2[1]).toHaveTextContent("1"); + }); + + test("add card to the board", async () => { + const store = setupStore({ + board: { + ids: [], + entities: {}, + }, + }); + + renderWithProviders(, { store }); + + // verify nodes is not exist + expect(screen.queryAllByTestId("card")).toHaveLength(0); + expect(screen.queryAllByTestId("position")).toHaveLength(0); + + await act(async () => { + store.dispatch( + Board.add({ + position: [0, 0], + card: "start", + }) + ); + await nextFrame(); + }); + + // verify nodes has been added + const cards = screen.queryAllByTestId("card"); + const positions = screen.queryAllByTestId("position"); + + // expect to find one card + expect(cards).toHaveLength(1); + // expect to find one position + expect(positions).toHaveLength(1); + + // verify the card + expect(cards[0]).toHaveTextContent("start"); + // verify the position + const positionItems1 = within(positions[0]).queryAllByRole("listitem"); + expect(positionItems1).toHaveLength(2); + expect(positionItems1[0]).toHaveTextContent("0"); + expect(positionItems1[1]).toHaveTextContent("0"); + }); + + test("remove card from the board", async () => { + const store = setupStore({ + board: { + ids: [Vec.id([0, 0])], + entities: { + [Vec.id([0, 0])]: { + position: [0, 0], + card: PathCard.START, + }, + }, + }, + }); + + renderWithProviders(, { store }); + + // verify nodes exist + expect(screen.queryAllByTestId("position")).toHaveLength(1); + expect(screen.queryAllByTestId("card")).toHaveLength(1); + + await act(async () => { + store.dispatch( + Board.remove(Vec.id([0, 0])) + ); + await nextFrame(); + }); + + // check nodes had been removed + expect(screen.queryAllByTestId("position")).toHaveLength(0); + expect(screen.queryAllByTestId("card")).toHaveLength(0); + }); +}); diff --git a/apps/frontend/test/utils/index.tsx b/apps/frontend/test/utils/index.tsx index 419154e..4886862 100644 --- a/apps/frontend/test/utils/index.tsx +++ b/apps/frontend/test/utils/index.tsx @@ -20,3 +20,9 @@ export function renderWithProviders( return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }; } + +export function nextFrame() { + return new Promise((resolve) => { + requestAnimationFrame(resolve); + }); +}