Skip to content

Commit

Permalink
Merge pull request #56 from nickovchinnikov/nick/gameWithRedux
Browse files Browse the repository at this point in the history
Add React + Redux and useReducer
  • Loading branch information
nickovchinnikov authored Oct 14, 2021
2 parents 1874090 + 51838d8 commit 3299811
Show file tree
Hide file tree
Showing 12 changed files with 8,365 additions and 8 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,5 +304,10 @@
[Pull request](https://github.com/nickovchinnikov/minesweeper/pull/54/files)

### Game module by TDD with createSlice
### Game module by TDD with createSlice 2

[Pull request](https://github.com/nickovchinnikov/minesweeper/pull/55/files)

### useReducer

[Pull request](https://github.com/nickovchinnikov/minesweeper/pull/56/files)
7 changes: 7 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from 'react-router-dom';

import { MinesweeperWithHooks } from '@/pages/MinesweeperWithHooks';
import { MinesweeperWithRedux } from '@/pages/MinesweeperWithRedux';

export const App: FC = () => (
<Router>
Expand All @@ -20,6 +21,9 @@ export const App: FC = () => (
<li>
<Link to="/game-with-hooks">Game With Hooks</Link>
</li>
<li>
<Link to="/game-with-redux">Game With Redux</Link>
</li>
</ul>
</nav>
<Switch>
Expand All @@ -29,6 +33,9 @@ export const App: FC = () => (
<Route path="/game-with-hooks/:username?">
<MinesweeperWithHooks />
</Route>
<Route path="/game-with-redux">
<MinesweeperWithRedux />
</Route>
<Route path="*">
<Redirect to="/" />
</Route>
Expand Down
47 changes: 47 additions & 0 deletions src/modules/GameWithRedux/GameWithRedux.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
import { render, screen } from '@testing-library/react';

import { GameWithRedux } from './GameWithRedux';

jest.mock('@/core/Field');

beforeEach(() => {
jest.clearAllMocks();
});

describe('GameWithHooks test cases', () => {
it('Render game field by default', () => {
const { asFragment } = render(<GameWithRedux />);
expect(asFragment()).toMatchSnapshot();
});
it('Cell click works fine', () => {
const { asFragment } = render(<GameWithRedux />);
userEvent.click(screen.getByTestId('0,0'));
expect(asFragment()).toMatchSnapshot();
});
it('Context menu handler on a cell works fine', () => {
const { asFragment } = render(<GameWithRedux />);
userEvent.click(screen.getByTestId('0,0'), { button: 2 });
expect(asFragment()).toMatchSnapshot();
});
it('Reset handler works fine', () => {
const { asFragment } = render(<GameWithRedux />);
userEvent.click(screen.getByTestId('0,0'));
expect(asFragment()).toMatchSnapshot();
userEvent.click(screen.getByRole('button'));
expect(asFragment()).toMatchSnapshot();
});
it('Change level works fine', () => {
const { asFragment } = render(<GameWithRedux />);
userEvent.selectOptions(screen.getByRole('combobox'), 'intermediate');
expect(asFragment()).toMatchSnapshot();
});
it('Game over reset the game state', () => {
const { asFragment } = render(<GameWithRedux />);
userEvent.click(screen.getByTestId('0,0'));
expect(asFragment()).toMatchSnapshot();
userEvent.click(screen.getByText('🙂'));
expect(asFragment()).toMatchSnapshot();
});
});
62 changes: 62 additions & 0 deletions src/modules/GameWithRedux/GameWithRedux.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { FC, useReducer, useCallback } from 'react';

import { Coords } from '@/core/Field';

import { GameLevels, LevelNames } from '@/modules/GameSettings';

import { Scoreboard } from '@/components/Scoreboard';
import { Grid } from '@/components/Grid';
import { GameOver } from '@/components/Game';

import { reducer, actions, getInitialState } from './game';

export const GameWithRedux: FC = () => {
const [
{ level, time, isGameOver, isWin, settings, playerField, flagCounter },
dispatch,
] = useReducer(reducer, getInitialState());

const [, bombs] = settings;

const onClick = useCallback(
(coords: Coords) => dispatch(actions.openCell(coords)),
// Stryker disable next-line ArrayDeclaration
[]
);

const onReset = useCallback(
() => dispatch(actions.reset()),
// Stryker disable next-line ArrayDeclaration
[]
);

const onContextMenu = useCallback(
(coords: Coords) => dispatch(actions.setFlag(coords)),
// Stryker disable next-line ArrayDeclaration
[]
);

const onChangeLevel = useCallback(
({ target: { value: level } }: React.ChangeEvent<HTMLSelectElement>) =>
dispatch(actions.changeLevel(level as LevelNames)),
// Stryker disable next-line ArrayDeclaration
[]
);

return (
<>
<Scoreboard
time={String(time)}
bombs={String(bombs - flagCounter)}
levels={GameLevels as unknown as string[]}
defaultLevel={level}
onChangeLevel={onChangeLevel}
onReset={onReset}
/>
{isGameOver && <GameOver onClick={onReset} isWin={isWin} />}
<Grid onClick={onClick} onContextMenu={onContextMenu}>
{playerField}
</Grid>
</>
);
};
Loading

0 comments on commit 3299811

Please sign in to comment.