Replace chai with unexpected.js

This commit is contained in:
SleepWalker 2016-07-30 13:44:43 +03:00
parent ae11dbdc97
commit 9abbe2ebab
12 changed files with 221 additions and 144 deletions

View File

@ -10,7 +10,7 @@ module.exports = function(config) {
// frameworks to use // frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'sinon', 'chai'], frameworks: ['mocha', 'sinon'],
// list of files / patterns to load in the browser // list of files / patterns to load in the browser
@ -35,7 +35,7 @@ module.exports = function(config) {
webpackServer: { webpackServer: {
noInfo: true //please don't spam the console when running in karma! noInfo: true // please don't spam the console when running in karma!
}, },
@ -44,6 +44,12 @@ module.exports = function(config) {
// available reporters: https://npmjs.org/browse/keyword/karma-reporter // available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['nyan'], reporters: ['nyan'],
nyanReporter: {
// suppress the red background on errors in the error
// report at the end of the test run
suppressErrorHighlighting: true
},
// web server port // web server port
port: 9876, port: 9876,

View File

@ -49,7 +49,6 @@
"babel-preset-stage-0": "^6.3.13", "babel-preset-stage-0": "^6.3.13",
"babel-runtime": "^6.0.0", "babel-runtime": "^6.0.0",
"bundle-loader": "^0.5.4", "bundle-loader": "^0.5.4",
"chai": "^3.0.0",
"css-loader": "^0.23.0", "css-loader": "^0.23.0",
"enzyme": "^2.2.0", "enzyme": "^2.2.0",
"eslint": "^3.1.1", "eslint": "^3.1.1",
@ -63,7 +62,6 @@
"imports-loader": "^0.6.5", "imports-loader": "^0.6.5",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"karma": "^1.1.0", "karma": "^1.1.0",
"karma-chai": "*",
"karma-mocha": "^1.0.0", "karma-mocha": "^1.0.0",
"karma-nyan-reporter": "^0.2.3", "karma-nyan-reporter": "^0.2.3",
"karma-phantomjs-launcher": "*", "karma-phantomjs-launcher": "*",
@ -87,6 +85,8 @@
"scripts": "file:scripts", "scripts": "file:scripts",
"sinon": "^1.15.3", "sinon": "^1.15.3",
"style-loader": "^0.13.0", "style-loader": "^0.13.0",
"unexpected": "^10.15.0",
"unexpected-sinon": "^10.2.1",
"url-loader": "^0.5.7", "url-loader": "^0.5.7",
"webpack": "^1.12.9", "webpack": "^1.12.9",
"webpack-dev-server": "^1.14.0", "webpack-dev-server": "^1.14.0",

View File

@ -1,6 +1,9 @@
import expect from 'unexpected';
import request from 'services/request'; import request from 'services/request';
import { import {
setLoadingState,
oAuthValidate, oAuthValidate,
oAuthComplete, oAuthComplete,
setClient, setClient,
@ -19,21 +22,29 @@ const oauthData = {
}; };
describe('components/auth/actions', () => { describe('components/auth/actions', () => {
const dispatch = sinon.stub(); const dispatch = sinon.stub().named('dispatch');
const getState = sinon.stub(); const getState = sinon.stub().named('getState');
const callThunk = function(fn, ...args) { function callThunk(fn, ...args) {
const thunk = fn(...args); const thunk = fn(...args);
return thunk(dispatch, getState); return thunk(dispatch, getState);
}; }
function expectDispatchCalls(calls) {
expect(dispatch, 'to have calls satisfying', [
[setLoadingState(true)]
].concat(calls).concat([
[setLoadingState(false)]
]));
}
beforeEach(() => { beforeEach(() => {
dispatch.reset(); dispatch.reset();
getState.reset(); getState.reset();
getState.returns({}); getState.returns({});
sinon.stub(request, 'get'); sinon.stub(request, 'get').named('request.get');
sinon.stub(request, 'post'); sinon.stub(request, 'post').named('request.post');
}); });
afterEach(() => { afterEach(() => {
@ -42,10 +53,10 @@ describe('components/auth/actions', () => {
}); });
describe('#oAuthValidate()', () => { describe('#oAuthValidate()', () => {
it('should dispatch setClient, setOAuthRequest and setScopes', () => { let resp;
// TODO: the assertions may be splitted up to one per test
const resp = { beforeEach(() => {
resp = {
client: {id: 123}, client: {id: 123},
oAuth: {state: 123}, oAuth: {state: 123},
session: { session: {
@ -54,12 +65,21 @@ describe('components/auth/actions', () => {
}; };
request.get.returns(Promise.resolve(resp)); request.get.returns(Promise.resolve(resp));
});
it('should send get request to an api', () => {
return callThunk(oAuthValidate, oauthData).then(() => { return callThunk(oAuthValidate, oauthData).then(() => {
sinon.assert.calledWith(request.get, '/api/oauth2/v1/validate'); expect(request.get, 'to have a call satisfying', ['/api/oauth2/v1/validate', {}]);
sinon.assert.calledWith(dispatch, setClient(resp.client)); });
sinon.assert.calledWith(dispatch, setOAuthRequest(resp.oAuth)); });
sinon.assert.calledWith(dispatch, setScopes(resp.session.scopes));
it('should dispatch setClient, setOAuthRequest and setScopes', () => {
return callThunk(oAuthValidate, oauthData).then(() => {
expectDispatchCalls([
[setClient(resp.client)],
[setOAuthRequest(resp.oAuth)],
[setScopes(resp.session.scopes)]
]);
}); });
}); });
}); });
@ -73,8 +93,20 @@ describe('components/auth/actions', () => {
}); });
}); });
it('should post to api/oauth2/complete', () => {
request.post.returns(Promise.resolve({
redirectUri: ''
}));
return callThunk(oAuthComplete).then(() => {
expect(request.post, 'to have a call satisfying', [
'/api/oauth2/v1/complete?client_id=&redirect_uri=&response_type=&scope=&state=',
{}
]);
});
});
it('should dispatch setOAuthCode for static_page redirect', () => { it('should dispatch setOAuthCode for static_page redirect', () => {
// TODO: it may be split on separate url and dispatch tests
const resp = { const resp = {
success: true, success: true,
redirectUri: 'static_page?code=123&state=' redirectUri: 'static_page?code=123&state='
@ -83,12 +115,15 @@ describe('components/auth/actions', () => {
request.post.returns(Promise.resolve(resp)); request.post.returns(Promise.resolve(resp));
return callThunk(oAuthComplete).then(() => { return callThunk(oAuthComplete).then(() => {
sinon.assert.calledWithMatch(request.post, /\/api\/oauth2\/v1\/complete/); expectDispatchCalls([
sinon.assert.calledWith(dispatch, setOAuthCode({ [
success: true, setOAuthCode({
code: '123', success: true,
displayCode: false code: '123',
})); displayCode: false
})
]
]);
}); });
}); });
@ -102,7 +137,7 @@ describe('components/auth/actions', () => {
request.post.returns(Promise.reject(resp)); request.post.returns(Promise.reject(resp));
return callThunk(oAuthComplete).then((resp) => { return callThunk(oAuthComplete).then((resp) => {
expect(resp).to.be.deep.equal({ expect(resp, 'to equal', {
success: false, success: false,
redirectUri: 'redirectUri' redirectUri: 'redirectUri'
}); });
@ -118,8 +153,10 @@ describe('components/auth/actions', () => {
request.post.returns(Promise.reject(resp)); request.post.returns(Promise.reject(resp));
return callThunk(oAuthComplete).catch((resp) => { return callThunk(oAuthComplete).catch((resp) => {
expect(resp.acceptRequired).to.be.true; expect(resp.acceptRequired, 'to be true');
sinon.assert.calledWith(dispatch, requirePermissionsAccept()); expectDispatchCalls([
[requirePermissionsAccept()]
]);
}); });
}); });
}); });

View File

@ -1,3 +1,4 @@
import expect from 'unexpected';
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
@ -8,16 +9,16 @@ describe('<ChangePassword />', () => {
it('renders two <Input /> components', () => { it('renders two <Input /> components', () => {
const component = shallow(<ChangePassword onSubmit={() => {}} />); const component = shallow(<ChangePassword onSubmit={() => {}} />);
expect(component.find('Input')).to.have.length(2); expect(component.find('Input'), 'to satisfy', {length: 2});
}); });
it('should call onSubmit if passwords entered', () => { it('should call onSubmit if passwords entered', () => {
const onSubmit = sinon.spy(); const onSubmit = sinon.spy().named('onSubmit');
const component = shallow(<ChangePassword onSubmit={onSubmit} />); const component = shallow(<ChangePassword onSubmit={onSubmit} />);
component.find('Form').simulate('submit'); component.find('Form').simulate('submit');
sinon.assert.calledOnce(onSubmit); expect(onSubmit, 'was called');
}); });
}); });

View File

@ -1,3 +1,5 @@
import expect from 'unexpected';
import React from 'react'; import React from 'react';
import { shallow, mount } from 'enzyme'; import { shallow, mount } from 'enzyme';
@ -22,7 +24,7 @@ describe('<PopupStack />', () => {
}; };
const component = shallow(<PopupStack {...props} />); const component = shallow(<PopupStack {...props} />);
expect(component.find(DummyPopup)).to.have.length(2); expect(component.find(DummyPopup), 'to satisfy', {length: 2});
}); });
it('should pass onClose as props', () => { it('should pass onClose as props', () => {
@ -35,7 +37,7 @@ describe('<PopupStack />', () => {
popups: [ popups: [
{ {
Popup: (props = {}) => { Popup: (props = {}) => {
expect(props.onClose).to.be.a('function'); expect(props.onClose, 'to be a', 'function');
return <DummyPopup {...expectedProps} />; return <DummyPopup {...expectedProps} />;
} }
@ -45,8 +47,8 @@ describe('<PopupStack />', () => {
const component = mount(<PopupStack {...props} />); const component = mount(<PopupStack {...props} />);
const popup = component.find(DummyPopup); const popup = component.find(DummyPopup);
expect(popup).to.have.length(1); expect(popup, 'to satisfy', {length: 1});
expect(popup.props()).to.deep.equal(expectedProps); expect(popup.props(), 'to equal', expectedProps);
}); });
it('should hide popup, when onClose called', () => { it('should hide popup, when onClose called', () => {
@ -59,20 +61,22 @@ describe('<PopupStack />', () => {
Popup: DummyPopup Popup: DummyPopup
} }
], ],
destroy: sinon.stub() destroy: sinon.stub().named('props.destroy')
}; };
const component = shallow(<PopupStack {...props} />); const component = shallow(<PopupStack {...props} />);
component.find(DummyPopup).last().prop('onClose')(); component.find(DummyPopup).last().prop('onClose')();
sinon.assert.calledOnce(props.destroy); expect(props.destroy, 'was called once');
sinon.assert.calledWith(props.destroy, sinon.match.same(props.popups[1])); expect(props.destroy, 'to have a call satisfying', [
expect.it('to be', props.popups[1])
]);
}); });
it('should hide popup, when overlay clicked', () => { it('should hide popup, when overlay clicked', () => {
const preventDefault = sinon.stub(); const preventDefault = sinon.stub().named('event.preventDefault');
const props = { const props = {
destroy: sinon.stub(), destroy: sinon.stub().named('props.destroy'),
popups: [ popups: [
{ {
Popup: DummyPopup Popup: DummyPopup
@ -84,13 +88,13 @@ describe('<PopupStack />', () => {
const overlay = component.find(`.${styles.overlay}`); const overlay = component.find(`.${styles.overlay}`);
overlay.simulate('click', {target: 1, currentTarget: 1, preventDefault}); overlay.simulate('click', {target: 1, currentTarget: 1, preventDefault});
sinon.assert.calledOnce(props.destroy); expect(props.destroy, 'was called once');
sinon.assert.calledOnce(preventDefault); expect(preventDefault, 'was called once');
}); });
it('should hide popup on overlay click if disableOverlayClose', () => { it('should hide popup on overlay click if disableOverlayClose', () => {
const props = { const props = {
destroy: sinon.stub(), destroy: sinon.stub().named('props.destroy'),
popups: [ popups: [
{ {
Popup: DummyPopup, Popup: DummyPopup,
@ -103,12 +107,12 @@ describe('<PopupStack />', () => {
const overlay = component.find(`.${styles.overlay}`); const overlay = component.find(`.${styles.overlay}`);
overlay.simulate('click', {target: 1, currentTarget: 1, preventDefault() {}}); overlay.simulate('click', {target: 1, currentTarget: 1, preventDefault() {}});
sinon.assert.notCalled(props.destroy); expect(props.destroy, 'was not called');
}); });
it('should hide popup, when esc pressed', () => { it('should hide popup, when esc pressed', () => {
const props = { const props = {
destroy: sinon.stub(), destroy: sinon.stub().named('props.destroy'),
popups: [ popups: [
{ {
Popup: DummyPopup Popup: DummyPopup
@ -121,12 +125,12 @@ describe('<PopupStack />', () => {
event.which = 27; event.which = 27;
document.dispatchEvent(event); document.dispatchEvent(event);
sinon.assert.calledOnce(props.destroy); expect(props.destroy, 'was called once');
}); });
it('should hide first popup in stack if esc pressed', () => { it('should hide first popup in stack if esc pressed', () => {
const props = { const props = {
destroy: sinon.stub(), destroy: sinon.stub().named('props.destroy'),
popups: [ popups: [
{ {
Popup() {return null;} Popup() {return null;}
@ -142,13 +146,15 @@ describe('<PopupStack />', () => {
event.which = 27; event.which = 27;
document.dispatchEvent(event); document.dispatchEvent(event);
sinon.assert.calledOnce(props.destroy); expect(props.destroy, 'was called once');
sinon.assert.calledWithExactly(props.destroy, props.popups[1]); expect(props.destroy, 'to have a call satisfying', [
expect.it('to be', props.popups[1])
]);
}); });
it('should NOT hide popup on esc pressed if disableOverlayClose', () => { it('should NOT hide popup on esc pressed if disableOverlayClose', () => {
const props = { const props = {
destroy: sinon.stub(), destroy: sinon.stub().named('props.destroy'),
popups: [ popups: [
{ {
Popup: DummyPopup, Popup: DummyPopup,
@ -162,6 +168,6 @@ describe('<PopupStack />', () => {
event.which = 27; event.which = 27;
document.dispatchEvent(event); document.dispatchEvent(event);
sinon.assert.notCalled(props.destroy); expect(props.destroy, 'was not called');
}); });
}); });

View File

@ -1,3 +1,5 @@
import expect from 'unexpected';
import reducer from 'components/ui/popup/reducer'; import reducer from 'components/ui/popup/reducer';
import {create, destroy} from 'components/ui/popup/actions'; import {create, destroy} from 'components/ui/popup/actions';
@ -5,8 +7,8 @@ describe('popup/reducer', () => {
it('should have no popups by default', () => { it('should have no popups by default', () => {
const actual = reducer(undefined, {}); const actual = reducer(undefined, {});
expect(actual.popups).to.be.an('array'); expect(actual.popups, 'to be an', 'array');
expect(actual.popups).to.be.empty; expect(actual.popups, 'to be empty');
}); });
describe('#create', () => { describe('#create', () => {
@ -15,7 +17,7 @@ describe('popup/reducer', () => {
Popup: FakeComponent Popup: FakeComponent
})); }));
expect(actual.popups[0]).to.be.deep.equal({ expect(actual.popups[0], 'to equal', {
Popup: FakeComponent Popup: FakeComponent
}); });
}); });
@ -23,7 +25,7 @@ describe('popup/reducer', () => {
it('should support shortcut popup creation', () => { it('should support shortcut popup creation', () => {
const actual = reducer(undefined, create(FakeComponent)); const actual = reducer(undefined, create(FakeComponent));
expect(actual.popups[0]).to.be.deep.equal({ expect(actual.popups[0], 'to equal', {
Popup: FakeComponent Popup: FakeComponent
}); });
}); });
@ -36,13 +38,13 @@ describe('popup/reducer', () => {
Popup: FakeComponent Popup: FakeComponent
})); }));
expect(actual.popups[1]).to.be.deep.equal({ expect(actual.popups[1], 'to equal', {
Popup: FakeComponent Popup: FakeComponent
}); });
}); });
it('throws when no type provided', () => { it('throws when no type provided', () => {
expect(() => reducer(undefined, create())).to.throw('Popup is required'); expect(() => reducer(undefined, create()), 'to throw', 'Popup is required');
}); });
}); });
@ -56,11 +58,11 @@ describe('popup/reducer', () => {
}); });
it('should remove popup', () => { it('should remove popup', () => {
expect(state.popups).to.have.length(1); expect(state.popups, 'to have length', 1);
state = reducer(state, destroy(popup)); state = reducer(state, destroy(popup));
expect(state.popups).to.have.length(0); expect(state.popups, 'to have length', 0);
}); });
it('should not remove something, that it should not', () => { it('should not remove something, that it should not', () => {
@ -70,8 +72,8 @@ describe('popup/reducer', () => {
state = reducer(state, destroy(popup)); state = reducer(state, destroy(popup));
expect(state.popups).to.have.length(1); expect(state.popups, 'to have length', 1);
expect(state.popups[0]).to.not.equal(popup); expect(state.popups[0], 'not to be', popup);
}); });
}); });
}); });

View File

@ -1,3 +1,5 @@
import expect from 'unexpected';
import { routeActions } from 'react-router-redux'; import { routeActions } from 'react-router-redux';
import request from 'services/request'; import request from 'services/request';
@ -9,8 +11,8 @@ import {
describe('components/user/actions', () => { describe('components/user/actions', () => {
const dispatch = sinon.stub(); const dispatch = sinon.stub().named('dispatch');
const getState = sinon.stub(); const getState = sinon.stub().named('getState');
const callThunk = function(fn, ...args) { const callThunk = function(fn, ...args) {
const thunk = fn(...args); const thunk = fn(...args);
@ -22,8 +24,8 @@ describe('components/user/actions', () => {
dispatch.reset(); dispatch.reset();
getState.reset(); getState.reset();
getState.returns({}); getState.returns({});
sinon.stub(request, 'get'); sinon.stub(request, 'get').named('request.get');
sinon.stub(request, 'post'); sinon.stub(request, 'post').named('request.post');
}); });
afterEach(() => { afterEach(() => {
@ -42,14 +44,16 @@ describe('components/user/actions', () => {
request.post.returns(new Promise((resolve) => { request.post.returns(new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
// we must not overwrite user's token till request starts // we must not overwrite user's token till request starts
sinon.assert.notCalled(dispatch); expect(dispatch, 'was not called');
resolve(); resolve();
}, 0); }, 0);
})); }));
return callThunk(logout).then(() => { return callThunk(logout).then(() => {
sinon.assert.calledWith(request.post, '/api/authentication/logout'); expect(request.post, 'to have a call satisfying', [
'/api/authentication/logout'
]);
}); });
}); });
@ -63,10 +67,12 @@ describe('components/user/actions', () => {
request.post.returns(Promise.resolve()); request.post.returns(Promise.resolve());
return callThunk(logout).then(() => { return callThunk(logout).then(() => {
sinon.assert.calledWith(dispatch, setUser({ expect(dispatch, 'to have a call satisfying', [
lang: 'foo', setUser({
isGuest: true lang: 'foo',
})); isGuest: true
})
]);
}); });
}); });
@ -80,7 +86,9 @@ describe('components/user/actions', () => {
request.post.returns(Promise.resolve()); request.post.returns(Promise.resolve());
return callThunk(logout).then(() => { return callThunk(logout).then(() => {
sinon.assert.calledWith(dispatch, routeActions.push('/login')); expect(dispatch, 'to have a call satisfying', [
routeActions.push('/login')
]);
}); });
}); });
}); });

View File

@ -1,6 +1,9 @@
import 'polyfills'; import 'polyfills';
import expect from 'unexpected';
expect.use(require('unexpected-sinon'));
// require all modules ending in "_test" from the // require all modules ending in "_test" from the
// current directory and all subdirectories // current directory and all subdirectories
var testsContext = require.context(".", true, /\.test\.jsx?$/); const testsContext = require.context('.', true, /\.test\.jsx?$/);
testsContext.keys().forEach(testsContext); testsContext.keys().forEach(testsContext);

View File

@ -1,3 +1,5 @@
import expect from 'unexpected';
import AuthFlow from 'services/authFlow/AuthFlow'; import AuthFlow from 'services/authFlow/AuthFlow';
import RegisterState from 'services/authFlow/RegisterState'; import RegisterState from 'services/authFlow/RegisterState';
@ -12,16 +14,15 @@ describe('AuthFlow.functional', () => {
let navigate; let navigate;
beforeEach(() => { beforeEach(() => {
actions = {test: sinon.stub()}; actions = {};
actions.test.returns('passed');
store = { store = {
getState: sinon.stub(), getState: sinon.stub().named('store.getState'),
dispatch: sinon.spy(({type, payload = {}}) => { dispatch: sinon.spy(({type, payload = {}}) => {
if (type === '@@router/TRANSITION' && payload.method === 'push') { if (type === '@@router/TRANSITION' && payload.method === 'push') {
// emulate redux-router // emulate redux-router
navigate.apply(null, payload.args); navigate(...payload.args);
} }
}) }).named('store.dispatch')
}; };
state = {}; state = {};
@ -36,8 +37,8 @@ describe('AuthFlow.functional', () => {
} }
}; };
sinon.stub(flow, 'run'); sinon.stub(flow, 'run').named('flow.run');
sinon.spy(flow, 'navigate'); sinon.spy(flow, 'navigate').named('flow.navigate');
store.getState.returns(state); store.getState.returns(state);
}); });
@ -51,8 +52,8 @@ describe('AuthFlow.functional', () => {
it('should redirect guest / -> /login', () => { it('should redirect guest / -> /login', () => {
navigate('/'); navigate('/');
sinon.assert.calledOnce(flow.navigate); expect(flow.navigate, 'was called once');
sinon.assert.calledWithExactly(flow.navigate, '/login'); expect(flow.navigate, 'to have a call satisfying', ['/login']);
}); });
it('should redirect guest to /login after /login -> /', () => { it('should redirect guest to /login after /login -> /', () => {
@ -63,8 +64,8 @@ describe('AuthFlow.functional', () => {
navigate('/login'); navigate('/login');
navigate('/'); navigate('/');
sinon.assert.calledTwice(flow.navigate); expect(flow.navigate, 'was called twice');
sinon.assert.alwaysCalledWithExactly(flow.navigate, '/login'); expect(flow.navigate, 'to have a call satisfying', ['/login']);
}); });
}); });
@ -98,10 +99,11 @@ describe('AuthFlow.functional', () => {
navigate('/oauth2'); navigate('/oauth2');
sinon.assert.calledThrice(flow.run); expect(flow.run, 'to have calls satisfying', [
sinon.assert.calledWith(flow.run.getCall(0), 'oAuthValidate'); ['oAuthValidate', {}],
sinon.assert.calledWith(flow.run.getCall(1), 'oAuthComplete'); ['oAuthComplete', {}],
sinon.assert.calledWithExactly(flow.run.getCall(2), 'redirect', expectedRedirect); ['redirect', expectedRedirect]
]);
}); });
describe('/resend-activation #goBack()', () => { describe('/resend-activation #goBack()', () => {
@ -120,24 +122,24 @@ describe('AuthFlow.functional', () => {
it('should goBack to /activation', () => { it('should goBack to /activation', () => {
navigate('/activation'); navigate('/activation');
expect(flow.state).to.be.instanceof(ActivationState); expect(flow.state, 'to be a', ActivationState);
flow.state.reject(flow); flow.state.reject(flow);
expect(flow.state).to.be.instanceof(ResendActivationState); expect(flow.state, 'to be a', ResendActivationState);
flow.state.goBack(flow); flow.state.goBack(flow);
expect(flow.state).to.be.instanceof(ActivationState); expect(flow.state, 'to be a', ActivationState);
}); });
it('should goBack to /register', () => { it('should goBack to /register', () => {
navigate('/register'); navigate('/register');
expect(flow.state).to.be.instanceof(RegisterState); expect(flow.state, 'to be a', RegisterState);
flow.state.reject(flow, {requestEmail: true}); flow.state.reject(flow, {requestEmail: true});
expect(flow.state).to.be.instanceof(ResendActivationState); expect(flow.state, 'to be a', ResendActivationState);
flow.state.goBack(flow); flow.state.goBack(flow);
expect(flow.state).to.be.instanceof(RegisterState); expect(flow.state, 'to be a', RegisterState);
}); });
}); });
}); });

View File

@ -1,3 +1,5 @@
import expect from 'unexpected';
import AuthFlow from 'services/authFlow/AuthFlow'; import AuthFlow from 'services/authFlow/AuthFlow';
import AbstractState from 'services/authFlow/AbstractState'; import AbstractState from 'services/authFlow/AbstractState';
@ -16,19 +18,21 @@ describe('AuthFlow', () => {
let actions; let actions;
beforeEach(() => { beforeEach(() => {
actions = {test: sinon.stub()}; actions = {
test: sinon.stub().named('actions.test')
};
actions.test.returns('passed'); actions.test.returns('passed');
flow = new AuthFlow(actions); flow = new AuthFlow(actions);
}); });
it('throws when no actions provided', () => { it('throws when no actions provided', () => {
expect(() => new AuthFlow()).to.throw('AuthFlow requires an actions object'); expect(() => new AuthFlow(), 'to throw', 'AuthFlow requires an actions object');
}); });
it('should not allow to mutate actions', () => { it('should not allow to mutate actions', () => {
expect(() => flow.actions.foo = 'bar').to.throw(/readonly/); expect(() => flow.actions.foo = 'bar', 'to throw', /readonly/);
expect(() => flow.actions.test = 'hacked').to.throw(/readonly/); expect(() => flow.actions.test = 'hacked', 'to throw', /readonly/);
}); });
describe('#setState', () => { describe('#setState', () => {
@ -36,17 +40,17 @@ describe('AuthFlow', () => {
const state = new AbstractState(); const state = new AbstractState();
flow.setState(state); flow.setState(state);
expect(flow.state).to.be.equal(state); expect(flow.state, 'to be', state);
}); });
it('should call #enter() on new state and pass reference to itself', () => { it('should call #enter() on new state and pass reference to itself', () => {
const state = new AbstractState(); const state = new AbstractState();
const spy = sinon.spy(state, 'enter'); const spy = sinon.spy(state, 'enter').named('state.enter');
flow.setState(state); flow.setState(state);
sinon.assert.calledWith(spy, flow); expect(spy, 'was called once');
sinon.assert.calledOnce(spy); expect(spy, 'to have a call satisfying', [flow]);
}); });
it('should call `leave` on previous state if any', () => { it('should call `leave` on previous state if any', () => {
@ -61,9 +65,9 @@ describe('AuthFlow', () => {
flow.setState(state1); flow.setState(state1);
flow.setState(state2); flow.setState(state2);
sinon.assert.calledWith(spy1, flow); expect(spy1, 'was called once');
sinon.assert.calledOnce(spy1); expect(spy1, 'to have a call satisfying', [flow]);
sinon.assert.notCalled(spy2); expect(spy2, 'was not called');
}); });
it('should return promise, if #enter returns it', () => { it('should return promise, if #enter returns it', () => {
@ -74,11 +78,11 @@ describe('AuthFlow', () => {
const actual = flow.setState(state); const actual = flow.setState(state);
expect(actual).to.be.equal(expected); expect(actual, 'to be', expected);
}); });
it('should throw if no state', () => { it('should throw if no state', () => {
expect(() => flow.setState()).to.throw('State is required'); expect(() => flow.setState(), 'to throw', 'State is required');
}); });
}); });
@ -88,7 +92,7 @@ describe('AuthFlow', () => {
beforeEach(() => { beforeEach(() => {
store = { store = {
getState() {}, getState() {},
dispatch: sinon.stub() dispatch: sinon.stub().named('store.dispatch')
}; };
flow.setStore(store); flow.setStore(store);
@ -97,76 +101,76 @@ describe('AuthFlow', () => {
it('should dispatch an action', () => { it('should dispatch an action', () => {
flow.run('test'); flow.run('test');
sinon.assert.calledOnce(store.dispatch); expect(store.dispatch, 'was called once');
sinon.assert.calledWith(store.dispatch, 'passed'); expect(store.dispatch, 'to have a call satisfying', ['passed']);
}); });
it('should dispatch an action with payload given', () => { it('should dispatch an action with payload given', () => {
flow.run('test', 'arg'); flow.run('test', 'arg');
sinon.assert.calledOnce(actions.test); expect(actions.test, 'was called once');
sinon.assert.calledWith(actions.test, 'arg'); expect(actions.test, 'to have a call satisfying', ['arg']);
}); });
it('should return action dispatch result', () => { it('should return action dispatch result', () => {
const expected = 'dispatch called'; const expected = 'dispatch called';
store.dispatch.returns(expected); store.dispatch.returns(expected);
expect(flow.run('test')).to.be.equal(expected); expect(flow.run('test'), 'to be', expected);
}); });
it('throws when running unexisted action', () => { it('throws when running unexisted action', () => {
expect(() => flow.run('123')).to.throw('Action 123 does not exists'); expect(() => flow.run('123'), 'to throw', 'Action 123 does not exists');
}); });
}); });
describe('#goBack', () => { describe('#goBack', () => {
it('should call goBack on state passing itself as argument', () => { it('should call goBack on state passing itself as argument', () => {
const state = new AbstractState(); const state = new AbstractState();
sinon.stub(state, 'goBack'); sinon.stub(state, 'goBack').named('state.goBack');
flow.setState(state); flow.setState(state);
flow.goBack(); flow.goBack();
sinon.assert.calledOnce(state.goBack); expect(state.goBack, 'was called once');
sinon.assert.calledWith(state.goBack, flow); expect(state.goBack, 'to have a call satisfying', [flow]);
}); });
}); });
describe('#resolve', () => { describe('#resolve', () => {
it('should call resolve on state passing itself and payload as arguments', () => { it('should call resolve on state passing itself and payload as arguments', () => {
const state = new AbstractState(); const state = new AbstractState();
sinon.stub(state, 'resolve'); sinon.stub(state, 'resolve').named('state.resolve');
flow.setState(state); flow.setState(state);
const expectedPayload = {foo: 'bar'}; const expectedPayload = {foo: 'bar'};
flow.resolve(expectedPayload); flow.resolve(expectedPayload);
sinon.assert.calledOnce(state.resolve); expect(state.resolve, 'was called once');
sinon.assert.calledWithExactly(state.resolve, flow, expectedPayload); expect(state.resolve, 'to have a call satisfying', [flow, expectedPayload]);
}); });
}); });
describe('#reject', () => { describe('#reject', () => {
it('should call reject on state passing itself and payload as arguments', () => { it('should call reject on state passing itself and payload as arguments', () => {
const state = new AbstractState(); const state = new AbstractState();
sinon.stub(state, 'reject'); sinon.stub(state, 'reject').named('state.reject');
flow.setState(state); flow.setState(state);
const expectedPayload = {foo: 'bar'}; const expectedPayload = {foo: 'bar'};
flow.reject(expectedPayload); flow.reject(expectedPayload);
sinon.assert.calledOnce(state.reject); expect(state.reject, 'was called once');
sinon.assert.calledWithExactly(state.reject, flow, expectedPayload); expect(state.reject, 'to have a call satisfying', [flow, expectedPayload]);
}); });
}); });
describe('#handleRequest()', () => { describe('#handleRequest()', () => {
beforeEach(() => { beforeEach(() => {
sinon.stub(flow, 'setState'); sinon.stub(flow, 'setState').named('flow.setState');
sinon.stub(flow, 'run'); sinon.stub(flow, 'run').named('flow.run');
}); });
Object.entries({ Object.entries({
@ -188,30 +192,33 @@ describe('AuthFlow', () => {
it(`should transition to ${type.name} if ${path}`, () => { it(`should transition to ${type.name} if ${path}`, () => {
flow.handleRequest(path); flow.handleRequest(path);
sinon.assert.calledOnce(flow.setState); expect(flow.setState, 'was called once');
sinon.assert.calledWithExactly(flow.setState, sinon.match.instanceOf(type)); expect(flow.setState, 'to have a call satisfying', [
expect.it('to be a', type)
]);
}); });
}); });
it('should run setOAuthRequest if /', () => { it('should run setOAuthRequest if /', () => {
flow.handleRequest('/'); flow.handleRequest('/');
sinon.assert.calledOnce(flow.run); expect(flow.run, 'was called once');
sinon.assert.calledWithExactly(flow.run, 'setOAuthRequest', {}); expect(flow.run, 'to have a call satisfying', ['setOAuthRequest', {}]);
}); });
it('should call callback', () => { it('should call callback', () => {
const callback = sinon.stub(); const callback = sinon.stub().named('callback');
flow.handleRequest('/', () => {}, callback); flow.handleRequest('/', () => {}, callback);
sinon.assert.calledOnce(callback); expect(callback, 'was called once');
}); });
it('should not call callback till returned from #enter() promise will be resolved', () => { it('should not call callback till returned from #enter() promise will be resolved', () => {
let resolve; let resolve;
const promise = {then: (cb) => {resolve = cb;}}; const promise = {then: (cb) => {resolve = cb;}};
const callback = sinon.stub(); const callback = sinon.stub().named('callback');
const state = new AbstractState(); const state = new AbstractState();
state.enter = () => promise; state.enter = () => promise;
@ -219,11 +226,11 @@ describe('AuthFlow', () => {
flow.handleRequest('/', () => {}, callback); flow.handleRequest('/', () => {}, callback);
expect(resolve).to.be.a('function'); expect(resolve, 'to be', callback);
sinon.assert.notCalled(callback); expect(callback, 'was not called');
resolve(); resolve();
sinon.assert.calledOnce(callback); expect(callback, 'was called once');
}); });
it('should not handle the same request twice', () => { it('should not handle the same request twice', () => {
@ -233,13 +240,15 @@ describe('AuthFlow', () => {
flow.handleRequest(path, () => {}, callback); flow.handleRequest(path, () => {}, callback);
flow.handleRequest(path, () => {}, callback); flow.handleRequest(path, () => {}, callback);
sinon.assert.calledOnce(flow.setState); expect(flow.setState, 'was called once');
sinon.assert.calledTwice(callback); expect(flow.setState, 'to have a call satisfying', [
sinon.assert.calledWithExactly(flow.setState, sinon.match.instanceOf(OAuthState)); expect.it('to be a', OAuthState)
]);
expect(callback, 'was called twice');
}); });
it('throws if unsupported request', () => { it('throws if unsupported request', () => {
expect(() => flow.handleRequest('/foo/bar')).to.throw('Unsupported request: /foo/bar'); expect(() => flow.handleRequest('/foo/bar'), 'to throw', 'Unsupported request: /foo/bar');
}); });
}); });
}); });

View File

@ -1,3 +1,5 @@
import expect from 'unexpected';
import CompleteState from 'services/authFlow/CompleteState'; import CompleteState from 'services/authFlow/CompleteState';
import LoginState from 'services/authFlow/LoginState'; import LoginState from 'services/authFlow/LoginState';
import ActivationState from 'services/authFlow/ActivationState'; import ActivationState from 'services/authFlow/ActivationState';
@ -174,8 +176,8 @@ describe('CompleteState', () => {
'oAuthComplete', 'oAuthComplete',
sinon.match.object sinon.match.object
).returns({then(success, fail) { ).returns({then(success, fail) {
expect(success).to.be.a('function'); expect(success, 'to be a', 'function');
expect(fail).to.be.a('function'); expect(fail, 'to be a', 'function');
}}); }});
state.enter(context); state.enter(context);
@ -260,16 +262,16 @@ describe('CompleteState', () => {
describe('permissions accept', () => { describe('permissions accept', () => {
it('should set flags, when user accepted permissions', () => { it('should set flags, when user accepted permissions', () => {
state = new CompleteState(); state = new CompleteState();
expect(state.isPermissionsAccepted).to.be.undefined; expect(state.isPermissionsAccepted, 'to be undefined');
state = new CompleteState({accept: undefined}); state = new CompleteState({accept: undefined});
expect(state.isPermissionsAccepted).to.be.undefined; expect(state.isPermissionsAccepted, 'to be undefined');
state = new CompleteState({accept: true}); state = new CompleteState({accept: true});
expect(state.isPermissionsAccepted).to.be.true; expect(state.isPermissionsAccepted, 'to be true');
state = new CompleteState({accept: false}); state = new CompleteState({accept: false});
expect(state.isPermissionsAccepted).to.be.false; expect(state.isPermissionsAccepted, 'to be false');
}); });
it('should run oAuthComplete passing accept: true', () => { it('should run oAuthComplete passing accept: true', () => {

View File

@ -91,6 +91,7 @@ var webpackConfig = {
}, },
externals: isTest ? { externals: isTest ? {
sinon: 'sinon',
// http://airbnb.io/enzyme/docs/guides/webpack.html // http://airbnb.io/enzyme/docs/guides/webpack.html
cheerio: 'window', cheerio: 'window',
'react/lib/ExecutionEnvironment': true, 'react/lib/ExecutionEnvironment': true,