Skip to content

Commit

Permalink
Feature/add frontend board redux test (#107)
Browse files Browse the repository at this point in the history
* test: add frontend board redux test

* test: add nextFrame function

---------

Co-authored-by: ChunRen Zhang <rayatn1011@gmail.com>
  • Loading branch information
kayac-chang and rayatn1011 committed Jul 14, 2023
1 parent 237adf8 commit e86acd0
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 1 deletion.
7 changes: 6 additions & 1 deletion apps/frontend/app/routes/game/store/board.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import type { RootState } from ".";
const adapter = createEntityAdapter<Placement>({
selectId: (placement) => Vec.id(placement.position),
});
export default createSlice({

const slice = createSlice({
name: "board",
initialState: adapter.getInitialState(),
reducers: {
Expand All @@ -32,6 +33,10 @@ export default createSlice({
},
},
});
export default slice;

// actions
export const { add, remove } = slice.actions;

// selectors
const selectors = adapter.getSelectors<RootState>((state) => state.board);
Expand Down
191 changes: 191 additions & 0 deletions apps/frontend/app/routes/game/store/board.test.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ul>
{boards.map((board) => (
<li key={board.position.join("") + board.card}>
<ul data-testid="position">
{board.position.map((item, index) => (
<li key={item + index}>{item}</li>
))}
</ul>
<div data-testid="card">{board.card}</div>
</li>
))}
</ul>
);
}

describe("board state", () => {
test("initial board state with empty", () => {
renderWithProviders(<View />, {
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(<View />, { 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(<View />, { 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(<View />, { 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(<View />, { 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);
});
});
6 changes: 6 additions & 0 deletions apps/frontend/test/utils/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,9 @@ export function renderWithProviders<State = {}>(

return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}

export function nextFrame() {
return new Promise((resolve) => {
requestAnimationFrame(resolve);
});
}

0 comments on commit e86acd0

Please sign in to comment.