Commit cfe30d84 authored by Gijs Hendriksen's avatar Gijs Hendriksen
Browse files

Updated and added tests for all sagas

parent d05d7d39
import { parseURL } from '../../app/sagas/deepLinking';
import { Linking } from 'react-native';
import { select } from 'redux-saga/effects';
import { expectSaga } from 'redux-saga-test-plan';
import deepLinkingSaga, { parseURL } from '../../app/sagas/deepLinking';
import { url as siteURL } from '../../app/utils/url';
import * as actions from '../../app/actions/deepLinking';
import * as sessionActions from '../../app/actions/session';
import * as pizzaActions from '../../app/actions/pizza';
import * as calendarActions from '../../app/actions/calendar';
import * as eventActions from '../../app/actions/event';
import { loggedInSelector } from '../../app/selectors/session';
jest.mock('../../app/navigation', () => ({
navigate: jest.fn(),
}));
......@@ -14,4 +26,65 @@ describe('deeplinking saga', () => {
const emptyUrl = parseURL('http://example.org/');
expect(emptyUrl).toEqual({ params: {}, path: '' });
});
it('should do nothing without a URL', () => expectSaga(deepLinkingSaga)
.dispatch(actions.deepLink())
.silentRun()
.then(({ effects }) => {
expect(effects.call).toBeUndefined();
expect(effects.select).toBeUndefined();
expect(effects.put).toBeUndefined();
}));
it('should wait for the user to be logged in', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), false],
])
.dispatch(actions.deepLink('http://example.org/'))
.take(sessionActions.SIGNED_IN)
.silentRun());
it('should not open an unknown url outside the app if stayInApp is true', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), true],
])
.dispatch(actions.deepLink('http://example.org/', true))
.silentRun()
.then(() => {
expect(Linking.openURL).not.toBeCalled();
}));
it('should open an unknown url outside the app if specified', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), true],
])
.dispatch(actions.deepLink('http://example.org/', false))
.silentRun()
.then(() => {
expect(Linking.openURL).toBeCalledWith('http://example.org/');
}));
it('should open the pizza url', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), true],
])
.dispatch(actions.deepLink(`${siteURL}/pizzas/`))
.put(pizzaActions.retrievePizzaInfo())
.silentRun());
it('should open the events calendar url', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), true],
])
.dispatch(actions.deepLink(`${siteURL}/events/`))
.put(calendarActions.open())
.silentRun());
it('should open an event detail', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), true],
])
.dispatch(actions.deepLink(`${siteURL}/events/1/`))
.put(eventActions.event('1'))
.silentRun());
});
import { Dimensions } from 'react-native';
import { select } from 'redux-saga/effects';
import { expectSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import membersSaga from '../../app/sagas/members';
import { tokenSelector } from '../../app/selectors/session';
import * as memberActions from '../../app/actions/members';
import { apiRequest } from '../../app/utils/url';
jest.mock('../../app/ui/components/standardHeader/style/StandardHeader', () => ({
TOTAL_BAR_HEIGHT: 20,
}));
jest.mock('../../app/ui/screens/memberList/style/MemberList', () => ({
memberSize: 24,
}));
jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(),
}));
jest.mock('Dimensions', () => ({
__esModule: true,
get: () => ({
height: 40 * 3 + 20,
}),
}));
global.fetch = jest.fn();
describe('members saga', () => {
describe('load members', () => {
it('should indicate the member list is being loaded', () => expectSaga(membersSaga)
.provide([
[select(tokenSelector), 'token'],
])
.dispatch(memberActions.members())
.put(memberActions.fetching())
.silentRun());
it('should load the member list without keywords', () => expectSaga(membersSaga)
.provide([
[select(tokenSelector), 'token'],
])
.dispatch(memberActions.members())
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('members', {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Token token',
},
}, {
limit: 3 * 6,
});
}));
it('should base the amount to fetch on the window height', () => {
Dimensions.get = () => ({ height: 40 * 4 + 20 });
expectSaga(membersSaga)
.provide([
[select(tokenSelector), 'token'],
])
.dispatch(memberActions.members())
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('members', {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Token token',
},
}, {
limit: 4 * 6,
});
});
Dimensions.get = () => ({ height: 40 * 3 + 20 });
});
it('should put the result data when the request succeeds', () => expectSaga(membersSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['members'] }), { results: [{ pk: 1 }], next: 'moreUrl' }],
])
.dispatch(memberActions.members())
.put(memberActions.success([{ pk: 1 }], 'moreUrl', ''))
.silentRun());
it('should load the member list with keywords', () => expectSaga(membersSaga)
.provide([
[select(tokenSelector), 'token'],
])
.dispatch(memberActions.members('John Doe'))
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('members', {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Token token',
},
}, {
limit: 3 * 6,
search: 'John Doe',
});
}));
it('should put the result data and keywords when the request succeeds', () => expectSaga(membersSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['members'] }), { results: [{ pk: 1 }], next: 'moreUrl' }],
])
.dispatch(memberActions.members('John Doe'))
.put(memberActions.success([{ pk: 1 }], 'moreUrl', 'John Doe'))
.silentRun());
it('should put an error when the request fails', () => expectSaga(membersSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.call(apiRequest, throwError)],
])
.dispatch(memberActions.members())
.put(memberActions.failure())
.silentRun());
});
describe('load more members', () => {
it('should indicate the member list is being loaded', () => expectSaga(membersSaga)
.provide([
[select(tokenSelector), 'token'],
])
.dispatch(memberActions.more('moreUrl'))
.put(memberActions.fetching())
.silentRun());
it('should load more members', () => expectSaga(membersSaga)
.provide([
[select(tokenSelector), 'token'],
])
.dispatch(memberActions.more('moreUrl'))
.silentRun()
.then(() => {
expect(fetch).toBeCalledWith('moreUrl', {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Token token',
},
});
}));
it('should put the result data when the request succeeds', () => {
const response = {
json: Promise.resolve({
results: [{ pk: 1 }],
next: 'evenMoreUrl',
}),
};
global.fetch.mockReturnValueOnce(Promise.resolve(response));
expectSaga(membersSaga)
.provide([
[select(tokenSelector), 'token'],
])
.dispatch(memberActions.more('moreUrl'))
.put(memberActions.moreSuccess([{ pk: 1 }], 'evenMoreUrl'))
.silentRun();
});
it('should put empty result data when the request fails', () => expectSaga(membersSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.call(fetch, throwError)],
])
.dispatch(memberActions.more('moreUrl'))
.put(memberActions.moreSuccess([], null))
.silentRun());
});
});
import { expectSaga } from 'redux-saga-test-plan';
import navigationSaga from '../../app/sagas/navigation';
import * as navigationActions from '../../app/actions/navigation';
import * as eventActions from '../../app/actions/event';
import * as profileActions from '../../app/actions/profile';
import * as pizzaActions from '../../app/actions/pizza';
import * as registrationActions from '../../app/actions/registration';
import * as sessionActions from '../../app/actions/session';
import * as calendarActions from '../../app/actions/calendar';
import * as membersActions from '../../app/actions/members';
import * as welcomeActions from '../../app/actions/welcome';
import { settingsActions } from '../../app/actions/settings';
import NavigationService from '../../app/navigation';
jest.mock('../../app/navigation', () => ({
__esModule: true,
default: {
goBack: jest.fn(),
toggleDrawer: jest.fn(),
navigate: jest.fn(),
},
}));
describe('navigation saga', () => {
describe('back saga', () => {
it('should go back on a back action', () => expectSaga(navigationSaga)
.dispatch(navigationActions.goBack())
.silentRun()
.then(() => {
expect(NavigationService.goBack).toBeCalled();
}));
it('should go back when a registration is saved', () => expectSaga(navigationSaga)
.dispatch(registrationActions.success())
.silentRun()
.then(() => {
expect(NavigationService.goBack).toBeCalled();
}));
});
describe('drawer saga', () => {
it('should toggle the drawer on a toggle drawer action', () => expectSaga(navigationSaga)
.dispatch(navigationActions.toggleDrawer())
.silentRun()
.then(() => {
expect(NavigationService.toggleDrawer).toBeCalled();
}));
});
describe('navigation saga', () => {
it('should open the welcome screen', () => expectSaga(navigationSaga)
.dispatch(welcomeActions.open())
.silentRun()
.then(() => {
expect(NavigationService.navigate).toBeCalledWith('Welcome');
}));
it('should open the settings screen', () => expectSaga(navigationSaga)
.dispatch(settingsActions.open())
.silentRun()
.then(() => {
expect(NavigationService.navigate).toBeCalledWith('Settings');
}));
it('should open the calendar screen', () => expectSaga(navigationSaga)
.dispatch(calendarActions.open())
.silentRun()
.then(() => {
expect(NavigationService.navigate).toBeCalledWith('Calendar');
}));
it('should open the members screen', () => expectSaga(navigationSaga)
.dispatch(membersActions.members())
.silentRun()
.then(() => {
expect(NavigationService.navigate).toBeCalledWith('MemberList');
}));
it('should open the event screen', () => expectSaga(navigationSaga)
.dispatch(eventActions.event(1))
.silentRun()
.then(() => {
expect(NavigationService.navigate).toBeCalledWith('Event');
}));
it('should open the profile screen', () => expectSaga(navigationSaga)
.dispatch(profileActions.profile())
.silentRun()
.then(() => {
expect(NavigationService.navigate).toBeCalledWith('Profile');
}));
it('should open the registration screen', () => expectSaga(navigationSaga)
.dispatch(registrationActions.retrieveFields(1))
.silentRun()
.then(() => {
expect(NavigationService.navigate).toBeCalledWith('Registration');
}));
it('should open the pizza screen', () => expectSaga(navigationSaga)
.dispatch(pizzaActions.retrievePizzaInfo())
.silentRun()
.then(() => {
expect(NavigationService.navigate).toBeCalledWith('Pizza');
}));
it('should switch to the signed in navigator', () => expectSaga(navigationSaga)
.dispatch(sessionActions.signedIn('user', 'token'))
.silentRun()
.then(() => {
expect(NavigationService.navigate).toBeCalledWith('SignedIn');
}));
it('should sign out with invalid token', () => expectSaga(navigationSaga)
.dispatch(sessionActions.tokenInvalid())
.silentRun()
.then(() => {
expect(NavigationService.navigate).toBeCalledWith('Auth');
}));
it('should sign out on a sign out action', () => expectSaga(navigationSaga)
.dispatch(sessionActions.signOut())
.silentRun()
.then(() => {
expect(NavigationService.navigate).toBeCalledWith('Auth');
}));
});
});
......@@ -11,14 +11,6 @@ jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(() => {}),
}));
jest.mock('../../app/navigation', () => ({
navigate: jest.fn(),
}));
jest.mock('../../app/selectors/session', () => ({
tokenSelector: () => 'token',
}));
describe('pizza saga', () => {
const error = new Error('error');
error.response = null;
......
......@@ -11,14 +11,6 @@ jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(() => {}),
}));
jest.mock('../../app/navigation', () => ({
navigate: jest.fn(),
}));
jest.mock('../../app/selectors/session', () => ({
tokenSelector: () => 'token',
}));
describe('profile saga', () => {
const error = new Error('error');
......
import { select } from 'redux-saga/effects';
import { expectSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { Platform } from 'react-native';
import pushNotificationsSaga from '../../app/sagas/pushNotifications';
import { apiRequest } from '../../app/utils/url';
......@@ -11,10 +10,6 @@ jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(),
}));
jest.mock('../../app/selectors/session', () => ({
tokenSelector: () => 'token',
}));
const mockIid = {
delete: jest.fn(),
};
......@@ -43,6 +38,17 @@ describe('pushNotifications saga', () => {
apiRequest.mockReset();
});
it('should do nothing without a token', () => expectSaga(pushNotificationsSaga)
.provide([
[select(tokenSelector), undefined],
])
.dispatch(pushActions.register())
.silentRun()
.then(({ effects }) => {
expect(effects.call).toBeUndefined();
expect(effects.put).toBeUndefined();
}));
it('should request permissions when platform is iOS', () => expectSaga(pushNotificationsSaga)
.provide([
[select(tokenSelector), 'token'],
......@@ -69,7 +75,6 @@ describe('pushNotifications saga', () => {
it('should post a token to the server', () => expectSaga(pushNotificationsSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['events'] }), { results: 'data' }],
])
.dispatch(pushActions.register())
.silentRun()
......@@ -85,6 +90,25 @@ describe('pushNotifications saga', () => {
method: 'POST',
});
}));
it('should post the correct categories to the server', () => expectSaga(pushNotificationsSaga)
.provide([
[select(tokenSelector), 'token'],
])
.dispatch(pushActions.register(['general', 'events']))
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('devices',
{
body: '{"type":"ios","receive_category":["general","events"]}',
headers: {
Accept: 'application/json',
Authorization: 'Token token',
'Content-Type': 'application/json',
},
method: 'POST',
});
}));
});
describe('invalidate', () => {
......
......@@ -24,10 +24,6 @@ jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(() => {}),
}));
jest.mock('../../app/selectors/session', () => ({
tokenSelector: () => 'token',
}));
describe('event selector', () => {
it('should select the event pk', () => {
expect(eventSelector({ event: { data: { pk: 2 } } })).toEqual(2);
......@@ -236,7 +232,19 @@ describe('registration saga', () => {
});
describe('fields', () => {
it('should retrieve the fields after a successful registration', () => expectSaga(registrationSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.fn(apiRequest), { pk: 1, fields: 'fields' }],
])
.dispatch(registrationActions.register(2))
.put(registrationActions.retrieveFields(1))
.silentRun());
it('should put a loading action', () => expectSaga(registrationSaga)
.provide([
[select(tokenSelector), 'token'],
])
.dispatch(registrationActions.retrieveFields(1))
.put(registrationActions.loading())
.silentRun());
......
......@@ -42,7 +42,7 @@ jest.mock('../../app/navigation', () => ({
jest.mock('react-native-sentry', () => ({
Sentry: {
setUserContext: () => {},
captureException: () => {},
captureException: jest.fn(),
},
}));
......@@ -177,12 +177,15 @@ describe('session saga', () => {
]);
}));
it('should not care about errors', () => expectSaga(sessionSaga)
it('should log any errors', () => expectSaga(sessionSaga)
.provide([
[matchers.call.fn(apiRequest), throwError(error)],
])
.dispatch(sessionActions.fetchUserInfo())
.silentRun());
.silentRun()
.then(() => {
expect(Sentry.captureException).toBeCalled();
}));
it('should do a GET request', () => expectSaga(sessionSaga)
.dispatch(sessionActions.fetchUserInfo())
......
import { expectSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { select } from 'redux-saga/effects';
import { AsyncStorage } from 'react-native';
import { Sentry } from 'react-native-sentry';
import settingsSaga from '../../app/sagas/settings';
import { notificationsSettingsActions, settingsActions } from '../../app/actions/settings';
import * as pushNotificationActions from '../../app/actions/pushNotifications';
import { tokenSelector } from '../../app/selectors/session';
import { apiRequest } from '../../app/utils/url';
jest.mock('react-native', () => ({
AsyncStorage: {
getItem: jest.fn(),
setItem: jest.fn(),
},
}));
jest.mock('react-native-sentry', () => ({
Sentry: {
captureException: jest.fn(),
},
}));
jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(),