Implement tests around redux game logic and add pre-commit hook
This commit is contained in:
+11
-3
@@ -3,7 +3,6 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@types/jest": "24.0.18",
|
||||
"@types/node": "12.7.12",
|
||||
"@types/react": "16.9.5",
|
||||
"@types/react-dom": "16.9.1",
|
||||
@@ -19,8 +18,15 @@
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"test-nowatch": "CI=true react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject",
|
||||
"lint": "eslint --ext .js,.tsx,.ts,.tsx src/"
|
||||
},
|
||||
"pre-commit": [
|
||||
"lint",
|
||||
"test-nowatch",
|
||||
"build"
|
||||
],
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
@@ -37,6 +43,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.18",
|
||||
"@types/react-redux": "^7.1.4",
|
||||
"@types/redux": "^3.6.0",
|
||||
"@types/styled-components": "^4.1.19",
|
||||
@@ -48,6 +55,7 @@
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-react": "^7.16.0",
|
||||
"eslint-plugin-react-hooks": "^1.7.0",
|
||||
"jest": "^24.9.0"
|
||||
"jest": "^24.9.0",
|
||||
"pre-commit": "^1.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -21,6 +21,7 @@ const StyledApp = styled.div`
|
||||
|
||||
interface StateProps {
|
||||
currentPlayer: Player;
|
||||
winner: Winner;
|
||||
}
|
||||
|
||||
interface ActionProps {
|
||||
@@ -29,10 +30,12 @@ interface ActionProps {
|
||||
|
||||
type Props = StateProps & ActionProps;
|
||||
|
||||
const App: React.FC<Props> = ({ currentPlayer, startGame }) => (
|
||||
const App: React.FC<Props> = ({ currentPlayer, startGame, winner }) => (
|
||||
<StyledApp>
|
||||
<GlobalStyle />
|
||||
<h1>{currentPlayer}</h1>
|
||||
<h1>
|
||||
{currentPlayer} - {winner}
|
||||
</h1>
|
||||
<button type="button" onClick={startGame}>
|
||||
Reset Game
|
||||
</button>
|
||||
@@ -42,6 +45,7 @@ const App: React.FC<Props> = ({ currentPlayer, startGame }) => (
|
||||
|
||||
const mapStateToProps = (state: GameState): StateProps => ({
|
||||
currentPlayer: state.currentPlayer,
|
||||
winner: state.winner,
|
||||
});
|
||||
|
||||
const mapActionsToProps = {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
describe('My Connected React-Redux Component', () => {
|
||||
it('should render with given state from Redux store', () => {
|
||||
});
|
||||
it('should dispatch an action on button click', () => {
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
describe('My Connected React-Redux Component', () => {
|
||||
it('should render with given state from Redux store', () => {});
|
||||
it('should dispatch an action on button click', () => {});
|
||||
});
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { Board, Cell, Cells, empty, Rows } from './Board';
|
||||
import { Player, Winner, tie, undetermined } from './Player';
|
||||
|
||||
export interface IBoardSummary {
|
||||
export interface BoardSummary {
|
||||
horizontal: Rows;
|
||||
vertical: Rows;
|
||||
diagonal: Rows;
|
||||
@@ -52,8 +52,8 @@ export const getWinnerFromLines = (lines: Rows): Winner => {
|
||||
return undetermined;
|
||||
};
|
||||
|
||||
export const getBoardSummary = (board: Board): IBoardSummary => {
|
||||
const summary: IBoardSummary = {
|
||||
export const getBoardSummary = (board: Board): BoardSummary => {
|
||||
const summary: BoardSummary = {
|
||||
horizontal: [[], [], []],
|
||||
vertical: [[], [], []],
|
||||
diagonal: [[], []],
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import { reducer, GameState, actions } from './game';
|
||||
import { Player, tie, undetermined } from '../lib/Player';
|
||||
import { empty, createEmptyBoard } from '../lib/Board';
|
||||
import { getBoardSummary, BoardSummary } from '../lib/scoring';
|
||||
|
||||
jest.mock('../lib/scoring');
|
||||
|
||||
const mockedGetBoardSummary = <jest.Mock<BoardSummary>>getBoardSummary;
|
||||
|
||||
describe('reducer with name', () => {
|
||||
describe('START_GAME', () => {
|
||||
it('must reset the game state', () => {
|
||||
const state: GameState = {
|
||||
currentPlayer: Player.O,
|
||||
activeGame: false,
|
||||
board: [],
|
||||
winner: tie,
|
||||
};
|
||||
const action = actions.startGame();
|
||||
const updatedState = reducer(state, action);
|
||||
expect(updatedState).toEqual({
|
||||
board: [[empty, empty, empty], [empty, empty, empty], [empty, empty, empty]],
|
||||
winner: undetermined,
|
||||
activeGame: true,
|
||||
currentPlayer: Player.X,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('END_GAME', () => {
|
||||
it('must set the activeGame flag to false', () => {
|
||||
const state: GameState = {
|
||||
currentPlayer: Player.O,
|
||||
activeGame: true,
|
||||
board: [],
|
||||
winner: tie,
|
||||
};
|
||||
const action = actions.endGame();
|
||||
const updatedState = reducer(state, action);
|
||||
expect(updatedState).toEqual({ ...state, activeGame: false });
|
||||
});
|
||||
});
|
||||
describe('NEXT_PLAYER', () => {
|
||||
it('must toggle the player everytime NEXT_PLAYER is called', () => {
|
||||
const state: GameState = {
|
||||
currentPlayer: Player.O,
|
||||
activeGame: true,
|
||||
board: [],
|
||||
winner: tie,
|
||||
};
|
||||
const action = actions.nextPlayer();
|
||||
let updatedState = reducer(state, action);
|
||||
expect(updatedState.currentPlayer).toEqual(Player.X);
|
||||
updatedState = reducer(updatedState, action);
|
||||
expect(updatedState.currentPlayer).toEqual(Player.O);
|
||||
updatedState = reducer(updatedState, action);
|
||||
expect(updatedState.currentPlayer).toEqual(Player.X);
|
||||
});
|
||||
it('must not toggle the player when the game is not active anymore', () => {
|
||||
const state: GameState = {
|
||||
currentPlayer: Player.O,
|
||||
activeGame: false,
|
||||
board: [],
|
||||
winner: tie,
|
||||
};
|
||||
const action = actions.nextPlayer();
|
||||
let updatedState = reducer(state, action);
|
||||
expect(updatedState.currentPlayer).toEqual(Player.O);
|
||||
updatedState = reducer(updatedState, action);
|
||||
expect(updatedState.currentPlayer).toEqual(Player.O);
|
||||
updatedState = reducer(updatedState, action);
|
||||
expect(updatedState.currentPlayer).toEqual(Player.O);
|
||||
});
|
||||
});
|
||||
describe('UPDATE_BOARD', () => {
|
||||
it('must mark an empty cell with the specified player', () => {
|
||||
const state: GameState = {
|
||||
currentPlayer: Player.O,
|
||||
activeGame: true,
|
||||
board: createEmptyBoard(),
|
||||
winner: tie,
|
||||
};
|
||||
const action = actions.updateBoard(2, 1, Player.O);
|
||||
const updatedState = reducer(state, action);
|
||||
expect(updatedState).toEqual({
|
||||
...state,
|
||||
board: [[empty, empty, empty], [empty, empty, empty], [empty, Player.O, empty]],
|
||||
});
|
||||
});
|
||||
it('must NOT mark a cell when it is not empty', () => {
|
||||
const state: GameState = {
|
||||
currentPlayer: Player.O,
|
||||
activeGame: true,
|
||||
board: [[empty, empty, empty], [empty, empty, empty], [empty, Player.O, empty]],
|
||||
winner: tie,
|
||||
};
|
||||
const action = actions.updateBoard(2, 1, Player.X);
|
||||
const updatedState = reducer(state, action);
|
||||
expect(updatedState).toEqual({
|
||||
...state,
|
||||
board: [[empty, empty, empty], [empty, empty, empty], [empty, Player.O, empty]],
|
||||
});
|
||||
});
|
||||
it('must NOT mark a cell when the game is not active anymore', () => {
|
||||
const state: GameState = {
|
||||
currentPlayer: Player.O,
|
||||
activeGame: false,
|
||||
board: [[empty, empty, empty], [empty, empty, empty], [empty, Player.O, empty]],
|
||||
winner: tie,
|
||||
};
|
||||
const action = actions.updateBoard(2, 1, Player.X);
|
||||
const updatedState = reducer(state, action);
|
||||
expect(updatedState).toEqual(state);
|
||||
});
|
||||
});
|
||||
describe('DETERMINE_WINNER', () => {
|
||||
it('must use the external scroring logic to decide if there is a winner', () => {
|
||||
const state: GameState = {
|
||||
currentPlayer: Player.O,
|
||||
activeGame: true,
|
||||
board: [],
|
||||
winner: undetermined,
|
||||
};
|
||||
mockedGetBoardSummary.mockReturnValueOnce({ horizontal: [], vertical: [], diagonal: [], winner: Player.X });
|
||||
const action = actions.determineWinner();
|
||||
const updatedState = reducer(state, action);
|
||||
expect(updatedState).toEqual({ ...state, winner: Player.X });
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -61,7 +61,7 @@ const defaultState: IGameState = {
|
||||
|
||||
export const reducer: Reducer<IGameState, Action> = (state: IGameState = defaultState, action: Action): IGameState => {
|
||||
if (action.type === START_GAME) {
|
||||
return { ...state, board: createEmptyBoard(), activeGame: true };
|
||||
return { ...state, board: createEmptyBoard(), activeGame: true, winner: undetermined, currentPlayer: Player.X };
|
||||
}
|
||||
if (action.type === END_GAME) {
|
||||
return { ...state, activeGame: false };
|
||||
|
||||
Reference in New Issue
Block a user