From 1554ee4097440ef335b1fd9cd0a84aa0579f3618 Mon Sep 17 00:00:00 2001 From: Benoit Averty Date: Wed, 15 Jun 2016 18:19:47 +0200 Subject: [PATCH 1/7] feat(gogame): Add the method playMove to GoGame prototype Each GoGame object now has a playMove method which calls the reducer with itself and the playMove action and returns the result. The GoGame itself stays immutable. --- .babelrc | 5 ++++- package.json | 4 +++- src/GoGame/GoGame.js | 3 +++ test/game/GoGame.spec.js | 22 ++++++++++++++++++++-- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/.babelrc b/.babelrc index 1c4057a..3f0ca7d 100644 --- a/.babelrc +++ b/.babelrc @@ -3,7 +3,10 @@ "plugins": ["transform-object-rest-spread", "add-module-exports"], "env": { "test": { - "plugins": [["__coverage__", { "only": "src/GoGame" }]] + "plugins": [ + ["__coverage__", { "only": "src/GoGame" }], + "rewire", + ] } } } diff --git a/package.json b/package.json index 0d9bb66..e2ff32e 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,11 @@ "babel-loader": "^6.2.4", "babel-plugin-__coverage__": "^1.11.111", "babel-plugin-add-module-exports": "^0.2.1", + "babel-plugin-rewire": "^1.0.0-rc-3", "babel-plugin-transform-object-rest-spread": "^6.8.0", "babel-preset-es2015": "^6.9.0", "babel-preset-react": "^6.5.0", "chai": "^3.5.0", - "chai-enzyme": "^0.4.2", "codecov.io": "^0.1.6", "commitizen": "^2.8.2", "cross-env": "^1.0.8", @@ -68,6 +68,8 @@ "nyc": "^6.4.4", "rimraf": "^2.5.2", "semantic-release": "^4.3.5", + "sinon": "^1.17.4", + "sinon-chai": "^2.8.0", "webpack": "^1.13.1", "webpack-dev-server": "^1.14.1" }, diff --git a/src/GoGame/GoGame.js b/src/GoGame/GoGame.js index f9c9f9e..ae71737 100644 --- a/src/GoGame/GoGame.js +++ b/src/GoGame/GoGame.js @@ -17,6 +17,9 @@ GoGame.prototype = { get turn() { return 'BLACK'; }, + playMove({ i, j }) { + return goGameReducer(this, actions.playMove(i, j)); + }, }; export default GoGame; diff --git a/test/game/GoGame.spec.js b/test/game/GoGame.spec.js index 0fe9c09..a493a3a 100644 --- a/test/game/GoGame.spec.js +++ b/test/game/GoGame.spec.js @@ -1,8 +1,11 @@ /* eslint-env mocha */ import _ from 'lodash'; -import { expect } from 'chai'; +import chai, { expect } from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +chai.use(sinonChai); -import { GoGame } from '../../src'; +import { GoGame, actions } from '../../src'; describe('GoGame', () => { describe('default / empty GoGame object', () => { @@ -49,4 +52,19 @@ describe('GoGame', () => { expect(game.turn).to.equal('BLACK'); }); }); + + /* eslint-disable no-underscore-dangle */ + describe('playMove method', () => { + it('Should call the reducer with the playMove action and return the result', () => { + const resultGame = {}; + const reducerStub = sinon.stub().returns(resultGame); + GoGame.__Rewire__('goGameReducer', reducerStub); + const game = new GoGame(); + + const actual = game.playMove({ i: 3, j: 3 }); + + expect(reducerStub).to.have.been.calledWith(game, actions.playMove(3, 3)); + expect(actual).to.equal(resultGame); + }); + }); }); From 938b6bf273428a88a58fbe57c2ea96533c740423 Mon Sep 17 00:00:00 2001 From: Benoit Averty Date: Thu, 16 Jun 2016 11:24:56 +0200 Subject: [PATCH 2/7] chore(test): refactor the test and coverage scripts --- .travis.yml | 7 +++++-- package.json | 5 ++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 169f5df..6efd47c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,13 +7,16 @@ notifications: email: false node_js: - '6' + - '5' + - '4' before_script: - npm prune script: - - npm run test + - npm run test:with-coverage + - npm run test:check-coverage after_success: - # No need to build as it is done in the prepublish script - npm run test:publish-coverage + # No need to build as it is done in the prepublish script - npm run semantic-release branches: only: diff --git a/package.json b/package.json index e2ff32e..7f277eb 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,8 @@ "test": "test" }, "scripts": { - "test": "npm run clean:coverage && npm run test:run && npm run test:check-coverage", - "test:run": "cross-env BABEL_ENV=test nyc --reporter=lcov --reporter=clover mocha --compilers js:babel-core/register --recursive", - "test:debug": "mocha --recursive --compilers js:babel-core/register --debug-brk", + "test": "cross-env BABEL_ENV=test mocha --compilers js:babel-core/register --recursive", + "test:with-coverage": "npm run clean:coverage && cross-env BABEL_ENV=test nyc --reporter=lcov mocha --compilers js:babel-core/register --recursive", "test:check-coverage": "nyc check-coverage --statements 100 --branches 100 --functions 100 --lines 100", "test:publish-coverage": "cat ./coverage/lcov.info | codecov", "prebuild": "npm run clean", From 5a133cde858b8d432e7fb5bd211cd7ec89d97482 Mon Sep 17 00:00:00 2001 From: Benoit Averty Date: Thu, 16 Jun 2016 11:47:29 +0200 Subject: [PATCH 3/7] chore(commit): add ghooks to enforce commit policy The commit message of each commit will be checked against the conventional format. The best solution to commit would be to use the git cz command. Tests are also run with coverage and the commit will fail if all the tests are not passing and the coverage is not 100% --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index 7f277eb..c0d91f2 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "eslint-plugin-import": "^1.8.0", "eslint-plugin-jsx-a11y": "^1.2.2", "eslint-plugin-react": "^5.1.1", + "ghooks": "^1.2.4", "mocha": "^2.5.1", "npm-run-all": "^2.1.2", "nyc": "^6.4.4", @@ -83,6 +84,10 @@ "rx": "^4.1.0" }, "config": { + "ghooks": { + "pre-commit": "npm run test:with-coverage -- -R dot && npm run test:check-coverage", + "commit-msg": "validate-commit-msg" + }, "commitizen": { "path": "./node_modules/cz-conventional-changelog" } From 0b371bae269bd10b699b3174299a1e231ed3e05a Mon Sep 17 00:00:00 2001 From: Benoit Averty Date: Thu, 16 Jun 2016 13:50:24 +0200 Subject: [PATCH 4/7] feat(gogame): Add remaining action methods to GoGame These methods are dynamically generated from the action creators. They are only shortcuts to the reducer. --- src/GoGame/GoGame.js | 13 ++++++++++--- test/game/GoGame.spec.js | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/GoGame/GoGame.js b/src/GoGame/GoGame.js index ae71737..4a7883f 100644 --- a/src/GoGame/GoGame.js +++ b/src/GoGame/GoGame.js @@ -1,3 +1,5 @@ +import _forIn from 'lodash/forIn'; + import goGameReducer from './reducer'; import actions from './actions'; @@ -17,9 +19,14 @@ GoGame.prototype = { get turn() { return 'BLACK'; }, - playMove({ i, j }) { - return goGameReducer(this, actions.playMove(i, j)); - }, }; +_forIn(actions, (actionCreator, actionName) => { + if (actionName !== 'init') { + GoGame.prototype[actionName] = function goGameShortcutMethod(...args) { + return new GoGame(goGameReducer(this, actionCreator(...args))); + }; + } +}); + export default GoGame; diff --git a/test/game/GoGame.spec.js b/test/game/GoGame.spec.js index a493a3a..f6d5cfd 100644 --- a/test/game/GoGame.spec.js +++ b/test/game/GoGame.spec.js @@ -54,17 +54,40 @@ describe('GoGame', () => { }); /* eslint-disable no-underscore-dangle */ - describe('playMove method', () => { - it('Should call the reducer with the playMove action and return the result', () => { - const resultGame = {}; - const reducerStub = sinon.stub().returns(resultGame); + describe('Shortcuts methods', () => { + const resultGame = { board: [], moves: [], actions: [] }; + const reducerStub = sinon.stub().returns(resultGame); + before(() => { GoGame.__Rewire__('goGameReducer', reducerStub); + }); + + it('Should not include the init() action', () => { const game = new GoGame(); + expect(game.init).to.not.exist; + }); + + const methods = { + playMove: [3, 3], + pass: [], + setMark: [{ i: 3, j: 3 }, 'test'], + }; + + _.forIn(methods, (actionArgs, action) => { + describe(`${action} method`, () => { + it(`Should call the reducer with itself and actions.${action}(), passing its args`, () => { + const game = new GoGame(); + game[action](...actionArgs); + + expect(reducerStub).to.have.been.calledWith(game, actions[action](...actionArgs)); + }); - const actual = game.playMove({ i: 3, j: 3 }); + it('Should return the result of the reducer as a GoGame object', () => { + const game = new GoGame(); + const actual = game[action](...actionArgs); - expect(reducerStub).to.have.been.calledWith(game, actions.playMove(3, 3)); - expect(actual).to.equal(resultGame); + expect(actual).to.deep.equal(new GoGame(resultGame)); + }); + }); }); }); }); From 8c10d113d70d7c9a9e0e4fb25fd0907d5e9a087f Mon Sep 17 00:00:00 2001 From: Benoit Averty Date: Thu, 16 Jun 2016 14:28:38 +0200 Subject: [PATCH 5/7] chore(build): Remove the travis cache and add missing dependency in package.json --- .travis.yml | 3 --- package.json | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6efd47c..f84ae85 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,5 @@ sudo: false language: node_js -cache: - directories: - - node_modules notifications: email: false node_js: diff --git a/package.json b/package.json index c0d91f2..8a22bb5 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "semantic-release": "^4.3.5", "sinon": "^1.17.4", "sinon-chai": "^2.8.0", + "validate-commit-msg": "^2.6.1", "webpack": "^1.13.1", "webpack-dev-server": "^1.14.1" }, From 825c5aa34bd0a3134e78bef3fa969c758e6a4d88 Mon Sep 17 00:00:00 2001 From: Benoit Averty Date: Thu, 16 Jun 2016 15:16:18 +0200 Subject: [PATCH 6/7] fix(reducer): Fix the construction of the initial state managed by the reducer --- src/GoGame/GoGame.js | 4 ++-- src/GoGame/reducer/index.js | 2 +- test/game/GoGame.spec.js | 9 ++++----- test/game/gameReducer.spec.js | 12 ++++++++++++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/GoGame/GoGame.js b/src/GoGame/GoGame.js index 4a7883f..b1330b8 100644 --- a/src/GoGame/GoGame.js +++ b/src/GoGame/GoGame.js @@ -1,4 +1,4 @@ -import _forIn from 'lodash/forIn'; +import _forOwn from 'lodash/forOwn'; import goGameReducer from './reducer'; import actions from './actions'; @@ -21,7 +21,7 @@ GoGame.prototype = { }, }; -_forIn(actions, (actionCreator, actionName) => { +_forOwn(actions, (actionCreator, actionName) => { if (actionName !== 'init') { GoGame.prototype[actionName] = function goGameShortcutMethod(...args) { return new GoGame(goGameReducer(this, actionCreator(...args))); diff --git a/src/GoGame/reducer/index.js b/src/GoGame/reducer/index.js index 603880c..5de0303 100644 --- a/src/GoGame/reducer/index.js +++ b/src/GoGame/reducer/index.js @@ -5,7 +5,7 @@ import playMoveReducer from './playMoveReducer'; import setMarkReducer from './setMarkReducer'; const initialGame = { - board: _map(Array(19), () => _map(Array(19), {})), + board: _map(Array(19), () => _map(Array(19), () => ({}))), moves: [], koCoordinates: null, actions: [], diff --git a/test/game/GoGame.spec.js b/test/game/GoGame.spec.js index f6d5cfd..052a815 100644 --- a/test/game/GoGame.spec.js +++ b/test/game/GoGame.spec.js @@ -5,15 +5,14 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; chai.use(sinonChai); -import { GoGame, actions } from '../../src'; +import { GoGame, actions, goGameReducer } from '../../src'; describe('GoGame', () => { describe('default / empty GoGame object', () => { - it('Should contain a 19x19 array of empty objects', () => { + it('Should return the result of the reducer with init action', () => { const game = new GoGame(); - const expected = _.map(Array(19), () => _.map(Array(19), {})); - expect(game.board).to.deep.equal(expected); + expect(game).to.deep.equal(new GoGame(goGameReducer(undefined, actions.init()))); }); it('Should have the ko property set to false', () => { @@ -72,7 +71,7 @@ describe('GoGame', () => { setMark: [{ i: 3, j: 3 }, 'test'], }; - _.forIn(methods, (actionArgs, action) => { + _.forOwn(methods, (actionArgs, action) => { describe(`${action} method`, () => { it(`Should call the reducer with itself and actions.${action}(), passing its args`, () => { const game = new GoGame(); diff --git a/test/game/gameReducer.spec.js b/test/game/gameReducer.spec.js index 867d790..249d71f 100644 --- a/test/game/gameReducer.spec.js +++ b/test/game/gameReducer.spec.js @@ -13,6 +13,18 @@ describe('Game Reducer', () => { expect(result).to.equal(state); }); + + it('Should return an object with a correct board', () => { + const newGame = goGameReducer(undefined, actions.init()); + + expect(newGame.board).to.exist; + newGame.board.forEach((row) => { + expect(row).to.exist.and.have.length(19); + row.forEach((intersection) => { + expect(intersection).to.deep.equal({}); + }); + }); + }); }); describe('With the playMove(i,j) action', () => { From 421f303964e20cbccf7d155c31c8e1f09ff278e9 Mon Sep 17 00:00:00 2001 From: Benoit Averty Date: Thu, 16 Jun 2016 16:26:43 +0200 Subject: [PATCH 7/7] test(gogame): Add the missing __ResetDependency__ in GoGame test This fucked up the rest of the tests on linux (including travis-ci) --- test/game/GoGame.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/game/GoGame.spec.js b/test/game/GoGame.spec.js index 052a815..fc3a840 100644 --- a/test/game/GoGame.spec.js +++ b/test/game/GoGame.spec.js @@ -59,6 +59,9 @@ describe('GoGame', () => { before(() => { GoGame.__Rewire__('goGameReducer', reducerStub); }); + after(() => { + GoGame.__ResetDependency__('goGameReducer'); + }); it('Should not include the init() action', () => { const game = new GoGame();