From 28e0e6667e27762844dc61f0b25fba1f76c2f25f Mon Sep 17 00:00:00 2001 From: Dmitry Zaets Date: Sat, 4 Jun 2016 22:04:54 +0200 Subject: [PATCH 1/9] Refactor utils. Split utils to separate files. Add "toNotDispatchActions" and "toNotDispatchActionsWithState". --- src/assertions.js | 10 +- src/asserts/actionUtils.js | 62 ---------- src/asserts/errors/dispatchedActionError.js | 9 ++ .../errors/notDispatchedActionError.js | 9 ++ src/asserts/index.js | 4 - src/asserts/toDispatchActions.js | 6 +- src/asserts/toDispatchActionsWithState.js | 44 +------ src/asserts/toNotDispatchActions.js | 13 ++ src/asserts/toNotDispatchActionsWithState.js | 8 ++ src/asserts/utils/assertDispatchedActions.js | 18 +++ .../utils/assertNotDispatchedActions.js | 12 ++ src/asserts/utils/getDispatchedActions.js | 20 ++++ src/asserts/utils/performAssertion.js | 41 +++++++ src/asserts/utils/unrollActions.js | 18 +++ src/initialState.js | 5 +- test/assertions/actionUtils.js | 112 ------------------ test/assertions/toDispatchActions.js | 39 ------ test/asserts/errors/dispatchedActionError.js | 10 ++ .../errors/notDispatchedActionError.js | 10 ++ test/asserts/toDispatchActions.js | 37 ++++++ test/asserts/toDispatchActionsWithState.js | 37 ++++++ test/asserts/toNotDispatchActions.js | 37 ++++++ test/asserts/toNotDispatchActionsWithState.js | 37 ++++++ test/asserts/utils/assertDispatchedActions.js | 64 ++++++++++ .../utils/assertNotDispatchedActions.js | 60 ++++++++++ test/asserts/utils/getDispatchedActions.js | 13 ++ .../utils/performAssertion.js} | 94 ++++++++------- test/asserts/utils/unrollActions.js | 37 ++++++ 28 files changed, 562 insertions(+), 304 deletions(-) delete mode 100644 src/asserts/actionUtils.js create mode 100644 src/asserts/errors/dispatchedActionError.js create mode 100644 src/asserts/errors/notDispatchedActionError.js delete mode 100644 src/asserts/index.js create mode 100644 src/asserts/toNotDispatchActions.js create mode 100644 src/asserts/toNotDispatchActionsWithState.js create mode 100644 src/asserts/utils/assertDispatchedActions.js create mode 100644 src/asserts/utils/assertNotDispatchedActions.js create mode 100644 src/asserts/utils/getDispatchedActions.js create mode 100644 src/asserts/utils/performAssertion.js create mode 100644 src/asserts/utils/unrollActions.js delete mode 100644 test/assertions/actionUtils.js delete mode 100644 test/assertions/toDispatchActions.js create mode 100644 test/asserts/errors/dispatchedActionError.js create mode 100644 test/asserts/errors/notDispatchedActionError.js create mode 100644 test/asserts/toDispatchActions.js create mode 100644 test/asserts/toDispatchActionsWithState.js create mode 100644 test/asserts/toNotDispatchActions.js create mode 100644 test/asserts/toNotDispatchActionsWithState.js create mode 100644 test/asserts/utils/assertDispatchedActions.js create mode 100644 test/asserts/utils/assertNotDispatchedActions.js create mode 100644 test/asserts/utils/getDispatchedActions.js rename test/{assertions/toDispatchActionsWithState.js => asserts/utils/performAssertion.js} (52%) create mode 100644 test/asserts/utils/unrollActions.js diff --git a/src/assertions.js b/src/assertions.js index 9161830..4eb6045 100644 --- a/src/assertions.js +++ b/src/assertions.js @@ -1,4 +1,12 @@ import { toDispatchActions } from './asserts/toDispatchActions'; import { toDispatchActionsWithState } from './asserts/toDispatchActionsWithState'; +import { toNotDispatchActions } from './asserts/toNotDispatchActions'; +import { toNotDispatchActionsWithState } from './asserts/toNotDispatchActionsWithState'; -export default { toDispatchActions, toDispatchActionsWithState }; + +export default { + toDispatchActions, + toDispatchActionsWithState, + toNotDispatchActions, + toNotDispatchActionsWithState +}; diff --git a/src/asserts/actionUtils.js b/src/asserts/actionUtils.js deleted file mode 100644 index 650cbdb..0000000 --- a/src/asserts/actionUtils.js +++ /dev/null @@ -1,62 +0,0 @@ -import findIndex from 'lodash.findindex'; -import flattenDeep from 'lodash.flattendeep'; -import { toArray } from '../utils'; -import getMockStore from '../mockStore'; - -function getDispatchedActions(initialState, action) { - return new Promise((resolve, reject) => { - const store = getMockStore()(initialState); - const dispatchResult = store.dispatch(action); - - if (dispatchResult instanceof Promise) { - dispatchResult.then(() => { - resolve(store.getActions()); - }).catch((result) => { - reject(result); - }); - } else { - resolve(store.getActions()); - } - }); -} - -function unrollActions(initialState, expectedActions) { - const promises = []; - const actions = toArray(expectedActions); - - for (let index = 0; index < actions.length; index++) { - promises.push(getDispatchedActions(initialState, actions[index])); - } - - return Promise.all(promises).then((resultActions) => { - return flattenDeep(resultActions); - }); -} - -function notDispatchedError(dispatchedActions, expectedActions, expectedAction) { - return new Error( - `Expected action ${JSON.stringify(expectedAction)} was not dispatched.\n` + - `Expected dispatched actions: ${JSON.stringify(expectedActions)}\n` + - `Actual dispatched actions: ${JSON.stringify(dispatchedActions)}` - ); -} - -function assertDispatchedActions(dispatched, expected) { - const availableActions = dispatched.slice(); - - for (let indexInExpected = 0; indexInExpected < expected.length; indexInExpected++) { - const indexInAvailable = findIndex(availableActions, expected[indexInExpected]); - - if (indexInAvailable !== -1) { - availableActions.splice(indexInAvailable, 1); - } else { - throw notDispatchedError(dispatched, expected, expected[indexInExpected]); - } - } -} - -export { - getDispatchedActions, - unrollActions, - assertDispatchedActions -}; diff --git a/src/asserts/errors/dispatchedActionError.js b/src/asserts/errors/dispatchedActionError.js new file mode 100644 index 0000000..3b7f860 --- /dev/null +++ b/src/asserts/errors/dispatchedActionError.js @@ -0,0 +1,9 @@ +function dispatchedActionError(dispatchedActions, expectedActions, action) { + return new Error( + `Action ${JSON.stringify(action)} was dispatched when it was unexpected.\n` + + `Actions expected to be not dispatched: ${JSON.stringify(expectedActions)}\n` + + `Actual dispatched actions: ${JSON.stringify(dispatchedActions)}` + ); +} + +export { dispatchedActionError }; diff --git a/src/asserts/errors/notDispatchedActionError.js b/src/asserts/errors/notDispatchedActionError.js new file mode 100644 index 0000000..4cba036 --- /dev/null +++ b/src/asserts/errors/notDispatchedActionError.js @@ -0,0 +1,9 @@ +function notDispatchedActionError(dispatchedActions, expectedActions, action) { + return new Error( + `Action ${JSON.stringify(action)} was not dispatched when it was expected.\n` + + `Actions expected to be dispatched: ${JSON.stringify(expectedActions)}\n` + + `Actual dispatched actions: ${JSON.stringify(dispatchedActions)}` + ); +} + +export { notDispatchedActionError }; diff --git a/src/asserts/index.js b/src/asserts/index.js deleted file mode 100644 index 4be9bf9..0000000 --- a/src/asserts/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import { toDispatchActions } from './toDispatchActions'; -import { toDispatchActionsWithState } from './toDispatchActionsWithState'; - -export default { toDispatchActions, toDispatchActionsWithState }; diff --git a/src/asserts/toDispatchActions.js b/src/asserts/toDispatchActions.js index ab1f9f1..68826c8 100644 --- a/src/asserts/toDispatchActions.js +++ b/src/asserts/toDispatchActions.js @@ -1,10 +1,10 @@ -import getInitialStoreState from '../initialState'; +import { getInitialStoreState } from '../initialState'; import { toDispatchActionsWithState } from './toDispatchActionsWithState'; -function toDispatchActions(actionUnderTest, expectedActions, done, fail) { +function toDispatchActions(action, expectedActions, done, fail) { return toDispatchActionsWithState( getInitialStoreState(), - actionUnderTest, + action, expectedActions, done, fail ); diff --git a/src/asserts/toDispatchActionsWithState.js b/src/asserts/toDispatchActionsWithState.js index 8584fb5..d72c196 100644 --- a/src/asserts/toDispatchActionsWithState.js +++ b/src/asserts/toDispatchActionsWithState.js @@ -1,42 +1,8 @@ -import { isFunction, isObject } from '../utils'; -import { getDispatchedActions, unrollActions, assertDispatchedActions } from './actionUtils'; +import { performAssertion } from './utils/performAssertion'; +import { assertDispatchedActions } from './utils/assertDispatchedActions'; -function toDispatchActionsWithState(initialState, actionUnderTest, expectedActions, done, fail) { - if (!isFunction(actionUnderTest) && !isObject(actionUnderTest)) { - throw new Error( - 'The "actualAction" argument must be a function or an object' - ); - } - - if (!isFunction(expectedActions) && - !isObject(expectedActions) && - !Array.isArray(expectedActions)) { - throw new Error( - 'The "expectedActions" argument must be ' + - 'an action creator function, an action object, or an array of them' - ); - } - - return getDispatchedActions(initialState, actionUnderTest).then((dispatchedActions) => { - return unrollActions(initialState, expectedActions).then((expectedUnrolledActions) => { - assertDispatchedActions(dispatchedActions, expectedUnrolledActions); - - if (isFunction(done)) { - done(); - } - }); - }).catch((err) => { - if (isFunction(fail)) { - fail(err); - return; - } else if (isFunction(done)) { - done(err); - return; - } - throw new Error(JSON.stringify(err)); - }); +function toDispatchActionsWithState(initialState, action, expectedActions, done, fail) { + performAssertion(assertDispatchedActions, initialState, action, expectedActions, done, fail); } -export { - toDispatchActionsWithState -}; +export { toDispatchActionsWithState }; diff --git a/src/asserts/toNotDispatchActions.js b/src/asserts/toNotDispatchActions.js new file mode 100644 index 0000000..61cdaf9 --- /dev/null +++ b/src/asserts/toNotDispatchActions.js @@ -0,0 +1,13 @@ +import { getInitialStoreState } from '../initialState'; +import { toNotDispatchActionsWithState } from './toNotDispatchActionsWithState'; + +function toNotDispatchActions(action, expectedActions, done, fail) { + return toNotDispatchActionsWithState( + getInitialStoreState(), + action, + expectedActions, + done, fail + ); +} + +export { toNotDispatchActions }; diff --git a/src/asserts/toNotDispatchActionsWithState.js b/src/asserts/toNotDispatchActionsWithState.js new file mode 100644 index 0000000..2120fe5 --- /dev/null +++ b/src/asserts/toNotDispatchActionsWithState.js @@ -0,0 +1,8 @@ +import { performAssertion } from './utils/performAssertion'; +import { assertNotDispatchedActions } from './utils/assertNotDispatchedActions'; + +function toNotDispatchActionsWithState(initialState, action, expectedActions, done, fail) { + performAssertion(assertNotDispatchedActions, initialState, action, expectedActions, done, fail); +} + +export { toNotDispatchActionsWithState }; diff --git a/src/asserts/utils/assertDispatchedActions.js b/src/asserts/utils/assertDispatchedActions.js new file mode 100644 index 0000000..f28f953 --- /dev/null +++ b/src/asserts/utils/assertDispatchedActions.js @@ -0,0 +1,18 @@ +import findIndex from 'lodash.findindex'; +import { notDispatchedActionError } from '../errors/notDispatchedActionError'; + +function assertDispatchedActions(dispatched, expected) { + const availableActions = dispatched.slice(); + + for (let indexInExpected = 0; indexInExpected < expected.length; indexInExpected++) { + const indexInAvailable = findIndex(availableActions, expected[indexInExpected]); + + if (indexInAvailable !== -1) { + availableActions.splice(indexInAvailable, 1); + } else { + throw notDispatchedActionError(dispatched, expected, expected[indexInExpected]); + } + } +} + +export { assertDispatchedActions }; diff --git a/src/asserts/utils/assertNotDispatchedActions.js b/src/asserts/utils/assertNotDispatchedActions.js new file mode 100644 index 0000000..eb463a0 --- /dev/null +++ b/src/asserts/utils/assertNotDispatchedActions.js @@ -0,0 +1,12 @@ +import findIndex from 'lodash.findindex'; +import { dispatchedActionError } from '../errors/dispatchedActionError'; + +function assertNotDispatchedActions(dispatched, expected) { + for (let indexInExpected = 0; indexInExpected < expected.length; indexInExpected++) { + if (findIndex(dispatched, expected[indexInExpected]) !== -1) { + throw dispatchedActionError(dispatched, expected, expected[indexInExpected]); + } + } +} + +export { assertNotDispatchedActions }; diff --git a/src/asserts/utils/getDispatchedActions.js b/src/asserts/utils/getDispatchedActions.js new file mode 100644 index 0000000..84468d4 --- /dev/null +++ b/src/asserts/utils/getDispatchedActions.js @@ -0,0 +1,20 @@ +import getMockStore from '../../mockStore'; + +function getDispatchedActions(initialState, action) { + return new Promise((resolve, reject) => { + const store = getMockStore()(initialState); + const dispatchResult = store.dispatch(action); + + if (dispatchResult instanceof Promise) { + dispatchResult.then(() => { + resolve(store.getActions()); + }).catch((result) => { + reject(result); + }); + } else { + resolve(store.getActions()); + } + }); +} + +export { getDispatchedActions }; diff --git a/src/asserts/utils/performAssertion.js b/src/asserts/utils/performAssertion.js new file mode 100644 index 0000000..00846c3 --- /dev/null +++ b/src/asserts/utils/performAssertion.js @@ -0,0 +1,41 @@ +import { isFunction, isObject } from '../../utils'; +import { getDispatchedActions } from './getDispatchedActions'; +import { unrollActions } from './unrollActions'; + +function performAssertion(assertFunction, initialState, action, expectedActions, done, fail) { + if (!isFunction(action) && !isObject(action)) { + throw new Error( + 'The "action" argument must be a function or an object' + ); + } + + if (!isFunction(expectedActions) && + !isObject(expectedActions) && + !Array.isArray(expectedActions)) { + throw new Error( + 'The "expectedActions" argument must be ' + + 'an action creator function, an action object, or an array of them' + ); + } + + return getDispatchedActions(initialState, action).then((dispatchedActions) => { + return unrollActions(initialState, expectedActions).then((expectedUnrolledActions) => { + assertFunction(dispatchedActions, expectedUnrolledActions); + + if (isFunction(done)) { + done(); + } + }); + }).catch((err) => { + if (isFunction(fail)) { + fail(err); + return; + } else if (isFunction(done)) { + done(err); + return; + } + throw new Error(JSON.stringify(err)); + }); +} + +export { performAssertion }; diff --git a/src/asserts/utils/unrollActions.js b/src/asserts/utils/unrollActions.js new file mode 100644 index 0000000..d5185ec --- /dev/null +++ b/src/asserts/utils/unrollActions.js @@ -0,0 +1,18 @@ +import flattenDeep from 'lodash.flattendeep'; +import { toArray } from '../../utils'; +import { getDispatchedActions } from './getDispatchedActions'; + +function unrollActions(initialState, expectedActions) { + const promises = []; + const actions = toArray(expectedActions); + + for (let index = 0; index < actions.length; index++) { + promises.push(getDispatchedActions(initialState, actions[index])); + } + + return Promise.all(promises).then((resultActions) => { + return flattenDeep(resultActions); + }); +} + +export { unrollActions }; diff --git a/src/initialState.js b/src/initialState.js index 76f75bd..d136e28 100644 --- a/src/initialState.js +++ b/src/initialState.js @@ -17,7 +17,6 @@ function getInitialStoreState() { export { buildInitialStoreState, - registerInitialStoreState + registerInitialStoreState, + getInitialStoreState }; - -export default getInitialStoreState; diff --git a/test/assertions/actionUtils.js b/test/assertions/actionUtils.js deleted file mode 100644 index a4e5eb0..0000000 --- a/test/assertions/actionUtils.js +++ /dev/null @@ -1,112 +0,0 @@ -import expect from 'expect'; -import { - getDispatchedActions, - unrollActions, - assertDispatchedActions -} from '../../src/asserts/actionUtils'; - -describe('assertions', () => { - describe('action utils', () => { - describe('getDispatchedActions', () => { - it('should be function', () => { expect(getDispatchedActions).toBeA('function'); }); - - it('should return a Promise', () => { - const result = getDispatchedActions({}, { type: '' }); - expect(result).toBeA(Promise); - }); - }); - - describe('unrollActions', () => { - function asyncActionCreator() { - return dispatch => { - dispatch({ type: '0-0' }); - dispatch({ type: '0-1' }); - return Promise.resolve().then(() => { - dispatch({ type: '1-0' }); - dispatch({ type: '1-1' }); - return Promise.resolve().then(() => { - dispatch({ type: '2-0' }); - dispatch({ type: '2-1' }); - }); - }); - }; - } - - it('should be function', () => { expect(unrollActions).toBeA('function'); }); - - it('should return flat array with all the actions', () => { - unrollActions({}, asyncActionCreator()).then((result) => { - const expectedActions = [ - { type: '0-0' }, - { type: '0-1' }, - { type: '1-0' }, - { type: '1-1' }, - { type: '2-0' }, - { type: '2-1' } - ]; - expect(result).toBe(expectedActions); - }); - }); - }); - - describe('assertDispatchedActions', () => { - it('should be function', () => { - expect(assertDispatchedActions).toBeA('function'); - }); - - describe('when expected action was not dispatched', () => { - it('should throw an error', () => { - const dispatchedActions = [ - { type: '0-0' }, - { type: '0-1' } - ]; - const expectedActions = [ - { type: '0-0' }, - { type: '10-0' } - ]; - - expect(() => { assertDispatchedActions(dispatchedActions, expectedActions); }) - .toThrow(); - }); - }); - - it('should accept expected duplicate actions', () => { - const dispatchedActions = [ - { type: '0-0' }, - { type: '0-1' }, - { type: '0-0' }, - { type: '0-2' } - ]; - const expectedActions = [ - { type: '0-0' }, - { type: '0-0' }, - { type: '0-1' }, - { type: '0-2' } - ]; - - expect(() => { assertDispatchedActions(dispatchedActions, expectedActions); }) - .toNotThrow(); - }); - - describe('when expected duplicate actions were not dispatched', () => { - it('should throw an error', () => { - const dispatchedActions = [ - { type: '0-0' }, - { type: '0-1' }, - { type: '0-2' }, - { type: '0-3' } - ]; - const expectedActions = [ - { type: '0-0' }, - { type: '0-0' }, - { type: '0-1' }, - { type: '0-2' } - ]; - - expect(() => { assertDispatchedActions(dispatchedActions, expectedActions); }) - .toThrow(); - }); - }); - }); - }); -}); diff --git a/test/assertions/toDispatchActions.js b/test/assertions/toDispatchActions.js deleted file mode 100644 index 90a4182..0000000 --- a/test/assertions/toDispatchActions.js +++ /dev/null @@ -1,39 +0,0 @@ -import expect from 'expect'; -import { toDispatchActions } from '../../src/asserts/toDispatchActions'; -import * as assertions from '../../src/asserts/toDispatchActionsWithState'; -import getInitialStoreState from '../../src/initialState'; - -describe('assertions', () => { - describe('toDispatchActions', () => { - const initialState = getInitialStoreState(); - const actualAction = { actualAction: 'actualAction' }; - const expectedAction = { expectedAction: 'expectedAction' }; - const spyDone = expect.createSpy(); - const spyFail = expect.createSpy(); - const toDispatchActionsWithStateResult = { result: 'result' }; - - beforeEach(() => { - expect.spyOn(assertions, 'toDispatchActionsWithState') - .andReturn(toDispatchActionsWithStateResult); - }); - - afterEach(() => { - expect.restoreSpies(); - }); - - it('should be function', () => { expect(toDispatchActions).toBeA('function'); }); - - it('should call toDispatchActionsWithState with initialState', () => { - toDispatchActions(actualAction, expectedAction, spyDone, spyFail); - - expect(assertions.toDispatchActionsWithState) - .toHaveBeenCalledWith(initialState, actualAction, expectedAction, spyDone, spyFail); - }); - - it('should return result of toDispatchActionsWithState', () => { - const result = toDispatchActions(actualAction, expectedAction, spyDone, spyFail); - - expect(result).toBe(toDispatchActionsWithStateResult); - }); - }); -}); diff --git a/test/asserts/errors/dispatchedActionError.js b/test/asserts/errors/dispatchedActionError.js new file mode 100644 index 0000000..6efb528 --- /dev/null +++ b/test/asserts/errors/dispatchedActionError.js @@ -0,0 +1,10 @@ +import expect from 'expect'; +import { dispatchedActionError } from '../../../src/asserts/errors/dispatchedActionError'; + +describe('assertion errors', () => { + describe('dispatchedActionError', () => { + it('should be function', () => { + expect(dispatchedActionError).toBeA('function'); + }); + }); +}); diff --git a/test/asserts/errors/notDispatchedActionError.js b/test/asserts/errors/notDispatchedActionError.js new file mode 100644 index 0000000..d86ddd8 --- /dev/null +++ b/test/asserts/errors/notDispatchedActionError.js @@ -0,0 +1,10 @@ +import expect from 'expect'; +import { notDispatchedActionError } from '../../../src/asserts/errors/notDispatchedActionError'; + +describe('assertion errors', () => { + describe('notDispatchedActionError', () => { + it('should be function', () => { + expect(notDispatchedActionError).toBeA('function'); + }); + }); +}); diff --git a/test/asserts/toDispatchActions.js b/test/asserts/toDispatchActions.js new file mode 100644 index 0000000..0c84902 --- /dev/null +++ b/test/asserts/toDispatchActions.js @@ -0,0 +1,37 @@ +import expect from 'expect'; +import { toDispatchActions } from '../../src/asserts/toDispatchActions'; +import * as assertions from '../../src/asserts/toDispatchActionsWithState'; +import { getInitialStoreState } from '../../src/initialState'; + +describe('toDispatchActions', () => { + const initialState = getInitialStoreState(); + const actualAction = { actualAction: 'actualAction' }; + const expectedAction = { expectedAction: 'expectedAction' }; + const spyDone = expect.createSpy(); + const spyFail = expect.createSpy(); + const toDispatchActionsWithStateResult = { result: 'result' }; + + beforeEach(() => { + expect.spyOn(assertions, 'toDispatchActionsWithState') + .andReturn(toDispatchActionsWithStateResult); + }); + + afterEach(() => { + expect.restoreSpies(); + }); + + it('should be function', () => { expect(toDispatchActions).toBeA('function'); }); + + it('should call toDispatchActionsWithState with initialState', () => { + toDispatchActions(actualAction, expectedAction, spyDone, spyFail); + + expect(assertions.toDispatchActionsWithState) + .toHaveBeenCalledWith(initialState, actualAction, expectedAction, spyDone, spyFail); + }); + + it('should return result of toDispatchActionsWithState', () => { + const result = toDispatchActions(actualAction, expectedAction, spyDone, spyFail); + + expect(result).toBe(toDispatchActionsWithStateResult); + }); +}); diff --git a/test/asserts/toDispatchActionsWithState.js b/test/asserts/toDispatchActionsWithState.js new file mode 100644 index 0000000..da05124 --- /dev/null +++ b/test/asserts/toDispatchActionsWithState.js @@ -0,0 +1,37 @@ +import expect from 'expect'; +import * as performAssertionObj from '../../src/asserts/utils/performAssertion'; +import * as assertDispatchedActionsObj from '../../src/asserts/utils/assertDispatchedActions'; +import { toDispatchActionsWithState } from '../../src/asserts/toDispatchActionsWithState'; +import { getInitialStoreState } from '../../src/initialState'; + +describe('toDispatchActionsWithState', () => { + const initialState = getInitialStoreState(); + const actualAction = { actualAction: 'actualAction' }; + const expectedAction = { expectedAction: 'expectedAction' }; + const spyDone = expect.createSpy(); + const spyFail = expect.createSpy(); + + beforeEach(() => { + expect.spyOn(performAssertionObj, 'performAssertion'); + expect.spyOn(assertDispatchedActionsObj, 'assertDispatchedActions'); + }); + + afterEach(() => { + expect.restoreSpies(); + }); + + it('should be function', () => { expect(toDispatchActionsWithState).toBeA('function'); }); + + it('should call performAssertion with assertDispatchedActions as first argument', () => { + toDispatchActionsWithState(actualAction, expectedAction, spyDone, spyFail); + + expect(performAssertionObj.performAssertion).toHaveBeenCalledWith( + assertDispatchedActionsObj.assertDispatchedActions, + initialState, + actualAction, + expectedAction, + spyDone, + spyFail + ); + }); +}); diff --git a/test/asserts/toNotDispatchActions.js b/test/asserts/toNotDispatchActions.js new file mode 100644 index 0000000..b00266e --- /dev/null +++ b/test/asserts/toNotDispatchActions.js @@ -0,0 +1,37 @@ +import expect from 'expect'; +import { toNotDispatchActions } from '../../src/asserts/toNotDispatchActions'; +import * as assertions from '../../src/asserts/toNotDispatchActionsWithState'; +import { getInitialStoreState } from '../../src/initialState'; + +describe('toNotDispatchActions', () => { + const initialState = getInitialStoreState(); + const actualAction = { actualAction: 'actualAction' }; + const expectedAction = { expectedAction: 'expectedAction' }; + const spyDone = expect.createSpy(); + const spyFail = expect.createSpy(); + const toNotDispatchActionsWithStateResult = { result: 'result' }; + + beforeEach(() => { + expect.spyOn(assertions, 'toNotDispatchActionsWithState') + .andReturn(toNotDispatchActionsWithStateResult); + }); + + afterEach(() => { + expect.restoreSpies(); + }); + + it('should be function', () => { expect(toNotDispatchActions).toBeA('function'); }); + + it('should call toNotDispatchActionsWithState with initialState', () => { + toNotDispatchActions(actualAction, expectedAction, spyDone, spyFail); + + expect(assertions.toDispatchActionsWithState) + .toHaveBeenCalledWith(initialState, actualAction, expectedAction, spyDone, spyFail); + }); + + it('should return result of toNotDispatchActionsWithState', () => { + const result = toNotDispatchActions(actualAction, expectedAction, spyDone, spyFail); + + expect(result).toBe(toNotDispatchActionsWithStateResult); + }); +}); diff --git a/test/asserts/toNotDispatchActionsWithState.js b/test/asserts/toNotDispatchActionsWithState.js new file mode 100644 index 0000000..20190fd --- /dev/null +++ b/test/asserts/toNotDispatchActionsWithState.js @@ -0,0 +1,37 @@ +import expect from 'expect'; +import * as performAssertionObj from '../../src/asserts/utils/performAssertion'; +import * as assertNotDispatchedActionsObj from '../../src/asserts/utils/assertNotDispatchedActions'; +import { toNotDispatchActionsWithState } from '../../src/asserts/toNotDispatchActionsWithState'; +import { getInitialStoreState } from '../../src/initialState'; + +describe('toNotDispatchActionsWithState', () => { + const initialState = getInitialStoreState(); + const actualAction = { actualAction: 'actualAction' }; + const expectedAction = { expectedAction: 'expectedAction' }; + const spyDone = expect.createSpy(); + const spyFail = expect.createSpy(); + + beforeEach(() => { + expect.spyOn(performAssertionObj, 'performAssertion'); + expect.spyOn(assertNotDispatchedActionsObj, 'assertNotDispatchedActions'); + }); + + afterEach(() => { + expect.restoreSpies(); + }); + + it('should be function', () => { expect(toNotDispatchActionsWithState).toBeA('function'); }); + + it('should call performAssertion with assertNotDispatchedActions as first argument', () => { + toNotDispatchActionsWithState(actualAction, expectedAction, spyDone, spyFail); + + expect(performAssertionObj.performAssertion).toHaveBeenCalledWith( + assertNotDispatchedActionsObj.assertNotDispatchedActions, + initialState, + actualAction, + expectedAction, + spyDone, + spyFail + ); + }); +}); diff --git a/test/asserts/utils/assertDispatchedActions.js b/test/asserts/utils/assertDispatchedActions.js new file mode 100644 index 0000000..108cfb4 --- /dev/null +++ b/test/asserts/utils/assertDispatchedActions.js @@ -0,0 +1,64 @@ +import expect from 'expect'; +import { assertDispatchedActions } from '../../../src/asserts/utils/assertDispatchedActions'; + +describe('assertion utils', () => { + describe('assertDispatchedActions', () => { + it('should be function', () => { + expect(assertDispatchedActions).toBeA('function'); + }); + + describe('when expected action was not dispatched', () => { + it('should throw an error', () => { + const dispatchedActions = [ + { type: '0-0' }, + { type: '0-1' } + ]; + const expectedActions = [ + { type: '0-0' }, + { type: '10-0' } + ]; + + expect(() => { assertDispatchedActions(dispatchedActions, expectedActions); }) + .toThrow(); + }); + }); + + it('should accept expected duplicate actions', () => { + const dispatchedActions = [ + { type: '0-0' }, + { type: '0-1' }, + { type: '0-0' }, + { type: '0-2' } + ]; + const expectedActions = [ + { type: '0-0' }, + { type: '0-0' }, + { type: '0-1' }, + { type: '0-2' } + ]; + + expect(() => { assertDispatchedActions(dispatchedActions, expectedActions); }) + .toNotThrow(); + }); + + describe('when expected duplicate actions were not dispatched', () => { + it('should throw an error', () => { + const dispatchedActions = [ + { type: '0-0' }, + { type: '0-1' }, + { type: '0-2' }, + { type: '0-3' } + ]; + const expectedActions = [ + { type: '0-0' }, + { type: '0-0' }, + { type: '0-1' }, + { type: '0-2' } + ]; + + expect(() => { assertDispatchedActions(dispatchedActions, expectedActions); }) + .toThrow(); + }); + }); + }); +}); diff --git a/test/asserts/utils/assertNotDispatchedActions.js b/test/asserts/utils/assertNotDispatchedActions.js new file mode 100644 index 0000000..1e72962 --- /dev/null +++ b/test/asserts/utils/assertNotDispatchedActions.js @@ -0,0 +1,60 @@ +import expect from 'expect'; +import { assertNotDispatchedActions } from '../../../src/asserts/utils/assertNotDispatchedActions'; + +describe('assertion utils', () => { + describe('assertNotDispatchedActions', () => { + it('should be function', () => { + expect(assertNotDispatchedActions).toBeA('function'); + }); + + describe('when expected action was dispatched', () => { + it('should throw an error', () => { + const dispatchedActions = [ + { type: '0-0' }, + { type: '0-1' } + ]; + const expectedActions = [ + { type: '0-0' }, + { type: '10-0' } + ]; + + expect(() => { assertNotDispatchedActions(dispatchedActions, expectedActions); }) + .toThrow(); + }); + }); + + it('should accept expected duplicate actions', () => { + const dispatchedActions = [ + { type: '0-0' }, + { type: '0-1' }, + { type: '0-0' }, + { type: '0-2' } + ]; + const expectedActions = [ + { type: '0-3' }, + { type: '0-3' } + ]; + + expect(() => { assertNotDispatchedActions(dispatchedActions, expectedActions); }) + .toNotThrow(); + }); + + describe('when expected duplicate actions were dispatched', () => { + it('should throw an error', () => { + const dispatchedActions = [ + { type: '0-0' }, + { type: '0-1' }, + { type: '0-2' }, + { type: '0-3' } + ]; + const expectedActions = [ + { type: '0-3' }, + { type: '0-3' } + ]; + + expect(() => { assertNotDispatchedActions(dispatchedActions, expectedActions); }) + .toThrow(); + }); + }); + }); +}); diff --git a/test/asserts/utils/getDispatchedActions.js b/test/asserts/utils/getDispatchedActions.js new file mode 100644 index 0000000..71f75c3 --- /dev/null +++ b/test/asserts/utils/getDispatchedActions.js @@ -0,0 +1,13 @@ +import expect from 'expect'; +import { getDispatchedActions } from '../../../src/asserts/utils/getDispatchedActions'; + +describe('assertion utils', () => { + describe('getDispatchedActions', () => { + it('should be function', () => { expect(getDispatchedActions).toBeA('function'); }); + + it('should return a Promise', () => { + const result = getDispatchedActions({}, { type: '' }); + expect(result).toBeA(Promise); + }); + }); +}); diff --git a/test/assertions/toDispatchActionsWithState.js b/test/asserts/utils/performAssertion.js similarity index 52% rename from test/assertions/toDispatchActionsWithState.js rename to test/asserts/utils/performAssertion.js index 49b7f64..c9b7f19 100644 --- a/test/assertions/toDispatchActionsWithState.js +++ b/test/asserts/utils/performAssertion.js @@ -1,10 +1,12 @@ import expect from 'expect'; -import * as actionUtils from '../../src/asserts/actionUtils'; -import { toDispatchActionsWithState } from '../../src/asserts/toDispatchActionsWithState'; -import getInitialStoreState from '../../src/initialState'; - -describe('assertions', () => { - describe('toDispatchActionsWithState', () => { +import { getInitialStoreState } from '../../../src/initialState'; +import { performAssertion } from '../../../src/asserts/utils/performAssertion'; +import * as getDispatchedActionsObj from '../../../src/asserts/utils/getDispatchedActions'; +import * as unrollActionsObj from '../../../src/asserts/utils/unrollActions'; + +describe('assertion utils', () => { + describe('performAction', () => { + const testAssertFunction = expect.createSpy(); const initialState = getInitialStoreState(); const actualAction = { actualAction: 'actualAction' }; const expectedAction = { expectedAction: 'expectedAction' }; @@ -14,19 +16,21 @@ describe('assertions', () => { expect.restoreSpies(); }); - it('should be function', () => { expect(toDispatchActionsWithState).toBeA('function'); }); + it('should be function', () => { expect(performAssertion).toBeA('function'); }); - describe('when "actualAction" is not a function or an object', () => { + describe('when "action" is not a function or an object', () => { it('should throw Error', () => { - expect(() => { toDispatchActionsWithState(initialState, 'test', expectedAction, spyDone); }) - .toThrow(); + expect(() => { + performAssertion(testAssertFunction, initialState, 'test', expectedAction, spyDone); + }).toThrow(); }); }); describe('when "expectedActions" not a function, not an object and not an array', () => { it('should throw Error', () => { - expect(() => { toDispatchActionsWithState(initialState, actualAction, 'test', spyDone); }) - .toThrow(); + expect(() => { + performAssertion(testAssertFunction, initialState, actualAction, 'test', spyDone); + }).toThrow(); }); }); @@ -35,13 +39,13 @@ describe('assertions', () => { let unrollActionsSpy; beforeEach(() => { - getDispatchedActionsSpy = expect.spyOn(actionUtils, 'getDispatchedActions'); - unrollActionsSpy = expect.spyOn(actionUtils, 'unrollActions'); + getDispatchedActionsSpy = expect.spyOn(getDispatchedActionsObj, 'getDispatchedActions'); + unrollActionsSpy = expect.spyOn(unrollActionsObj, 'unrollActions'); }); it('should call getDispatchedActions with initialState and action', () => { getDispatchedActionsSpy.andReturn(Promise.reject()); - toDispatchActionsWithState(initialState, actualAction, [expectedAction], spyDone); + performAssertion(testAssertFunction, initialState, actualAction, [expectedAction], spyDone); expect(getDispatchedActionsSpy) .toHaveBeenCalledWith(initialState, actualAction); @@ -53,43 +57,43 @@ describe('assertions', () => { getDispatchedActionsSpy.andReturn(Promise.reject(err)); }); - describe('when fail callback is passed', (done) => { + describe('when fail callback is spicified', (done) => { const spyFail = expect.createSpy(); it('should call fail callback with rejection reason as argument', () => { - toDispatchActionsWithState( + performAssertion( + testAssertFunction, initialState, actualAction, expectedAction, spyDone, spyFail ).catch(() => { - expect(spyFail) - .toHaveBeenCalledWith(err); + expect(spyFail).toHaveBeenCalledWith(err); done(); }); }); }); - describe('when fail callback is not passed', (done) => { + describe('when fail callback is not spicified', (done) => { it('should call done callback with rejection reason as argument', () => { - toDispatchActionsWithState( + performAssertion( + testAssertFunction, initialState, actualAction, expectedAction, spyDone ).catch(() => { - expect(spyDone) - .toHaveBeenCalledWith(err); + expect(spyDone).toHaveBeenCalledWith(err); done(); }); }); }); - describe('when done callback is passed', (done) => { + describe('when done callback is spicified', (done) => { it('should throw Error with with rejection reason as json', () => { try { - toDispatchActionsWithState(initialState, actualAction, expectedAction); + performAssertion(testAssertFunction, initialState, actualAction, expectedAction); } catch (error) { expect(error).toBe(JSON.stringify(error)); done(); @@ -105,14 +109,14 @@ describe('assertions', () => { }); it('should call unrollActions with initialState and expectedActions', (done) => { - toDispatchActionsWithState( + performAssertion( + testAssertFunction, initialState, actualAction, expectedAction, spyDone ).then(() => { - expect(unrollActionsSpy) - .toHaveBeenCalledWith(initialState, expectedAction); + expect(unrollActionsSpy).toHaveBeenCalledWith(initialState, expectedAction); done(); }); @@ -120,26 +124,34 @@ describe('assertions', () => { describe('when unrollActions rosolves', () => { const unrolledActions = [{ type: 'testAction' }]; - let assertDispatchedActionsSpy; beforeEach(() => { unrollActionsSpy.andReturn(Promise.resolve(unrolledActions)); - assertDispatchedActionsSpy = expect.spyOn(actionUtils, 'assertDispatchedActions'); }); - it('should call assertDispatchedActions with actual and expected actions', () => { - toDispatchActionsWithState(initialState, actualAction, expectedAction, spyDone) - .then(() => { - expect(assertDispatchedActionsSpy) - .toHaveBeenCalledWith(dispatchedActions, unrolledActions); - }); + it('should call assertionFunction with actual and expected actions', () => { + performAssertion( + testAssertFunction, + initialState, + actualAction, + expectedAction, + spyDone + ).then(() => { + expect(testAssertFunction) + .toHaveBeenCalledWith(dispatchedActions, unrolledActions); + }); }); - it('should call done callback if it passed', () => { - toDispatchActionsWithState(initialState, actualAction, expectedAction, spyDone) - .then(() => { - expect(spyDone).toHaveBeenCalled(); - }); + it('should call done callback if it spicified', () => { + performAssertion( + testAssertFunction, + initialState, + actualAction, + expectedAction, + spyDone + ).then(() => { + expect(spyDone).toHaveBeenCalled(); + }); }); }); }); diff --git a/test/asserts/utils/unrollActions.js b/test/asserts/utils/unrollActions.js new file mode 100644 index 0000000..9669035 --- /dev/null +++ b/test/asserts/utils/unrollActions.js @@ -0,0 +1,37 @@ +import expect from 'expect'; +import { unrollActions } from '../../../src/asserts/utils/unrollActions'; + +describe('assertion utils', () => { + describe('unrollActions', () => { + function asyncActionCreator() { + return dispatch => { + dispatch({ type: '0-0' }); + dispatch({ type: '0-1' }); + return Promise.resolve().then(() => { + dispatch({ type: '1-0' }); + dispatch({ type: '1-1' }); + return Promise.resolve().then(() => { + dispatch({ type: '2-0' }); + dispatch({ type: '2-1' }); + }); + }); + }; + } + + it('should be function', () => { expect(unrollActions).toBeA('function'); }); + + it('should return flat array with all the actions', () => { + unrollActions({}, asyncActionCreator()).then((result) => { + const expectedActions = [ + { type: '0-0' }, + { type: '0-1' }, + { type: '1-0' }, + { type: '1-1' }, + { type: '2-0' }, + { type: '2-1' } + ]; + expect(result).toBe(expectedActions); + }); + }); + }); +}); From 92582f6f38f12f1a165ca54d0defb721e6dbe4c7 Mon Sep 17 00:00:00 2001 From: Dmitry Zaets Date: Sat, 4 Jun 2016 22:05:53 +0200 Subject: [PATCH 2/9] Add chai support for "not" statements --- src/chai.js | 23 ++++++++++- test/chai/index.js | 98 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/chai.js b/src/chai.js index 188602e..5b2adc0 100644 --- a/src/chai.js +++ b/src/chai.js @@ -18,20 +18,37 @@ function registerAssertions() { const state = utils.flag(this, 'state'); if (state) { + if (utils.flag(this, 'negate')) { + return assertions.toNotDispatchActionsWithState(state, this._obj, expectedActions, done); + } return assertions.toDispatchActionsWithState(state, this._obj, expectedActions, done); } + if (utils.flag(this, 'negate')) { + return assertions.toNotDispatchActions(this._obj, expectedActions, done); + } return assertions.toDispatchActions(this._obj, expectedActions, done); } + function isDispatching(actualAction, expectedActions, done) { + new _chai.Assertion(actualAction) + .to.dispatch.actions(expectedActions, done); + } + function isDispatchingWithState(actualAction, expectedActions, state, done) { new _chai.Assertion(actualAction) .with.state(state) .to.dispatch.actions(expectedActions, done); } - function isDispatching(actualAction, expectedActions, done) { + function isNotDispatching(actualAction, expectedActions, done) { new _chai.Assertion(actualAction) - .to.dispatch.actions(expectedActions, done); + .to.not.dispatch.actions(expectedActions, done); + } + + function isNotDispatchingWithState(actualAction, expectedActions, state, done) { + new _chai.Assertion(actualAction) + .with.state(state) + .to.not.dispatch.actions(expectedActions, done); } _chai.Assertion.addChainableMethod('state', stateMethod); @@ -39,6 +56,8 @@ function registerAssertions() { _chai.Assertion.addMethod('actions', dispatchActionsMethod); _chai.assert.isDispatching = isDispatching; _chai.assert.isDispatchingWithState = isDispatchingWithState; + _chai.assert.isNotDispatching = isNotDispatching; + _chai.assert.isNotDispatchingWithState = isNotDispatchingWithState; }); } diff --git a/test/chai/index.js b/test/chai/index.js index eca2fd6..3386041 100644 --- a/test/chai/index.js +++ b/test/chai/index.js @@ -45,6 +45,28 @@ describe('chai', () => { .to.dispatch.actions(actions.expectedParentActions, done); }); }); + + describe('.not.dispatch.actions', () => { + it('should accept single action', (done) => { + expect(actions.start()) + .to.not.dispatch.actions(actions.anotherStart(), done); + }); + + it('should accept array with one action', (done) => { + expect(actions.start()) + .to.not.dispatch.actions([actions.anotherStart()], done); + }); + + it('should accept array with multiple actions', (done) => { + expect(actions.asyncActionCreator()) + .to.not.dispatch.actions(actions.anotherExpectedActions, done); + }); + + it('should accept array with nested async action creators', (done) => { + expect(actions.parentAsyncActionCreator()) + .to.not.dispatch.actions(actions.anotherParentExpectedActions, done); + }); + }); }); describe('should', () => { @@ -83,6 +105,28 @@ describe('chai', () => { .dispatch.actions(actions.expectedParentActions, done); }); }); + + describe('.not.dispath.actions', () => { + it('should accept single action', (done) => { + actions.start().should + .not.dispatch.actions(actions.anotherStart(), done); + }); + + it('should accept array with one action', (done) => { + actions.start().should + .not.dispatch.actions([actions.anotherStart()], done); + }); + + it('should accept array with multiple actions', (done) => { + actions.asyncActionCreator().should + .not.dispatch.actions(actions.anotherExpectedActions, done); + }); + + it('should accept array with nested async action creators', (done) => { + actions.parentAsyncActionCreator().should + .not.dispatch.actions(actions.anotherParentExpectedActions, done); + }); + }); }); describe('assert', () => { @@ -106,6 +150,26 @@ describe('chai', () => { }); }); + describe('isNotDispatchingWithState', () => { + it('should accept object as third argument', (done) => { + assert.isNotDispatchingWithState( + actions.actionCreatorWithGetState(), + actions.actionWithGetState({ property: 'anotherParentAsyncActionCreator' }), + { property: 'value' }, + done + ); + }); + + it('should accept function as third argument', (done) => { + assert.isNotDispatchingWithState( + actions.actionCreatorWithGetState(), + actions.actionWithGetState({ property: 'anotherValue' }), + () => { return { property: 'value' }; }, + done + ); + }); + }); + describe('isDispatching', () => { it('should accept single action', (done) => { assert.isDispatching( @@ -139,5 +203,39 @@ describe('chai', () => { ); }); }); + + describe('isNotDispatching', () => { + it('should accept single action', (done) => { + assert.isNotDispatching( + actions.start(), + actions.anotherStart(), + done + ); + }); + + it('should accept array with one action', (done) => { + assert.isNotDispatching( + actions.start(), + [actions.anotherStart()], + done + ); + }); + + it('should accept array with multiple actions', (done) => { + assert.isNotDispatching( + actions.asyncActionCreator(), + actions.anotherExpectedActions, + done + ); + }); + + it('should accept array with nested async action creators', (done) => { + assert.isNotDispatching( + actions.parentAsyncActionCreator(), + actions.anotherParentExpectedActions, + done + ); + }); + }); }); }); From 11608abc0acf9beeff801042bee1430c27054080 Mon Sep 17 00:00:00 2001 From: Dmitry Zaets Date: Sat, 4 Jun 2016 22:07:19 +0200 Subject: [PATCH 3/9] Add expect support for "not" statements --- src/expect.js | 8 ++++++++ test/expect/index.js | 20 ++++++++++++++++++++ test/general/initialState.js | 5 +++-- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/expect.js b/src/expect.js index 8325873..77ceff2 100644 --- a/src/expect.js +++ b/src/expect.js @@ -13,9 +13,17 @@ function toDispatchActions(expectedActions, done) { return assertions.toDispatchActions(this.actual, expectedActions, done); } +function toNotDispatchActions(expectedActions, done) { + if (this.state) { + return assertions.toNotDispatchActionsWithState(this.state, this.actual, expectedActions, done); + } + return assertions.toNotDispatchActions(this.actual, expectedActions, done); +} + function registerAssertions() { expect.extend({ toDispatchActions, + toNotDispatchActions, withState }); } diff --git a/test/expect/index.js b/test/expect/index.js index 23ca13e..09317d0 100644 --- a/test/expect/index.js +++ b/test/expect/index.js @@ -41,4 +41,24 @@ describe('expect', () => { .toDispatchActions(actions.expectedParentActions, done); }); }); + + describe('.toNotDispatchActions', () => { + it('should accept single action', (done) => { + expect(actions.start()).toNotDispatchActions(actions.anotherStart(), done); + }); + + it('should accept array with one action', (done) => { + expect(actions.start()).toNotDispatchActions([actions.anotherStart()], done); + }); + + it('should accept array with multiple actions', (done) => { + expect(actions.asyncActionCreator()) + .toNotDispatchActions(actions.anotherExpectedActions, done); + }); + + it('should accept array with nested async action creators', (done) => { + expect(actions.parentAsyncActionCreator()) + .toNotDispatchActions(actions.anotherParentExpectedActions, done); + }); + }); }); diff --git a/test/general/initialState.js b/test/general/initialState.js index a642d7e..94332e5 100644 --- a/test/general/initialState.js +++ b/test/general/initialState.js @@ -1,7 +1,8 @@ import expect from 'expect'; -import getInitialStoreState, { +import { buildInitialStoreState, - registerInitialStoreState + registerInitialStoreState, + getInitialStoreState } from '../../src/initialState'; import { expectedInitialState, reducerWithNesterReducers } from '../testingData/reducers'; From 72fbfb001cb3be8e104972db0c5a56358a672fa0 Mon Sep 17 00:00:00 2001 From: Dmitry Zaets Date: Sat, 4 Jun 2016 22:07:46 +0200 Subject: [PATCH 4/9] Add expect.js support for "not" statement --- src/expectjs.js | 12 ++++++++++-- test/expectjs/index.js | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/expectjs.js b/src/expectjs.js index 2aca59c..71641a2 100644 --- a/src/expectjs.js +++ b/src/expectjs.js @@ -10,9 +10,17 @@ function dispatchActions(expectedActions, done) { if (this.obj instanceof ActionWithInitialState) { const action = this.obj.action; const state = this.obj.state; - assertions.toDispatchActionsWithState(state, action, expectedActions, done); + if (this.flags.not) { + assertions.toNotDispatchActionsWithState(state, action, expectedActions, done); + } else { + assertions.toDispatchActionsWithState(state, action, expectedActions, done); + } } else { - assertions.toDispatchActions(this.obj, expectedActions, done); + if (this.flags.not) { + assertions.toNotDispatchActions(this.obj, expectedActions, done); + } else { + assertions.toDispatchActions(this.obj, expectedActions, done); + } } } diff --git a/test/expectjs/index.js b/test/expectjs/index.js index f033a31..177908e 100644 --- a/test/expectjs/index.js +++ b/test/expectjs/index.js @@ -41,4 +41,24 @@ describe('expect.js', () => { .to.dispatchActions(actions.expectedParentActions, done); }); }); + + describe('not.dispatchActions', () => { + it('should accept single action', (done) => { + expect(actions.start()).to.not.dispatchActions(actions.anotherStart(), done); + }); + + it('should accept array with one action', (done) => { + expect(actions.start()).to.not.dispatchActions([actions.anotherStart()], done); + }); + + it('should accept array with multiple actions', (done) => { + expect(actions.asyncActionCreator()) + .to.not.dispatchActions(actions.anotherExpectedActions, done); + }); + + it('should accept array with nested async action creators', (done) => { + expect(actions.parentAsyncActionCreator()) + .to.not.dispatchActions(actions.anotherParentExpectedActions, done); + }); + }); }); From bf16a48ca041fcfc6934148ecfc7269ec7a4a1bc Mon Sep 17 00:00:00 2001 From: Dmitry Zaets Date: Sat, 4 Jun 2016 22:07:58 +0200 Subject: [PATCH 5/9] Add javascript assertions tests --- test/assertions/index.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/assertions/index.js diff --git a/test/assertions/index.js b/test/assertions/index.js new file mode 100644 index 0000000..37a9b7a --- /dev/null +++ b/test/assertions/index.js @@ -0,0 +1,20 @@ +import expect from 'expect'; +import assertions from '../../src/assertions'; + +describe('assertions', () => { + it('should export toDispatchActions', () => { + expect(assertions.toDispatchActions).toBeA('function'); + }); + + it('should export toDispatchActionsWithState', () => { + expect(assertions.toDispatchActionsWithState).toBeA('function'); + }); + + it('should export toNotDispatchActions', () => { + expect(assertions.toNotDispatchActions).toBeA('function'); + }); + + it('should export toNotDispatchActionsWithState', () => { + expect(assertions.toNotDispatchActionsWithState).toBeA('function'); + }); +}); From f3386affd4e70725d4c640a19bee4851e076f033 Mon Sep 17 00:00:00 2001 From: Dmitry Zaets Date: Sat, 4 Jun 2016 22:08:14 +0200 Subject: [PATCH 6/9] Add should support for "not" statements --- src/should.js | 27 ++++++++++++++++++++++++--- test/should/index.js | 25 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/should.js b/src/should.js index 1e79a4a..313d66d 100644 --- a/src/should.js +++ b/src/should.js @@ -6,20 +6,41 @@ function withState(state) { this.obj = new ActionWithInitialState(this.obj, state); } -function dispatchActions(expectedActions, done) { +function dispatchActionsFunction(assert, assertWithState, expectedActions, done) { if (this.obj instanceof ActionWithInitialState) { const action = this.obj.action; const state = this.obj.state; - assertions.toDispatchActionsWithState(state, action, expectedActions, done); + assertWithState(state, action, expectedActions, done); } else { - assertions.toDispatchActions(this.obj, expectedActions, done); + assert(this.obj, expectedActions, done); } } +function dispatchActions(expectedActions, done) { + dispatchActionsFunction.call( + this, + assertions.toDispatchActions, + assertions.toDispatchActionsWithState, + expectedActions, + done + ); +} + +function notDispatchActions(expectedActions, done) { + dispatchActionsFunction.call( + this, + assertions.toNotDispatchActions, + assertions.toNotDispatchActionsWithState, + expectedActions, + done + ); +} + function registerAssertions() { should.Assertion.add('withState', withState); should.Assertion.alias('withState', 'state'); should.Assertion.add('dispatchActions', dispatchActions); + should.Assertion.add('notDispatchActions', notDispatchActions); } export { diff --git a/test/should/index.js b/test/should/index.js index 340a13d..11f7b8c 100644 --- a/test/should/index.js +++ b/test/should/index.js @@ -58,4 +58,29 @@ describe('should', () => { .dispatchActions(actions.expectedParentActions, done); }); }); + + describe('.notDispatchActions', () => { + it('should accept single action', (done) => { + should(actions.start()).notDispatchActions(actions.anotherStart(), done); + }); + + it('should accept array with one action', (done) => { + should(actions.start()).notDispatchActions([actions.anotherStart()], done); + }); + + it('should accept array with multiple actions', (done) => { + should(actions.asyncActionCreator()) + .notDispatchActions(actions.anotherExpectedActions, done); + }); + + it('should accept array with nested async action creators', (done) => { + should(actions.parentAsyncActionCreator()) + .notDispatchActions(actions.anotherParentExpectedActions, done); + }); + + it('should work with .should', (done) => { + actions.parentAsyncActionCreator().should + .notDispatchActions(actions.anotherParentExpectedActions, done); + }); + }); }); From f1ade24cfff72b7de28f30b03606d789d57429bd Mon Sep 17 00:00:00 2001 From: Dmitry Zaets Date: Sat, 4 Jun 2016 22:08:43 +0200 Subject: [PATCH 7/9] Add new test data --- test/testingData/actions.js | 50 ++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/test/testingData/actions.js b/test/testingData/actions.js index 1b5b021..b4504c3 100644 --- a/test/testingData/actions.js +++ b/test/testingData/actions.js @@ -43,6 +43,37 @@ function parentAsyncActionCreator() { }; } +const anotherAsyncStart = () => { return { type: 'another-test-action-start' }; }; +const anotherAsyncFinish = () => { return { type: 'another-test-action-finish' }; }; +const anotherAsyncFail = () => { return { type: 'another-test-action-fail' }; }; + +function anotherAsyncActionCreator() { + return dispatch => { + dispatch(anotherAsyncStart()); + return asyncFunction().then(() => { + dispatch(anotherAsyncFinish()); + }).catch(() => { + dispatch(anotherAsyncFail()); + }); + }; +} + +const anotherParentAsyncStart = () => { return { type: 'another-parent-test-action-start' }; }; +const anotherParentAsyncFinish = () => { return { type: 'another-parent-test-action-finish' }; }; +const anotherParentAsyncFail = () => { return { type: 'another-parent-test-action-fail' }; }; + +function anotherParentAsyncActionCreator() { + return dispatch => { + dispatch(anotherParentAsyncStart()); + return asyncFunction().then(() => { + dispatch(anotherAsyncActionCreator()); + dispatch(anotherParentAsyncFinish()); + }, () => { + dispatch(anotherParentAsyncFail()); + }); + }; +} + const expectedActions = [ start(), anotherStart(), @@ -55,6 +86,19 @@ const expectedParentActions = [ parentFinish() ]; + +const anotherExpectedActions = [ + anotherAsyncStart(), + anotherAsyncFinish() +]; + +const anotherParentExpectedActions = [ + anotherParentAsyncStart(), + anotherAsyncActionCreator(), + anotherParentAsyncFail() +]; + + export default { start, anotherStart, @@ -68,5 +112,9 @@ export default { parentFail, parentAsyncActionCreator, expectedActions, - expectedParentActions + expectedParentActions, + anotherAsyncActionCreator, + anotherParentAsyncActionCreator, + anotherExpectedActions, + anotherParentExpectedActions }; From 5a2b695af11ae91b3dfa39cee41f19d5349c76de Mon Sep 17 00:00:00 2001 From: Dmitry Zaets Date: Sat, 4 Jun 2016 22:09:21 +0200 Subject: [PATCH 8/9] Change general tests path --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cdaf05c..e851c8c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "Assertions for redux actions testing", "scripts": { "lint": "eslint src test", - "test:general": "mocha --compilers js:babel-register --reporter spec test/general/*.js test/assertions/*.js", + "test:general": "mocha --compilers js:babel-register --reporter spec test/general/*.js test/assertions/*.js test/asserts/**/*.js", "test:chai": "mocha --compilers js:babel-register --reporter spec test/chai/*.js", "test:expect": "mocha --compilers js:babel-register --reporter spec test/expect/*.js", "test:expectjs": "mocha --compilers js:babel-register --reporter spec test/expectjs/*.js", From 9915548cf4830eccdfe0f0877f2ac8ff2f25e8f6 Mon Sep 17 00:00:00 2001 From: Dmitry Zaets Date: Sat, 4 Jun 2016 22:29:33 +0200 Subject: [PATCH 9/9] Split readme and move to docs folder. Update docs with "not" statement. --- README.md | 358 +++++++------------------------------------ docs/README.md | 22 +++ docs/SUMMARY.md | 12 ++ docs/chai.md | 102 ++++++++++++ docs/expect.md | 50 ++++++ docs/expectjs.md | 54 +++++++ docs/installation.md | 46 ++++++ docs/javascript.md | 59 +++++++ docs/should.md | 75 +++++++++ docs/what_it_does.md | 106 +++++++++++++ 10 files changed, 584 insertions(+), 300 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/SUMMARY.md create mode 100644 docs/chai.md create mode 100644 docs/expect.md create mode 100644 docs/expectjs.md create mode 100644 docs/installation.md create mode 100644 docs/javascript.md create mode 100644 docs/should.md create mode 100644 docs/what_it_does.md diff --git a/README.md b/README.md index 53da08d..a661061 100644 --- a/README.md +++ b/README.md @@ -7,65 +7,19 @@ It use [redux-mock-store](https://github.com/arnaudbenard/redux-mock-store) to m [![build status](https://img.shields.io/travis/dmitry-zaets/redux-actions-assertions/master.svg?style=flat-square)](https://travis-ci.org/dmitry-zaets/redux-actions-assertions) [![npm version](https://img.shields.io/npm/v/redux-actions-assertions.svg?style=flat-square)](https://www.npmjs.com/package/redux-actions-assertions) -## What it does: -- [Simplifies initial setup](#simplifies-initial-setup); -- [Reduces repetitive code of test methods](#reduces-repetitive-code-of-test-methods); -- [Allows to avoid re-testing nested action creators](#allows-to-avoid-re-testing-nested-action-creators); - ## Supported Assertion Frameworks/Libraries: -- [chai](#chai) -- [expect](#expect) -- [expect.js](#expectjs) -- [should](#should) +- [chai](http://dmitry.js.org/redux-actions-assertions/chai.html) +- [expect](http://dmitry.js.org/redux-actions-assertions/expect.html) +- [expect.js](http://dmitry.js.org/redux-actions-assertions/expectjs.html) +- [should](http://dmitry.js.org/redux-actions-assertions/should.html) +- [pure javascript assertion](http://dmitry.js.org/redux-actions-assertions/javascript.html) -If you have not found assertion framework/library that you are using - you can use [pure javascript assertion](#javascript) or create an issue. +If you have not found assertion framework/library that you are using - please add comment into [this issue](https://github.com/dmitry-zaets/redux-actions-assertions/issues/3). -### Simplifies initial setup -It provides singe-time global configuration for middlewares and initial store state. - -Without: -```javascript -const middlewares = [thunk]; -const mockStore = configureStore(middlewares); -const store = mockStore({ /*initial store object*}); -``` -With: -```javascript -registerMiddlewares([ thunk ]); -// to set custom initial state -registerInitialStoreState(/*object of function*/); -// to generate initial state of your application -registerInitialStoreState(buildInitialStoreState(/*your root reducer*/)); -``` - -### Reduces repetitive code of test methods -It reduces boilerplate of test methods and makes testing fluent. - -Without: -```javascript -const store = mockStore(/* initial state */); -const expectedActions = [ - { type: types.FETCH_TODOS_REQUEST }, - /* All expected triggered action objects */ -]; -store.dispatch(fetchData()).then(() => { - const actions = store.getActions(); - expect(actions).toEqual(expectedActions); -}).then(done).catch(done); -``` - -With: -```javascript -const expectedActions = [ - /*All expected triggered action objects or action creator functions*/ -]; -expect(fetchData()).toDispatchActions(expectedActions, done); -``` - -With using customised store state: -```javascript -expect(fetchData()).withState({/*custom state*/}).toDispatchActions(expectedActions, done); -``` +## What it does: +- [Allows to avoid retesting nested action creators](#allows-to-avoid-retesting-nested-action-creators); +- [Reduces repetitive code of test methods](#reduces-repetitive-code-of-test-methods); +- [Simplifies initial setup](#simplifies-initial-setup); ### Allows to avoid re-testing nested action creators It allows to test only actions that need to be tested. @@ -125,6 +79,53 @@ expect(actionA()).withState({ todos: [] }).toDispatch([ ], done); ``` +### Reduces repetitive code of test methods +It reduces boilerplate of test methods and makes testing fluent. + +Without: +```javascript +const store = mockStore(/* initial state */); +const expectedActions = [ + { type: types.FETCH_TODOS_REQUEST }, + /* All expected triggered action objects */ +]; +store.dispatch(fetchData()).then(() => { + const actions = store.getActions(); + expect(actions).toEqual(expectedActions); +}).then(done).catch(done); +``` + +With: +```javascript +const expectedActions = [ + /*All expected triggered action objects or action creator functions*/ +]; +expect(fetchData()).toDispatchActions(expectedActions, done); +``` + +With using customised store state: +```javascript +expect(fetchData()).withState({/*custom state*/}).toDispatchActions(expectedActions, done); +``` + +### Simplifies initial setup +It provides singe-time global configuration for middlewares and initial store state. + +Without: +```javascript +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); +const store = mockStore({ /*initial store object*}); +``` +With: +```javascript +registerMiddlewares([ thunk ]); +// to set custom initial state +registerInitialStoreState(/*object of function*/); +// to generate initial state of your application +registerInitialStoreState(buildInitialStoreState(/*your root reducer*/)); +``` + ## Installation Using [npm](https://www.npmjs.org/): @@ -170,247 +171,4 @@ var registerInitialStoreState = reduxActionsAssertions.registerInitialStoreState // registration registerInitialStoreState(buildInitialStoreState(/* root reducer function */)); -``` - -## javascript - -### Registration - -For plain javasript assertions you dont need to register anything. Just import assertions in your tests: - -```js -// using ES6 modules -import assertions from 'redux-actions-assertions/assertions'; - -// using CommonJS modules -var assertions = require('redux-actions-assertions/assertions'); - -// in test -assertions.toDispatchActions(/**/) -assertions.toDispatchActionsWithState(/**/); -``` - -### Usage - -#### toDispatchActions -> `toDispatchActions(action, expectedActions, callback)` - -Asserts that when given `action` is dispatched it will dispatch `expectedActions`. `action` can be a plain object (action) or a function (action creator). `expectedActions` can be can be a plain object (action), a function (action creator), or an array of objects/functions. - -```js -toDispatchActions(testActionCreator(), [{ type: 'MY_ACTION_START' }], callback); -``` - -#### toDispatchActionsWithState - -> `toDispatchActionsWithState(initialState, action, expectedActions, callback)` - -Same as `toDispatchActions` + asserts that store initialised with `state` before `action` is dispatched. - -```js -toDispatchActions({property: 'value'}, testActionCreator(), [{ type: 'MY_ACTION_START' }], callback); -``` - -## [chai](https://github.com/chaijs/chai) - -### Registration - -```js -// using ES6 modules -import { registerAssertions } from 'redux-actions-assertions/chai'; - -// using CommonJS modules -var registerAssertions = require('redux-actions-assertions/chai').registerAssertions; - -// registration -registerAssertions(); -``` - -#### .to.dispatch.actions or assert.isDispatching - -> `expect(action).to.dispatch.actions(expectedActions, callback)` - -> `action.should.dispatch.actions(expectedActions, callback)` - -> `assert.isDispatching(action, expectedActions, callback)` - -Asserts that when given `action` is dispatched it will dispatch `expectedActions`. `action` can be a plain object (action) or a function (action creator). `expectedActions` can be can be a plain object (action), a function (action creator), or an array of objects/functions. - -```js -expect(myActionCreator()) - .to.dispatch.actions({ type: 'MY_ACTION_START' }, callback); - -myActionCreator() - .should.dispatch.actions({ type: 'MY_ACTION_START' }, callback); - -assert.isDispatching( - myActionCreator(), - { type: 'MY_ACTION_START' }, - callback -); -``` - -#### .with.state or assert.isDispatchingWithState - -> `expect(action).with.state(state).to.dispatch.actions(expectedActions, callback)` - -> `action.should.with.state(state).dispatch.actions(expectedActions, callback)` - -> `assert.isDispatchingWithState(action, expectedActions, state, callback)` - -Asserts that store initialised with `state` before `action` is dispatched. -```js -expect(myActionCreator()) - .with.state({ property: 'value' }) - .to.dispatch.actions([{ type: 'MY_ACTION_START' }, finishActionCreator()], callback); - -myActionCreator() - .should.with.({ property: 'value' }) - .dispatch.actions([{ type: 'MY_ACTION_START' }, finishActionCreator()], callback); - -assert.isDispatchingWithState( - myActionCreator(), - [{ type: 'MY_ACTION_START' }, finishActionCreator()], - { property: 'value' } - callback -); -``` - -## [expect](https://github.com/mjackson/expect) - -### Registration - -```js -// using ES6 modules -import { registerAssertions } from 'redux-actions-assertions/expect'; - -// using CommonJS modules -var registerAssertions = require('redux-actions-assertions/expect').registerAssertions; - -// registration -registerAssertions(); -``` -### Usage - -#### .toDispatchActions - -> `expect(action).toDispatchActions(expectedActions, callback)` - -Asserts that when given `action` is dispatched it will dispatch `expectedActions`. `action` can be a plain object (action) or a function (action creator). `expectedActions` can be can be a plain object (action), a function (action creator), or an array of objects/functions. - -```js -expect(myActionCreator()) - .toDispatchActions({ type: 'MY_ACTION_START' }, callback); -``` - -#### .withState - -> `expect(action).withState(state).toDispatchActions(expectedActions, callback)` - -Asserts that store initialised with `state` before `action` is dispatched. - -```js -expect(myActionCreator()) - .withState({property: 'value'}) - .toDispatchActions([{ type: 'MY_ACTION_START' }, finishActionCreator()], callback); -``` - -## [expect.js](https://github.com/Automattic/expect.js) - -### Registration - -```js -// using ES6 modules -import { registerAssertions } from 'redux-actions-assertions/expectjs'; - -// using CommonJS modules -var registerAssertions = require('redux-actions-assertions/expectjs').registerAssertions; - -// registration -registerAssertions(); -``` - -### Usage - -#### .dispatchActions - -> `expect(action).to.dispatchActions(expectedActions, callback)` - -Asserts that when given `action` is dispatched it will dispatch `expectedActions`. `action` can be a plain object (action) or a function (action creator). `expectedActions` can be can be a plain object (action), a function (action creator), or an array of objects/functions. - -```js -expect(myActionCreator()) - .to.dispatchActions({ type: 'MY_ACTION_START' }, callback); -``` - -#### .withState - -> `expect(action).withState(state).to.dispatchActions(expectedActions, callback)` - -Asserts that store initialised with `state` before `action` is dispatched. - -```js -expect(myActionCreator()) - .withState({ property: 'value' }) - .to.dispatchActions([{ type: 'MY_ACTION_START' }, finishActionCreator()], callback); -``` - -## [should](https://github.com/shouldjs/should.js) - -### Registration - -```js -// using ES6 modules -import { registerAssertions } from 'redux-actions-assertions/should'; - -// using CommonJS modules -var registerAssertions = require('redux-actions-assertions/should').registerAssertions; - -// registration -registerAssertions(); -``` - -### Usage - -#### .dispatchActions - -> `should(action).dispatchActions(expectedActions, callback)` -> `action.should.dispatchActions(expectedActions, callback)` - -Asserts that when given `action` is dispatched it will dispatch `expectedActions`. `action` can be a plain object (action) or a function (action creator). `expectedActions` can be can be a plain object (action), a function (action creator), or an array of objects/functions. - -```js -should(myActionCreator()) - .dispatchActions({ type: 'MY_ACTION_START' }, callback); - -myActionCreator().should - .dispatchActions({ type: 'MY_ACTION_START' }, callback); -``` - -#### .withState or with.state - -> `should(action).withState(state).dispatchActions(expectedActions, callback)` -> `should(action).with.state(state).dispatchActions(expectedActions, callback)` - -> `action.should.withState(state).dispatchActions(expectedActions, callback)` -> `action.should.with.state(state).dispatchActions(expectedActions, callback)` - -Asserts that store initialised with `state` before `action` is dispatched. - -```js -should(myActionCreator()) - .withState({ property: 'value' }) - .dispatchActions({ type: 'MY_ACTION_START' }, callback); - -should(myActionCreator()) - .with.state({ property: 'value' }) - .dispatchActions({ type: 'MY_ACTION_START' }, callback); - -myActionCreator().should - .withState({ property: 'value' }) - .dispatchActions({ type: 'MY_ACTION_START' }, callback); - -myActionCreator().should - .with.state({ property: 'value' }) - .dispatchActions({ type: 'MY_ACTION_START' }, callback); -``` +``` \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2c8726d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,22 @@ +# redux-actions-assertions +Assertions for redux actions testing. + +This library adds assertions for [redux actions](http://redux.js.org/docs/advanced/AsyncActions.html) testing. +It use [redux-mock-store](https://github.com/arnaudbenard/redux-mock-store) to mock redux store. + +[![build status](https://img.shields.io/travis/dmitry-zaets/redux-actions-assertions/master.svg?style=flat-square)](https://travis-ci.org/dmitry-zaets/redux-actions-assertions) +[![npm version](https://img.shields.io/npm/v/redux-actions-assertions.svg?style=flat-square)](https://www.npmjs.com/package/redux-actions-assertions) + +## Supported Assertion Frameworks/Libraries: +- [chai](http://dmitry.js.org/redux-actions-assertions/chai.html) +- [expect](http://dmitry.js.org/redux-actions-assertions/expect.html) +- [expect.js](http://dmitry.js.org/redux-actions-assertions/expectjs.html) +- [should](http://dmitry.js.org/redux-actions-assertions/should.html) +- [pure javascript assertion](http://dmitry.js.org/redux-actions-assertions/javascript.html) + +If you have not found assertion framework/library that you are using - please add comment into [this issue](https://github.com/dmitry-zaets/redux-actions-assertions/issues/3). + +## What it does: +- [Allows to avoid retesting nested action creators](http://dmitry.js.org/redux-actions-assertions/what_it_does.html#allows-to-avoid-retesting-nested-action-creators); +- [Reduces repetitive code of test methods](http://dmitry.js.org/redux-actions-assertions/what_it_does.html#reduces-repetitive-code-of-test-methods); +- [Simplifies initial setup](http://dmitry.js.org/redux-actions-assertions/what_it_does.html#simplifies-initial-setup); \ No newline at end of file diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md new file mode 100644 index 0000000..4001f7c --- /dev/null +++ b/docs/SUMMARY.md @@ -0,0 +1,12 @@ +# Summary + +* [Introduction](README.md) +* [What it does](what_it_does.md) +* [Installation](installation.md) +* API Reference + * [javascript](javascript.md) + * [chai](chai.md) + * [expect](expect.md) + * [expect.js](expectjs.md) + * [should](should.md) + diff --git a/docs/chai.md b/docs/chai.md new file mode 100644 index 0000000..d2d7a55 --- /dev/null +++ b/docs/chai.md @@ -0,0 +1,102 @@ +# [chai](https://github.com/chaijs/chai) + +## Registration + +```js +// using ES6 modules +import { registerAssertions } from 'redux-actions-assertions/chai'; + +// using CommonJS modules +var registerAssertions = require('redux-actions-assertions/chai').registerAssertions; + +// registration +registerAssertions(); +``` + +## Usage + +### .to.dispatch.actions or assert.isDispatching + +> `expect(action).to.dispatch.actions(expectedActions, callback)` + +> `action.should.dispatch.actions(expectedActions, callback)` + +> `assert.isDispatching(action, expectedActions, callback)` + +Asserts that when given `action` is dispatched it will dispatch `expectedActions`. `action` can be plain object (action) or function (action creator). `expectedActions` can be can be plain object (action) or function (action creator) or array of objects/functions. + +```js +expect(myActionCreator()) + .to.dispatch.actions({ type: 'MY_ACTION_START' }, callback); + +myActionCreator() + .should.dispatch.actions({ type: 'MY_ACTION_START' }, callback); + +assert.isDispatching( + myActionCreator(), + { type: 'MY_ACTION_START' }, + callback +); +``` + +### .not.to.dispatch.actions or .to.not.dispatch.actions or assert.isNotDispatching + +> `expect(action).not.to.dispatch.actions(expectedActions, callback)` +> `expect(action).to.not.dispatch.actions(expectedActions, callback)` + +> `action.should.not.dispatch.actions(expectedActions, callback)` + +> `assert.isNotDispatching(action, expectedActions, callback)` + +Asserts that when given `action` is dispatched it will not dispatch `expectedActions`. `action` can be plain object (action) or function (action creator). `expectedActions` can be can be plain object (action) or function (action creator) or array of objects/functions. + +```js +expect(myActionCreator()) + .not.to.dispatch.actions({ type: 'MY_ACTION_START' }, callback); + +myActionCreator() + .should.not.dispatch.actions({ type: 'MY_ACTION_START' }, callback); + +assert.isNotDispatching( + myActionCreator(), + { type: 'MY_ACTION_START' }, + callback +); +``` + +### .with.state or assert.isDispatchingWithState and assert.isNotDispatchingWithState + +> `expect(action).with.state(state).to.dispatch.actions(expectedActions, callback)` +> `expect(action).with.state(state).not.to.dispatch.actions(expectedActions, callback)` +> `expect(action).with.state(state).to.not.dispatch.actions(expectedActions, callback)` + +> `action.should.with.state(state).dispatch.actions(expectedActions, callback)` +> `action.should.with.state(state).not.dispatch.actions(expectedActions, callback)` + +> `assert.isDispatchingWithState(action, expectedActions, state, callback)` +> `assert.isNotDispatchingWithState(action, expectedActions, state, callback)` + +Asserts that store initialised with `state` before `action` is dispatched. +```js +expect(myActionCreator()) + .with.state({ property: 'value' }) + .to.dispatch.actions([{ type: 'MY_ACTION_START' }, finishActionCreator()], callback); + +myActionCreator() + .should.with.({ property: 'value' }) + .dispatch.actions([{ type: 'MY_ACTION_START' }, finishActionCreator()], callback); + +assert.isDispatchingWithState( + myActionCreator(), + [{ type: 'MY_ACTION_START' }, finishActionCreator()], + { property: 'value' } + callback +); + +assert.isNotDispatchingWithState( + myActionCreator(), + [{ type: 'MY_ACTION_START' }, finishActionCreator()], + { property: 'value' } + callback +); +``` \ No newline at end of file diff --git a/docs/expect.md b/docs/expect.md new file mode 100644 index 0000000..6968e38 --- /dev/null +++ b/docs/expect.md @@ -0,0 +1,50 @@ +# [expect](https://github.com/mjackson/expect) + +## Registration + +```js +// using ES6 modules +import { registerAssertions } from 'redux-actions-assertions/expect'; + +// using CommonJS modules +var registerAssertions = require('redux-actions-assertions/expect').registerAssertions; + +// registration +registerAssertions(); +``` + +## Usage + +### .toDispatchActions + +> `expect(action).toDispatchActions(expectedActions, callback)` + +Asserts that when given `action` is dispatched it will dispatch `expectedActions`. `action` can be plain object (action) or function (action creator). `expectedActions` can be can be plain object (action) or function (action creator) or array of objects/functions. + +```js +expect(myActionCreator()) + .toDispatchActions({ type: 'MY_ACTION_START' }, callback); +``` + +### .toNotDispatchActions + +> `expect(action).toNotDispatchActions(expectedActions, callback)` + +Asserts that when given `action` is dispatched it will not dispatch `expectedActions`. `action` can be plain object (action) or function (action creator). `expectedActions` can be can be plain object (action) or function (action creator) or array of objects/functions. + +```js +expect(myActionCreator()) + .toNotDispatchActions({ type: 'MY_ACTION_START' }, callback); +``` + +### .withState + +> `expect(action).withState(state).toDispatchActions(expectedActions, callback)` + +Asserts that store initialised with `state` before `action` is dispatched. + +```js +expect(myActionCreator()) + .withState({property: 'value'}) + .toDispatchActions([{ type: 'MY_ACTION_START' }, finishActionCreator()], callback); +``` \ No newline at end of file diff --git a/docs/expectjs.md b/docs/expectjs.md new file mode 100644 index 0000000..5ce9850 --- /dev/null +++ b/docs/expectjs.md @@ -0,0 +1,54 @@ +## [expect.js](https://github.com/Automattic/expect.js) + +### Registration + +```js +// using ES6 modules +import { registerAssertions } from 'redux-actions-assertions/expectjs'; + +// using CommonJS modules +var registerAssertions = require('redux-actions-assertions/expectjs').registerAssertions; + +// registration +registerAssertions(); +``` + +## Usage + +### .dispatchActions + +> `expect(action).to.dispatchActions(expectedActions, callback)` + +Asserts that when given `action` is dispatched it will dispatch `expectedActions`. `action` can be plain object (action) or function (action creator). `expectedActions` can be can be plain object (action) or function (action creator) or array of objects/functions. + +```js +expect(myActionCreator()) + .to.dispatchActions({ type: 'MY_ACTION_START' }, callback); +``` + +### .not.dispatchActions + +> `expect(action).not.to.dispatchActions(expectedActions, callback)` +> `expect(action).to.not.dispatchActions(expectedActions, callback)` + +Asserts that when given `action` is dispatched it will not dispatch `expectedActions`. `action` can be plain object (action) or function (action creator). `expectedActions` can be can be plain object (action) or function (action creator) or array of objects/functions. + +```js +expect(myActionCreator()) + .not.to.dispatchActions({ type: 'MY_ACTION_START' }, callback); + +expect(myActionCreator()) + .to.not.dispatchActions({ type: 'MY_ACTION_START' }, callback); +``` + +### .withState + +> `expect(action).withState(state).to.dispatchActions(expectedActions, callback)` + +Asserts that store initialised with `state` before `action` is dispatched. + +```js +expect(myActionCreator()) + .withState({ property: 'value' }) + .to.dispatchActions([{ type: 'MY_ACTION_START' }, finishActionCreator()], callback); +``` diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..5a6ba83 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,46 @@ +# Installation + +Using [npm](https://www.npmjs.org/): + + $ npm install --save-dev redux-actions-assertions + +## Redux middlewares registration + +```js +// using ES6 modules +import { registerMiddlewares } from 'redux-actions-assertions'; + +// using CommonJS modules +var registerMiddlewares = require('redux-actions-assertions').registerMiddlewares; + +// registration +registerMiddlewares([ + /* Here you need to list your middlewares */ +]); +``` + +## Default initial store state registration + +**By using state object or function:** +```js +// using ES6 modules +import { registerInitialStoreState } from 'redux-actions-assertions'; + +// using CommonJS modules +var registerInitialStoreState = require('redux-actions-assertions').registerInitialStoreState; + +// registration +registerInitialStoreState(/* default initial state object or function */); +``` +**By using your root reducer:** +```js +// using ES6 modules +import { buildInitialStoreState, registerInitialStoreState } from 'redux-actions-assertions'; + +// using CommonJS modules +var reduxActionsAssertions = require('redux-actions-assertions'); +var registerInitialStoreState = reduxActionsAssertions.registerInitialStoreState; + +// registration +registerInitialStoreState(buildInitialStoreState(/* root reducer function */)); +``` diff --git a/docs/javascript.md b/docs/javascript.md new file mode 100644 index 0000000..9f56ab3 --- /dev/null +++ b/docs/javascript.md @@ -0,0 +1,59 @@ +# javascript + +## Registration + +For plain javascript assertions you don't need to register anything. Just import assertions in your tests: + +```js +// using ES6 modules +import assertions from 'redux-actions-assertions/assertions'; + +// using CommonJS modules +var assertions = require('redux-actions-assertions/assertions'); + +// in test +assertions.toDispatchActions(/**/) +assertions.toNotDispatchActions(/**/) +assertions.toDispatchActionsWithState(/**/); +assertions.toNotDispatchActionsWithState(/**/); +``` + +## Usage + +### toDispatchActions +> `toDispatchActions(action, expectedActions, callback)` + +Asserts that when given `action` is dispatched it will dispatch `expectedActions`. `action` can be plain object (action) or function (action creator). `expectedActions` can be can be plain object (action) or function (action creator) or array of objects/functions. + +```js +toDispatchActions(testActionCreator(), [{ type: 'MY_ACTION_START' }], callback); +``` + +### toNotDispatchActions +> `toNotDispatchActions(action, expectedActions, callback)` + +Asserts that when given `action` is dispatched it will not dispatch `expectedActions`. `action` can be plain object (action) or function (action creator). `expectedActions` can be can be plain object (action) or function (action creator) or array of objects/functions. + +```js +toNotDispatchActions(testActionCreator(), [{ type: 'MY_ACTION_START' }], callback); +``` + +### toDispatchActionsWithState + +> `toDispatchActionsWithState(initialState, action, expectedActions, callback)` + +Same as `toDispatchActions` + asserts that store initialised with `state` before `action` is dispatched. + +```js +toDispatchActions({property: 'value'}, testActionCreator(), [{ type: 'MY_ACTION_START' }], callback); +``` + +### toNotDispatchActionsWithState + +> `toNotDispatchActionsWithState(initialState, action, expectedActions, callback)` + +Same as `toNotDispatchActions` + asserts that store initialised with `state` before `action` is dispatched. + +```js +toNotDispatchActions({property: 'value'}, testActionCreator(), [{ type: 'MY_ACTION_START' }], callback); +``` \ No newline at end of file diff --git a/docs/should.md b/docs/should.md new file mode 100644 index 0000000..9bb0411 --- /dev/null +++ b/docs/should.md @@ -0,0 +1,75 @@ +# [should](https://github.com/shouldjs/should.js) + +## Registration + +```js +// using ES6 modules +import { registerAssertions } from 'redux-actions-assertions/should'; + +// using CommonJS modules +var registerAssertions = require('redux-actions-assertions/should').registerAssertions; + +// registration +registerAssertions(); +``` + +## Usage + +### .dispatchActions + +> `should(action).dispatchActions(expectedActions, callback)` +> `action.should.dispatchActions(expectedActions, callback)` + +Asserts that when given `action` is dispatched it will dispatch `expectedActions`. `action` can be plain object (action) or function (action creator). `expectedActions` can be can be plain object (action) or function (action creator) or array of objects/functions. + +```js +should(myActionCreator()) + .dispatchActions({ type: 'MY_ACTION_START' }, callback); + +myActionCreator().should + .dispatchActions({ type: 'MY_ACTION_START' }, callback); +``` + + +### .notDispatchActions + +> `should(action).notDispatchActions(expectedActions, callback)` +> `action.should.notDispatchActions(expectedActions, callback)` + +Asserts that when given `action` is dispatched it will not dispatch `expectedActions`. `action` can be plain object (action) or function (action creator). `expectedActions` can be can be plain object (action) or function (action creator) or array of objects/functions. + +```js +should(myActionCreator()) + .notDispatchActions({ type: 'MY_ACTION_START' }, callback); + +myActionCreator().should + .notDispatchActions({ type: 'MY_ACTION_START' }, callback); +``` + +### .withState or with.state + +> `should(action).withState(state).dispatchActions(expectedActions, callback)` +> `should(action).with.state(state).dispatchActions(expectedActions, callback)` + +> `action.should.withState(state).dispatchActions(expectedActions, callback)` +> `action.should.with.state(state).dispatchActions(expectedActions, callback)` + +Asserts that store initialised with `state` before `action` is dispatched. + +```js +should(myActionCreator()) + .withState({ property: 'value' }) + .dispatchActions({ type: 'MY_ACTION_START' }, callback); + +should(myActionCreator()) + .with.state({ property: 'value' }) + .dispatchActions({ type: 'MY_ACTION_START' }, callback); + +myActionCreator().should + .withState({ property: 'value' }) + .dispatchActions({ type: 'MY_ACTION_START' }, callback); + +myActionCreator().should + .with.state({ property: 'value' }) + .dispatchActions({ type: 'MY_ACTION_START' }, callback); +``` \ No newline at end of file diff --git a/docs/what_it_does.md b/docs/what_it_does.md new file mode 100644 index 0000000..95e9b36 --- /dev/null +++ b/docs/what_it_does.md @@ -0,0 +1,106 @@ +# What it does + +## Allows to avoid retesting nested action creators +It allows to test only actions that need to be tested. + +**Example:** +We have two actions (A, B). Each one makes async http requests. +Action A makes a request and if the result is successful it triggers Action B. +Action B is also used as an independent action. +Action B can be tested separately. +Therefore, we don't need to test it again in Action A. + +Actions: +```javascript +function actionA() { + return dispatch => { + dispatch(actionAStart()); + return api.getA().then(response => { + dispatch(actionAFinish(response)); + dispatch(actionB()); + }).catch(err => { + dispatch(actionAFailure(err)); + }); + }; +} + +function actionB() { + return dispatch => { + dispatch(actionBStart()); + return api.getB().then(response => { + dispatch(actionBFinish(response)); + }).catch(err => { + dispatch(actionBFailure(err)); + }); + }; +} +``` + +Without: +```javascript +const expectedActions = [ + { type: action_a_start }, + { type: action_a_success }, + { type: action_b_start }, // retesting of action B + { type: action_b_success } // retesting of action B]; +const store = mockStore({ todos: [] }); +store.dispatch(actionA()).then(() => { + expect(store.getActions()).toEqual(expectedActions); +}).then(done).catch(done); +``` + +With: +```javascript +expect(actionA()).withState({ todos: [] }).toDispatch([ + { type: action_a_start }, + { type: action_a_success }, + actionB() // just executing tested action +], done); +``` + +## Reduces repetitive code of test methods +It reduces boilerplate of test methods and makes testing fluent. + +Without: +```javascript +const store = mockStore(/* initial state */); +const expectedActions = [ + { type: types.FETCH_TODOS_REQUEST }, + /* All expected triggered action objects */ +]; +store.dispatch(fetchData()).then(() => { + const actions = store.getActions(); + expect(actions).toEqual(expectedActions); +}).then(done).catch(done); +``` + +With: +```javascript +const expectedActions = [ + /*All expected triggered action objects or action creator functions*/ +]; +expect(fetchData()).toDispatchActions(expectedActions, done); +``` + +With using customised store state: +```javascript +expect(fetchData()).withState({/*custom state*/}).toDispatchActions(expectedActions, done); +``` + +## Simplifies initial setup +It provides singe-time global configuration for middlewares and initial store state. + +Without: +```javascript +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); +const store = mockStore({ /*initial store object*}); +``` +With: +```javascript +registerMiddlewares([ thunk ]); +// to set custom initial state +registerInitialStoreState(/*object of function*/); +// to generate initial state of your application +registerInitialStoreState(buildInitialStoreState(/*your root reducer*/)); +``` \ No newline at end of file