From 4d067bea5d92f3930067e3ce6f2614aea6558fc5 Mon Sep 17 00:00:00 2001 From: Nikita Ovchinnikov Date: Fri, 15 Oct 2021 16:12:31 +0400 Subject: [PATCH 1/2] Add timer functionality --- README.md | 6 ++ .../GameWithRedux/GameWithReactRedux/Grid.tsx | 12 ++- src/modules/GameWithRedux/game.test.ts | 99 ++++++++++++++++++- src/modules/GameWithRedux/game.ts | 31 +++++- 4 files changed, 143 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3e7e356..fe7b9be 100644 --- a/README.md +++ b/README.md @@ -317,3 +317,9 @@ ### React-Redux [Pull request](https://github.com/nickovchinnikov/minesweeper/pull/57/files) + +### Global store benifits and redux-dev-tools + +### Redux middlewares + +[Pull request](https://github.com/nickovchinnikov/minesweeper/pull/58/files) diff --git a/src/modules/GameWithRedux/GameWithReactRedux/Grid.tsx b/src/modules/GameWithRedux/GameWithReactRedux/Grid.tsx index 362f66f..da11bd3 100644 --- a/src/modules/GameWithRedux/GameWithReactRedux/Grid.tsx +++ b/src/modules/GameWithRedux/GameWithReactRedux/Grid.tsx @@ -5,7 +5,7 @@ import { Coords } from '@/core/Field'; import { RootState } from '@/store'; import { Grid as GridComponent } from '@/components/Grid'; -import { actions } from '@/modules/GameWithRedux/game'; +import { actions, runTimer } from '@/modules/GameWithRedux/game'; export const Grid: FC = () => { const { playerField } = useSelector( @@ -17,13 +17,19 @@ export const Grid: FC = () => { const dispatch = useDispatch(); const onClick = useCallback( - (coords: Coords) => dispatch(actions.openCell(coords)), + (coords: Coords) => { + dispatch(actions.openCell(coords)); + dispatch(runTimer()); + }, // Stryker disable next-line ArrayDeclaration [] ); const onContextMenu = useCallback( - (coords: Coords) => dispatch(actions.setFlag(coords)), + (coords: Coords) => { + dispatch(actions.setFlag(coords)); + dispatch(runTimer()); + }, // Stryker disable next-line ArrayDeclaration [] ); diff --git a/src/modules/GameWithRedux/game.test.ts b/src/modules/GameWithRedux/game.test.ts index a9d4cfa..1f7a331 100644 --- a/src/modules/GameWithRedux/game.test.ts +++ b/src/modules/GameWithRedux/game.test.ts @@ -1,9 +1,11 @@ import { GameSettings } from '@/modules/GameSettings'; import { CellState, Field } from '@/core/Field'; +import { RootState } from '@/store'; + const { empty: e, hidden: h, bomb: b, flag: f, weakFlag: w } = CellState; -import { reducer, actions, State } from './game'; +import { reducer, actions, runTimer, recursiveUpdate, State } from './game'; describe('Game reducer', () => { const level = 'beginner'; @@ -177,4 +179,99 @@ describe('Game reducer', () => { expect(nextState.playerField).toHaveLength(16); }); }); + + describe('Check updateTime action', () => { + it('Update time from 0', () => { + expect(reducer(baseInitialState, actions.updateTime())).toEqual( + expect.objectContaining({ + time: 1, + }) + ); + }); + it('Update time from 10', () => { + expect( + reducer({ ...baseInitialState, time: 10 }, actions.updateTime()) + ).toEqual( + expect.objectContaining({ + time: 11, + }) + ); + }); + }); + + describe('Async actions check', () => { + it('Check action runTimer with state { isGameStarted: true, time: 0 }', () => { + const mockDispatch = jest.fn(); + runTimer()( + mockDispatch, + () => ({ + game: { + isGameStarted: true, + time: 0, + } as State, + }), + undefined + ); + expect(mockDispatch).toHaveBeenCalled(); + }); + it('Check action runTimer with state { isGameStarted: true, time: 1 }', () => { + const mockDispatch = jest.fn(); + runTimer()( + mockDispatch, + () => ({ + game: { + isGameStarted: true, + time: 1, + } as State, + }), + undefined + ); + expect(mockDispatch).not.toHaveBeenCalled(); + }); + it('Check action runTimer with state { isGameStarted: false, time: 10 }', () => { + const mockDispatch = jest.fn(); + runTimer()( + mockDispatch, + () => ({ + game: { + isGameStarted: false, + time: 10, + } as State, + }), + undefined + ); + expect(mockDispatch).not.toHaveBeenCalled(); + }); + + it('Check action recursiveUpdate with state { isGameStarted: true }', () => { + jest.useFakeTimers(); + const mockDispatch = jest.fn(); + recursiveUpdate()( + mockDispatch, + () => ({ + game: { + isGameStarted: true, + } as State, + }), + undefined + ); + jest.advanceTimersByTime(1000); + expect(mockDispatch).toHaveBeenCalledTimes(2); + }); + it('Check action recursiveUpdate with state { isGameStarted: false }', () => { + jest.useFakeTimers(); + const mockDispatch = jest.fn(); + recursiveUpdate()( + mockDispatch, + () => ({ + game: { + isGameStarted: false, + } as State, + }), + undefined + ); + jest.advanceTimersByTime(1000); + expect(mockDispatch).not.toHaveBeenCalled(); + }); + }); }); diff --git a/src/modules/GameWithRedux/game.ts b/src/modules/GameWithRedux/game.ts index 0abb650..1cf7298 100644 --- a/src/modules/GameWithRedux/game.ts +++ b/src/modules/GameWithRedux/game.ts @@ -1,4 +1,9 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { + createSlice, + PayloadAction, + ThunkAction, + AnyAction, +} from '@reduxjs/toolkit'; import { Field, @@ -10,6 +15,7 @@ import { import { LevelNames, GameSettings } from '@/modules/GameSettings'; import { openCell as openCellHandler } from '@/core/openCell'; import { setFlag } from '@/core/setFlag'; +import { RootState } from '@/store'; export interface State { level: LevelNames; @@ -81,8 +87,31 @@ export const { reducer, actions } = createSlice({ state.flagCounter = newFlagCounter; state.playerField = newPlayerField; }, + updateTime: (state) => { + state.time = state.time + 1; + }, reset: ({ level }) => getInitialState(level), changeLevel: (state, { payload }: PayloadAction) => getInitialState(payload), }, }); + +export const recursiveUpdate = + (): ThunkAction => + (dispatch, getState) => + setTimeout(() => { + const { isGameStarted } = getState().game; + if (isGameStarted) { + dispatch(actions.updateTime()); + dispatch(recursiveUpdate()); + } + }, 1000); + +export const runTimer = + (): ThunkAction => + (dispatch, getState) => { + const { isGameStarted, time } = getState().game; + if (time === 0 && isGameStarted) { + dispatch(recursiveUpdate()); + } + }; From 9fce39b6bc49766df951979ce62fe438b20b054d Mon Sep 17 00:00:00 2001 From: Nikita Ovchinnikov Date: Fri, 15 Oct 2021 16:19:21 +0400 Subject: [PATCH 2/2] Drop upused import --- src/modules/GameWithRedux/game.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/GameWithRedux/game.test.ts b/src/modules/GameWithRedux/game.test.ts index 1f7a331..5191794 100644 --- a/src/modules/GameWithRedux/game.test.ts +++ b/src/modules/GameWithRedux/game.test.ts @@ -1,8 +1,6 @@ import { GameSettings } from '@/modules/GameSettings'; import { CellState, Field } from '@/core/Field'; -import { RootState } from '@/store'; - const { empty: e, hidden: h, bomb: b, flag: f, weakFlag: w } = CellState; import { reducer, actions, runTimer, recursiveUpdate, State } from './game';