Replace ReduxNavigator by react-navigation

parent 29cf596d
......@@ -67,5 +67,6 @@ build android:
- ./gradlew --no-daemon ":app:assemble"
artifacts:
paths:
- $CI_PROJECT_DIR/android/app/build/outputs/apk/app-debug.apk
- $CI_PROJECT_DIR/android/app/build/outputs/apk/app-release.apk
\ No newline at end of file
- $CI_PROJECT_DIR/android/app/build/outputs/apk/debug/app-debug.apk
- $CI_PROJECT_DIR/android/app/build/outputs/apk/release/app-release-unsigned.apk
- $CI_PROJECT_DIR/android/app/build/outputs/apk/release/app-release.apk
\ No newline at end of file
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`navigation actions should create an action to navigate back 1`] = `
Object {
"type": "NAVIGATE_BACK",
}
`;
exports[`navigation actions should create an action to navigate to a screen 1`] = `
Object {
"payload": Object {
"newSection": false,
"scene": "scene",
},
"type": "NAVIGATE_NAVIGATE",
}
`;
exports[`navigation actions should create an action to navigate to a screen 2`] = `
Object {
"payload": Object {
"newSection": true,
"scene": "scene",
},
"type": "NAVIGATE_NAVIGATE",
}
`;
exports[`navigation actions should create an action to update the drawer state 1`] = `
Object {
"payload": Object {
"drawerOpen": "state",
},
"type": "NAVIGATE_OPENDRAWER",
}
`;
......@@ -24,8 +24,7 @@ Object {
exports[`navigation actions should create an action to load a profile 1`] = `
Object {
"payload": Object {
"member": "me",
"token": "token",
"member": "token",
},
"type": "PROFILE_PROFILE",
}
......@@ -34,8 +33,7 @@ Object {
exports[`navigation actions should create an action to load a profile 2`] = `
Object {
"payload": Object {
"member": 1,
"token": "abc123",
"member": "abc123",
},
"type": "PROFILE_PROFILE",
}
......
......@@ -6,17 +6,13 @@ Object {
"token": "token",
"username": "username",
},
"type": "SESSION_SUCCESS",
"type": "SESSION_SIGNED_IN",
}
`;
exports[`session actions should create an action for a successful user profile load 1`] = `
exports[`session actions should create an action to fetch user info 1`] = `
Object {
"payload": Object {
"displayName": "displayName",
"photo": "photo",
},
"type": "SESSION_PROFILE_SUCCESS",
"type": "SESSION_FETCH_USER_INFO",
}
`;
......@@ -26,28 +22,19 @@ Object {
}
`;
exports[`session actions should create an action to load the user profile 1`] = `
Object {
"payload": Object {
"token": "token",
},
"type": "SESSION_PROFILE",
}
`;
exports[`session actions should create an action to log the user in 1`] = `
Object {
"payload": Object {
"pass": "password",
"user": "username",
},
"type": "SESSION_LOGIN",
"type": "SESSION_SIGN_IN",
}
`;
exports[`session actions should create an action to log the user out 1`] = `
Object {
"type": "SESSION_LOGOUT",
"type": "SESSION_SIGN_OUT",
}
`;
......@@ -56,3 +43,13 @@ Object {
"type": "SESSION_TOKEN_INVALID",
}
`;
exports[`session actions should create an action to set user info 1`] = `
Object {
"payload": Object {
"displayName": "displayName",
"photo": "photo",
},
"type": "SESSION_SET_USER_INFO",
}
`;
import * as actions from '../../app/actions/navigation';
describe('navigation actions', () => {
it('should expose the navigation actions', () => {
expect(actions.NAVIGATE).toEqual('NAVIGATE_NAVIGATE');
expect(actions.BACK).toEqual('NAVIGATE_BACK');
expect(actions.OPENDRAWER).toEqual('NAVIGATE_OPENDRAWER');
});
it('should create an action to navigate to a screen', () => {
expect(actions.navigate('scene')).toMatchSnapshot();
expect(actions.navigate('scene', true)).toMatchSnapshot();
});
it('should create an action to navigate back', () => {
expect(actions.back()).toMatchSnapshot();
});
it('should create an action to update the drawer state', () => {
expect(actions.updateDrawer('state')).toMatchSnapshot();
});
});
\ No newline at end of file
......@@ -3,12 +3,12 @@ import * as actions from '../../app/actions/session';
describe('session actions', () => {
it('should expose the session actions', () => {
expect(actions.INIT).toEqual('SESSION_INIT');
expect(actions.SIGNED_IN).toEqual('SESSION_SUCCESS');
expect(actions.SIGN_IN).toEqual('SESSION_LOGIN');
expect(actions.SIGNED_IN).toEqual('SESSION_SIGNED_IN');
expect(actions.SIGN_IN).toEqual('SESSION_SIGN_IN');
expect(actions.TOKEN_INVALID).toEqual('SESSION_TOKEN_INVALID');
expect(actions.SIGN_OUT).toEqual('SESSION_LOGOUT');
expect(actions.FETCH_USER_INFO).toEqual('SESSION_PROFILE');
expect(actions.SET_USER_INFO).toEqual('SESSION_PROFILE_SUCCESS');
expect(actions.SIGN_OUT).toEqual('SESSION_SIGN_OUT');
expect(actions.FETCH_USER_INFO).toEqual('SESSION_FETCH_USER_INFO');
expect(actions.SET_USER_INFO).toEqual('SESSION_SET_USER_INFO');
});
it('should create an action to init the session', () => {
......@@ -31,11 +31,11 @@ describe('session actions', () => {
expect(actions.signOut()).toMatchSnapshot();
});
it('should create an action to load the user profile', () => {
expect(actions.profile('token')).toMatchSnapshot();
it('should create an action to fetch user info', () => {
expect(actions.fetchUserInfo()).toMatchSnapshot();
});
it('should create an action for a successful user profile load', () => {
expect(actions.userInfoSuccess('displayName', 'photo')).toMatchSnapshot();
it('should create an action to set user info', () => {
expect(actions.setUserInfo('displayName', 'photo')).toMatchSnapshot();
});
});
\ No newline at end of file
......@@ -7,8 +7,6 @@ import { apiRequest, tokenSelector } from '../../app/utils/url';
import calendarSaga from '../../app/sagas/calendar';
import * as calendarActions from '../../app/actions/calendar';
import * as navActions from '../../app/actions/navigation';
import { EVENT_LIST_SCENE } from '../../app/ui/components/navigator/scenes';
jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(() => {}),
......@@ -27,15 +25,6 @@ describe('calendar saga', () => {
.put(calendarActions.fetching())
.silentRun());
it('should start fetching on navigate', () => expectSaga(calendarSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.fn(apiRequest), []],
])
.dispatch(navActions.navigate(EVENT_LIST_SCENE))
.put(calendarActions.fetching())
.silentRun());
it('should put an error when the api request fails', () => expectSaga(calendarSaga)
.provide([
[select(tokenSelector), 'token'],
......
import * as matchers from 'redux-saga-test-plan/matchers';
import { select } from 'redux-saga/effects';
import { expectSaga } from 'redux-saga-test-plan';
import deepLinkingSaga, { parseURL } from '../../app/sagas/deepLinking';
import * as deepLinkingActions from '../../app/actions/deepLinking';
import { url as siteURL, apiRequest, loggedInSelector } from '../../app/utils/url';
import * as navigationActions from '../../app/actions/navigation';
import * as eventActions from '../../app/actions/event';
import * as loginActions from '../../app/actions/session';
import * as pizzaActions from '../../app/actions/pizza';
import { EVENT_LIST_SCENE } from '../../app/ui/components/navigator/scenes';
import { parseURL } from '../../app/sagas/deepLinking';
import { url as siteURL } from '../../app/utils/url';
describe('calendar saga', () => {
jest.mock('../../app/navigation', () => ({
navigate: jest.fn(),
}));
describe('deeplinking saga', () => {
it('should parse a URL correctly', () => {
const eventsUrl = parseURL(`${siteURL}/events/1`);
expect(eventsUrl).toEqual({ params: {}, path: '/events/1' });
......@@ -19,46 +14,4 @@ describe('calendar saga', () => {
const emptyUrl = parseURL('http://example.org/');
expect(emptyUrl).toEqual({ params: {}, path: '' });
});
it('should not do anything when no url is provided', () => expectSaga(deepLinkingSaga)
.dispatch(deepLinkingActions.deepLink(''))
.not.put.like({ type: 'DEEPLINKING_DEEPLINK' })
.silentRun());
it('should wait for login before processing deeplink', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), false],
[matchers.call.fn(apiRequest), []],
])
.dispatch(deepLinkingActions.deepLink(`${siteURL}/events/1/`))
.dispatch(loginActions.signedIn('', ''))
.put(eventActions.event('1'))
.silentRun());
it('should navigate to eventList on /events/ deeplink', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), true],
[matchers.call.fn(apiRequest), []],
])
.dispatch(deepLinkingActions.deepLink(`${siteURL}/events/`))
.put(navigationActions.navigate(EVENT_LIST_SCENE))
.silentRun());
it('shouldl load event on /events/{id} deeplink', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), true],
[matchers.call.fn(apiRequest), []],
])
.dispatch(deepLinkingActions.deepLink(`${siteURL}/events/1/`))
.put(eventActions.event('1'))
.silentRun());
it('should load pizzas on /pizzas/ deeplink', () => expectSaga(deepLinkingSaga)
.provide([
[select(loggedInSelector), true],
[matchers.call.fn(apiRequest), []],
])
.dispatch(deepLinkingActions.deepLink(`${siteURL}/pizzas/`))
.put(pizzaActions.retrievePizzaInfo())
.silentRun());
});
......@@ -7,14 +7,16 @@ import { apiRequest, tokenSelector } from '../../app/utils/url';
import eventSaga from '../../app/sagas/event';
import * as eventActions from '../../app/actions/event';
import * as navActions from '../../app/actions/navigation';
import { EVENT_SCENE } from '../../app/ui/components/navigator/scenes';
jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(() => {}),
tokenSelector: () => 'token',
}));
jest.mock('../../app/navigation', () => ({
navigate: jest.fn(),
}));
describe('event saga', () => {
const error = new Error('error');
......@@ -27,15 +29,6 @@ describe('event saga', () => {
.put(eventActions.fetching())
.silentRun());
it('should navigate to the event scene', () => expectSaga(eventSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.fn(apiRequest), []],
])
.dispatch(eventActions.event(1))
.put(navActions.navigate(EVENT_SCENE))
.silentRun());
it('should put an error when the api request fails', () => expectSaga(eventSaga)
.provide([
[select(tokenSelector), 'token'],
......
......@@ -5,14 +5,16 @@ import { throwError } from 'redux-saga-test-plan/providers';
import { apiRequest, tokenSelector } from '../../app/utils/url';
import pizzaSaga from '../../app/sagas/pizza';
import * as pizzaActions from '../../app/actions/pizza';
import * as navigationActions from '../../app/actions/navigation';
import { PIZZA_SCENE } from '../../app/ui/components/navigator/scenes';
jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(() => {}),
tokenSelector: () => 'token',
}));
jest.mock('../../app/navigation', () => ({
navigate: jest.fn(),
}));
describe('pizza saga', () => {
const error = new Error('error');
error.response = null;
......@@ -22,16 +24,6 @@ describe('pizza saga', () => {
});
describe('retrieve pizza info', () => {
it('should start fetching and navigate on retrieve action', () => expectSaga(pizzaSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.fn(apiRequest), []],
])
.dispatch(pizzaActions.retrievePizzaInfo())
.put(pizzaActions.fetching())
.put(navigationActions.navigate(PIZZA_SCENE))
.silentRun());
describe('failures', () => {
beforeAll(() => {
error.response = null;
......@@ -254,4 +246,4 @@ describe('pizza saga', () => {
}));
});
});
});
\ No newline at end of file
});
......@@ -5,14 +5,16 @@ import { throwError } from 'redux-saga-test-plan/providers';
import profileSaga from '../../app/sagas/profile';
import { apiRequest, tokenSelector } from '../../app/utils/url';
import * as profileActions from '../../app/actions/profile';
import * as navActions from '../../app/actions/navigation';
import { PROFILE_SCENE } from '../../app/ui/components/navigator/scenes';
jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(() => {}),
tokenSelector: () => 'token',
}));
jest.mock('../../app/navigation', () => ({
navigate: jest.fn(),
}));
describe('profile saga', () => {
const error = new Error('error');
......@@ -21,9 +23,8 @@ describe('profile saga', () => {
[select(tokenSelector), 'token'],
[matchers.call.fn(apiRequest), []],
])
.dispatch(profileActions.profile('token', 1))
.dispatch(profileActions.profile(1))
.put(profileActions.fetching())
.put(navActions.navigate(PROFILE_SCENE))
.silentRun());
it('should put success when the request succeeds', () => expectSaga(profileSaga)
......@@ -31,7 +32,7 @@ describe('profile saga', () => {
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['members/1'] }), 'data'],
])
.dispatch(profileActions.profile('token', 1))
.dispatch(profileActions.profile(1))
.put(profileActions.success('data'))
.silentRun());
......@@ -40,7 +41,7 @@ describe('profile saga', () => {
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['members/1'] }), throwError(error)],
])
.dispatch(profileActions.profile('token', 1))
.dispatch(profileActions.profile(1))
.put(profileActions.failure())
.silentRun());
......@@ -48,7 +49,7 @@ describe('profile saga', () => {
.provide([
[select(tokenSelector), 'token'],
])
.dispatch(profileActions.profile('token', 1))
.dispatch(profileActions.profile(1))
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('members/1', {
......
......@@ -7,8 +7,6 @@ import * as registrationActions from '../../app/actions/registration';
import registrationSaga, { eventSelector } from '../../app/sagas/registration';
import { apiRequest, tokenSelector } from '../../app/utils/url';
import * as eventActions from '../../app/actions/event';
import * as navigationActions from '../../app/actions/navigation';
import { REGISTRATION_SCENE } from '../../app/ui/components/navigator/scenes';
jest.mock('react-native-snackbar', () => ({
LENGTH_LONG: 100,
......@@ -16,6 +14,11 @@ jest.mock('react-native-snackbar', () => ({
dismiss: jest.fn(),
}));
jest.mock('../../app/navigation', () => ({
navigate: jest.fn(),
goBack: jest.fn(),
}));
jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(() => {}),
tokenSelector: () => 'token',
......@@ -66,16 +69,6 @@ describe('registration saga', () => {
);
}));
it('should put a retrieve fields action when they are available', () => expectSaga(registrationSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['events/1/registrations'] }),
{ fields: {}, pk: 2 }],
])
.dispatch(registrationActions.register(1))
.put(registrationActions.retrieveFields(2))
.silentRun());
it('should show a failure action when the request fails', () => expectSaga(registrationSaga)
.provide([
[select(tokenSelector), 'token'],
......@@ -122,15 +115,6 @@ describe('registration saga', () => {
.put(registrationActions.success())
.silentRun());
it('should navigate back on success', () => expectSaga(registrationSaga)
.provide([
[select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['registrations/1'] })],
])
.dispatch(registrationActions.update(1, {}))
.put(navigationActions.back())
.silentRun());
it('should show a snackbar on success', () => expectSaga(registrationSaga)
.provide([
[select(tokenSelector), 'token'],
......@@ -253,11 +237,6 @@ describe('registration saga', () => {
.put(registrationActions.loading())
.silentRun());
it('should navigate to the registration screen', () => expectSaga(registrationSaga)
.dispatch(registrationActions.retrieveFields(1))
.put(navigationActions.navigate(REGISTRATION_SCENE))
.silentRun());
it('should put showFields action when the request succeeds', () => expectSaga(registrationSaga)
.provide([
[select(tokenSelector), 'token'],
......
......@@ -12,6 +12,7 @@ import { apiRequest } from '../../app/utils/url';
import * as sessionActions from '../../app/actions/session';
import * as pushNotificationsActions from '../../app/actions/pushNotifications';
jest.mock('react-native-snackbar', () => ({
LENGTH_LONG: 100,
show: jest.fn(),
......@@ -22,12 +23,17 @@ jest.mock('react-native', () => ({
AsyncStorage: {
multiSet: jest.fn(),
multiRemove: jest.fn(),
clear: jest.fn(),
},
}));
jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(() => {}),
tokenSelector: () => 'token',
tokenSelector: () => 'abc123',
}));
jest.mock('../../app/navigation', () => ({
navigate: jest.fn(),
}));
jest.mock('react-native-sentry', () => ({
......@@ -56,7 +62,7 @@ describe('session saga', () => {
[matchers.call.like({ fn: Sentry.setUserContext }), {}],
])
.put(sessionActions.signedIn('username', 'abc123'))
.put(sessionActions.profile('abc123'))
.put(sessionActions.fetchUserInfo())
.dispatch(sessionActions.signIn('username', 'password'))
.silentRun());
......@@ -121,7 +127,7 @@ describe('session saga', () => {
.dispatch(sessionActions.signOut())
.silentRun()
.then(() => {
expect(AsyncStorage.multiRemove).toBeCalledWith([USERNAMEKEY, TOKENKEY]);
expect(AsyncStorage.clear).toBeCalled();
}));
it('should put a push notification invalidation action', () => expectSaga(sessionSaga)
......@@ -149,8 +155,8 @@ describe('session saga', () => {
},
}],
])
.put(sessionActions.userInfoSuccess('Johnny Test', 'http://example.org/photo.png'))
.dispatch(sessionActions.profile('abc123'))
.put(sessionActions.setUserInfo('Johnny Test', 'http://example.org/photo.png'))
.dispatch(sessionActions.fetchUserInfo())
.silentRun());
it('should save the token in the AsyncStorage when the request succeeds', () => expectSaga(sessionSaga)
......@@ -162,7 +168,7 @@ describe('session saga', () => {
},
}],
])
.dispatch(sessionActions.profile('abc123'))
.dispatch(sessionActions.fetchUserInfo())
.silentRun()
.then(() => {
expect(AsyncStorage.multiSet).toBeCalledWith([
......@@ -175,11 +181,11 @@ describe('session saga', () => {
.provide([
[matchers.call.fn(apiRequest), throwError(error)],
])
.dispatch(sessionActions.profile('token'))
.dispatch(sessionActions.fetchUserInfo())
.silentRun());
it('should do a GET request', () => expectSaga(sessionSaga)
.dispatch(sessionActions.profile('abc123'))
.dispatch(sessionActions.fetchUserInfo())
.silentRun()
.then(() => {
expect(apiRequest).toBeCalledWith('members/me', {
......
......@@ -4,6 +4,10 @@ import configureStore from 'redux-mock-store'
import MemberView from '../../../../app/ui/components/memberView/MemberView';
import reducer from '../../../../app/reducers/index';
jest.mock('react-navigation', () => ({
withNavigation: component => component,
}));
describe('MemberView component', () => {
const mockStore = configureStore(reducer);
const initialState = {
......
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import renderer from 'react-test-renderer';
import ReduxNavigator from '../../../../app/ui/components/navigator/ReduxNavigator';
import reducer from '../../../../app/reducers';
import { LOGIN_SCENE } from '../../../../app/ui/components/navigator/scenes';
describe('ReduxNavigator component', () => {
const mockStore = configureStore(reducer);
const initialState = {
navigation: {
currentScene: LOGIN_SCENE,
previousScenes: [],
drawerOpen: false,
},
};
const store = mockStore(initialState);
it('renders correctly', () => {
const tree = renderer
.create(
<Provider store={store}>
<ReduxNavigator />
</Provider>,
)
.toJSON();
expect(tree).toMatchSnapshot();
});
});
import React from 'react';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import Sidebar from '../../../../app/ui/components/navigator/Sidebar';
import Sidebar from '../../../../app/ui/components/sidebar/Sidebar';
import reducer from '../../../../app/reducers';
const mockNavigation = {
navigate: jest.fn(),
};
describe('Sidebar component', () => {