mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-12-15 09:49:01 +05:30
Базовые тесты для authFlow. Осталось покрыть navigate и смену стейтов
This commit is contained in:
parent
291b75a3b0
commit
f66759eaa9
@ -34,6 +34,12 @@ const store = applyMiddleware(
|
||||
thunk
|
||||
)(createStore)(reducer);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// some shortcuts for testing on localhost
|
||||
|
||||
window.testOAuth = () => location.href = '/oauth?client_id=ely&redirect_uri=http%3A%2F%2Fely.by&response_type=code&scope=minecraft_server_session';
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<ReduxProvider store={store}>
|
||||
|
@ -1,5 +1,14 @@
|
||||
import AuthFlow from './authFlow/AuthFlow';
|
||||
|
||||
// TODO: a way to unload service (when we are on account page)
|
||||
import * as actions from 'components/auth/actions';
|
||||
import {updateUser} from 'components/user/actions';
|
||||
|
||||
export default new AuthFlow();
|
||||
const availableActions = {
|
||||
...actions,
|
||||
updateUser,
|
||||
redirect(url) {
|
||||
location.href = url;
|
||||
}
|
||||
};
|
||||
|
||||
export default new AuthFlow(availableActions);
|
||||
|
@ -1,22 +1,25 @@
|
||||
import { routeActions } from 'react-router-redux';
|
||||
|
||||
import * as actions from 'components/auth/actions';
|
||||
import {updateUser} from 'components/user/actions';
|
||||
|
||||
import RegisterState from './RegisterState';
|
||||
import LoginState from './LoginState';
|
||||
import OAuthState from './OAuthState';
|
||||
import ForgotPasswordState from './ForgotPasswordState';
|
||||
|
||||
const availableActions = {
|
||||
...actions,
|
||||
updateUser,
|
||||
redirect(url) {
|
||||
location.href = url;
|
||||
}
|
||||
};
|
||||
// TODO: a way to unload service (when we are on account page)
|
||||
|
||||
export default class AuthFlow {
|
||||
constructor(actions) {
|
||||
if (typeof actions !== 'object') {
|
||||
throw new Error('AuthFlow requires an actions object');
|
||||
}
|
||||
|
||||
this.actions = actions;
|
||||
|
||||
if (Object.freeze) {
|
||||
Object.freeze(this.actions);
|
||||
}
|
||||
}
|
||||
|
||||
setStore(store) {
|
||||
this.navigate = (route) => {
|
||||
const {routing} = this.getState();
|
||||
@ -48,11 +51,11 @@ export default class AuthFlow {
|
||||
}
|
||||
|
||||
run(actionId, payload) {
|
||||
if (!availableActions[actionId]) {
|
||||
if (!this.actions[actionId]) {
|
||||
throw new Error(`Action ${actionId} does not exists`);
|
||||
}
|
||||
|
||||
return this.dispatch(availableActions[actionId](payload));
|
||||
return this.dispatch(this.actions[actionId](payload));
|
||||
}
|
||||
|
||||
setState(state) {
|
||||
@ -110,8 +113,4 @@ export default class AuthFlow {
|
||||
throw new Error(`Unsupported request: ${path}`);
|
||||
}
|
||||
}
|
||||
|
||||
login() {
|
||||
this.setState(new LoginState());
|
||||
}
|
||||
}
|
||||
|
143
tests/services/authFlow/AuthFlow.test.js
Normal file
143
tests/services/authFlow/AuthFlow.test.js
Normal file
@ -0,0 +1,143 @@
|
||||
import AuthFlow from 'services/authFlow/AuthFlow';
|
||||
import AbstractState from 'services/authFlow/AbstractState';
|
||||
|
||||
// TODO: navigate and state switching
|
||||
|
||||
describe('AuthFlow', () => {
|
||||
let flow;
|
||||
let actions;
|
||||
|
||||
beforeEach(() => {
|
||||
actions = {test: sinon.stub()};
|
||||
actions.test.returns('passed');
|
||||
|
||||
flow = new AuthFlow(actions);
|
||||
});
|
||||
|
||||
it('throws when no actions provided', () => {
|
||||
expect(() => new AuthFlow()).to.throw('AuthFlow requires an actions object');
|
||||
});
|
||||
|
||||
it('should not allow to mutate actions', () => {
|
||||
expect(() => flow.actions.foo = 'bar').to.throw(/readonly/);
|
||||
expect(() => flow.actions.test = 'hacked').to.throw(/readonly/);
|
||||
});
|
||||
|
||||
describe('#setState', () => {
|
||||
it('should change state', () => {
|
||||
const state = new AbstractState();
|
||||
flow.setState(state);
|
||||
|
||||
expect(flow.state).to.be.equal(state);
|
||||
});
|
||||
|
||||
it('should call `enter` on new state and pass reference to itself', () => {
|
||||
const state = new AbstractState();
|
||||
const spy = sinon.spy(state, 'enter');
|
||||
|
||||
flow.setState(state);
|
||||
|
||||
sinon.assert.calledWith(spy, flow);
|
||||
sinon.assert.calledOnce(spy);
|
||||
});
|
||||
|
||||
it('should call `leave` on previous state if any', () => {
|
||||
const state1 = new AbstractState();
|
||||
const state2 = new AbstractState();
|
||||
const spy1 = sinon.spy(state1, 'leave');
|
||||
const spy2 = sinon.spy(state2, 'leave');
|
||||
|
||||
flow.setState(state1);
|
||||
flow.setState(state2);
|
||||
|
||||
sinon.assert.calledWith(spy1, flow);
|
||||
sinon.assert.calledOnce(spy1);
|
||||
sinon.assert.notCalled(spy2);
|
||||
});
|
||||
|
||||
it('should throw if no state', () => {
|
||||
expect(() => flow.setState()).to.throw('State is required');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#run', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = {
|
||||
getState() {},
|
||||
dispatch: sinon.stub()
|
||||
};
|
||||
|
||||
flow.setStore(store);
|
||||
});
|
||||
|
||||
it('should dispatch an action', () => {
|
||||
flow.run('test');
|
||||
|
||||
sinon.assert.calledOnce(store.dispatch);
|
||||
sinon.assert.calledWith(store.dispatch, 'passed');
|
||||
});
|
||||
|
||||
it('should dispatch an action with payload given', () => {
|
||||
flow.run('test', 'arg');
|
||||
|
||||
sinon.assert.calledOnce(actions.test);
|
||||
sinon.assert.calledWith(actions.test, 'arg');
|
||||
});
|
||||
|
||||
it('should return action dispatch result', () => {
|
||||
const expected = 'dispatch called';
|
||||
store.dispatch.returns(expected);
|
||||
|
||||
expect(flow.run('test')).to.be.equal(expected);
|
||||
});
|
||||
|
||||
it('throws when running unexisted action', () => {
|
||||
expect(() => flow.run('123')).to.throw('Action 123 does not exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#goBack', () => {
|
||||
it('should call goBack on state passing itself as argument', () => {
|
||||
const state = new AbstractState();
|
||||
sinon.stub(state, 'goBack');
|
||||
flow.setState(state);
|
||||
|
||||
flow.goBack();
|
||||
|
||||
sinon.assert.calledOnce(state.goBack);
|
||||
sinon.assert.calledWith(state.goBack, flow);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#resolve', () => {
|
||||
it('should call resolve on state passing itself and payload as arguments', () => {
|
||||
const state = new AbstractState();
|
||||
sinon.stub(state, 'resolve');
|
||||
flow.setState(state);
|
||||
|
||||
const expectedPayload = {foo: 'bar'};
|
||||
|
||||
flow.resolve(expectedPayload);
|
||||
|
||||
sinon.assert.calledOnce(state.resolve);
|
||||
sinon.assert.calledWithExactly(state.resolve, flow, expectedPayload);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#reject', () => {
|
||||
it('should call reject on state passing itself and payload as arguments', () => {
|
||||
const state = new AbstractState();
|
||||
sinon.stub(state, 'reject');
|
||||
flow.setState(state);
|
||||
|
||||
const expectedPayload = {foo: 'bar'};
|
||||
|
||||
flow.reject(expectedPayload);
|
||||
|
||||
sinon.assert.calledOnce(state.reject);
|
||||
sinon.assert.calledWithExactly(state.reject, flow, expectedPayload);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user