Verified Commit f2d05d7f authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Finish profile picture update

parent c51c51a2
...@@ -8,9 +8,7 @@ Object { ...@@ -8,9 +8,7 @@ Object {
exports[`profile actions should create an action for a successful profile load 1`] = ` exports[`profile actions should create an action for a successful profile load 1`] = `
Object { Object {
"payload": Object { "payload": "profile",
"profileData": "profile",
},
"type": "PROFILE_SUCCESS", "type": "PROFILE_SUCCESS",
} }
`; `;
......
...@@ -49,6 +49,7 @@ Object { ...@@ -49,6 +49,7 @@ Object {
"payload": Object { "payload": Object {
"displayName": "displayName", "displayName": "displayName",
"photo": "photo", "photo": "photo",
"pk": "pk",
}, },
"type": "SESSION_SET_USER_INFO", "type": "SESSION_SET_USER_INFO",
} }
......
...@@ -36,6 +36,6 @@ describe('session actions', () => { ...@@ -36,6 +36,6 @@ describe('session actions', () => {
}); });
it('should create an action to set user info', () => { it('should create an action to set user info', () => {
expect(actions.setUserInfo('displayName', 'photo')).toMatchSnapshot(); expect(actions.setUserInfo('pk', 'displayName', 'photo')).toMatchSnapshot();
}); });
}); });
\ No newline at end of file
...@@ -14,7 +14,6 @@ Object { ...@@ -14,7 +14,6 @@ Object {
"birthday": "", "birthday": "",
"display_name": "", "display_name": "",
"membership_type": "", "membership_type": "",
"photo": "http://localhost:8000/static/members/images/default-avatar.jpg",
"pk": -1, "pk": -1,
"profile_description": "", "profile_description": "",
"programme": "", "programme": "",
......
...@@ -4,6 +4,7 @@ exports[`session reducer initially should return the initial state 1`] = ` ...@@ -4,6 +4,7 @@ exports[`session reducer initially should return the initial state 1`] = `
Object { Object {
"displayName": "", "displayName": "",
"photo": "http://localhost:8000/static/members/images/default-avatar.jpg", "photo": "http://localhost:8000/static/members/images/default-avatar.jpg",
"pk": -1,
"status": "SIGNED_OUT", "status": "SIGNED_OUT",
"token": "", "token": "",
"username": "", "username": "",
......
...@@ -54,9 +54,13 @@ describe('session reducer', () => { ...@@ -54,9 +54,13 @@ describe('session reducer', () => {
describe('has retrieved user info', () => { describe('has retrieved user info', () => {
const state = reducer( const state = reducer(
emptyState, emptyState,
actions.setUserInfo('John Doe', 'imageUrl'), actions.setUserInfo('pk', 'John Doe', 'imageUrl'),
); );
it('should contain the primary key', () => {
expect(state).toHaveProperty('pk', 'pk');
});
it('should contain the display name', () => { it('should contain the display name', () => {
expect(state).toHaveProperty('displayName', 'John Doe'); expect(state).toHaveProperty('displayName', 'John Doe');
}); });
......
...@@ -142,19 +142,11 @@ describe('members saga', () => { ...@@ -142,19 +142,11 @@ describe('members saga', () => {
it('should load more members', () => expectSaga(membersSaga) it('should load more members', () => expectSaga(membersSaga)
.provide([ .provide([
[select(tokenSelector), 'token'], [select(tokenSelector), 'token'],
[matchers.call.like({ fn: apiRequest, args: ['moreUrl'] }), { results: [{ pk: 1 }], next: 'moreUrl2' }],
]) ])
.dispatch(memberActions.more('moreUrl')) .dispatch(memberActions.more('moreUrl'))
.silentRun() .put(memberActions.moreSuccess([{ pk: 1 }], 'moreUrl2'))
.then(() => { .silentRun());
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', () => { it('should put the result data when the request succeeds', () => {
const response = { const response = {
......
...@@ -7,6 +7,12 @@ import { apiRequest } from '../../app/utils/url'; ...@@ -7,6 +7,12 @@ import { apiRequest } from '../../app/utils/url';
import * as profileActions from '../../app/actions/profile'; import * as profileActions from '../../app/actions/profile';
import { tokenSelector } from '../../app/selectors/session'; import { tokenSelector } from '../../app/selectors/session';
jest.mock('react-native-snackbar', () => ({
LENGTH_LONG: 100,
show: jest.fn(),
dismiss: jest.fn(),
}));
jest.mock('../../app/utils/url', () => ({ jest.mock('../../app/utils/url', () => ({
apiRequest: jest.fn(() => {}), apiRequest: jest.fn(() => {}),
})); }));
...@@ -57,4 +63,4 @@ describe('profile saga', () => { ...@@ -57,4 +63,4 @@ describe('profile saga', () => {
method: 'GET', method: 'GET',
}); });
})); }));
}); });
\ No newline at end of file
...@@ -119,7 +119,7 @@ describe('session saga', () => { ...@@ -119,7 +119,7 @@ describe('session saga', () => {
.dispatch(sessionActions.signOut()) .dispatch(sessionActions.signOut())
.silentRun() .silentRun()
.then(() => { .then(() => {
expect(AsyncStorage.clear).toBeCalled(); expect(AsyncStorage.multiRemove).toBeCalled();
})); }));
it('should put a push notification invalidation action', () => expectSaga(sessionSaga) it('should put a push notification invalidation action', () => expectSaga(sessionSaga)
......
import DeviceInfo from 'react-native-device-info';
import { import {
apiRequest, apiRequest,
apiUrl, apiUrl,
...@@ -7,20 +8,17 @@ import { ...@@ -7,20 +8,17 @@ import {
TokenInvalidError, TokenInvalidError,
} from '../../app/utils/url'; } from '../../app/utils/url';
import DeviceInfo from 'react-native-device-info';
const fetchPromiseResult = { const fetchPromiseResult = {
status: 200, status: 200,
json: () => Promise.resolve('responseJson'), json: () => Promise.resolve({ exampleJson: 'val' }),
clone: global.fetch,
}; };
global.fetch = jest.fn().mockReturnValue(
Promise.resolve(fetchPromiseResult),
);
fetchPromiseResult.clone = global.fetch;
describe('url helper', () => { describe('url helper', () => {
beforeEach(() => { beforeEach(() => {
global.fetch = jest.fn().mockReturnValue(
Promise.resolve(fetchPromiseResult),
);
}); });
it('should expose the constants', () => { it('should expose the constants', () => {
...@@ -37,7 +35,7 @@ describe('url helper', () => { ...@@ -37,7 +35,7 @@ describe('url helper', () => {
.then((response) => { .then((response) => {
expect(global.fetch).toBeCalledWith(`${apiUrl}/route/`, expect(global.fetch).toBeCalledWith(`${apiUrl}/route/`,
{ headers: { 'Accept-Language': 'en' } }); { headers: { 'Accept-Language': 'en' } });
expect(response).toEqual('responseJson'); expect(response).toEqual({ exampleJson: 'val' });
}); });
}); });
...@@ -65,7 +63,7 @@ describe('url helper', () => { ...@@ -65,7 +63,7 @@ describe('url helper', () => {
.then((response) => { .then((response) => {
expect(global.fetch).toBeCalledWith(`${apiUrl}/route/`, expect(global.fetch).toBeCalledWith(`${apiUrl}/route/`,
{ headers: { 'Accept-Language': 'en' } }); { headers: { 'Accept-Language': 'en' } });
expect(response).toEqual('responseJson'); expect(response).toEqual({ exampleJson: 'val' });
}); });
}); });
...@@ -74,7 +72,6 @@ describe('url helper', () => { ...@@ -74,7 +72,6 @@ describe('url helper', () => {
const response = { const response = {
status: 404, status: 404,
json: () => Promise.resolve('responseJson'), json: () => Promise.resolve('responseJson'),
clone: () => ({ status: 404 }),
}; };
global.fetch.mockReturnValue(Promise.resolve(response)); global.fetch.mockReturnValue(Promise.resolve(response));
return apiRequest('route', {}, null) return apiRequest('route', {}, null)
...@@ -86,7 +83,6 @@ describe('url helper', () => { ...@@ -86,7 +83,6 @@ describe('url helper', () => {
const response = { const response = {
status: 204, status: 204,
json: () => Promise.resolve('responseJson'), json: () => Promise.resolve('responseJson'),
clone: () => ({ status: 204 }),
}; };
global.fetch.mockReturnValue(Promise.resolve(response)); global.fetch.mockReturnValue(Promise.resolve(response));
return apiRequest('route', {}, null) return apiRequest('route', {}, null)
...@@ -99,7 +95,6 @@ describe('url helper', () => { ...@@ -99,7 +95,6 @@ describe('url helper', () => {
status: 403, status: 403,
headers: { get: key => (key === 'content-language' ? 'en' : 'nl') }, headers: { get: key => (key === 'content-language' ? 'en' : 'nl') },
json: () => Promise.resolve({ detail: 'Invalid token.' }), json: () => Promise.resolve({ detail: 'Invalid token.' }),
clone: () => 'responseCopy',
}; };
global.fetch.mockReturnValue(Promise.resolve(response)); global.fetch.mockReturnValue(Promise.resolve(response));
return apiRequest('route', {}, null) return apiRequest('route', {}, null)
...@@ -112,7 +107,6 @@ describe('url helper', () => { ...@@ -112,7 +107,6 @@ describe('url helper', () => {
status: 403, status: 403,
headers: { get: key => (key === 'content-language' ? 'nl' : 'en') }, headers: { get: key => (key === 'content-language' ? 'nl' : 'en') },
json: () => Promise.resolve({ detail: 'Ongeldige token.' }), json: () => Promise.resolve({ detail: 'Ongeldige token.' }),
clone: () => 'responseCopy',
}; };
global.fetch.mockReturnValue(Promise.resolve(response)); global.fetch.mockReturnValue(Promise.resolve(response));
return apiRequest('route', {}, null) return apiRequest('route', {}, null)
...@@ -125,16 +119,14 @@ describe('url helper', () => { ...@@ -125,16 +119,14 @@ describe('url helper', () => {
status: 403, status: 403,
headers: { get: key => (key === 'content-language' ? 'en' : 'nl') }, headers: { get: key => (key === 'content-language' ? 'en' : 'nl') },
json: () => Promise.resolve({ detail: 'Not authorized.' }), json: () => Promise.resolve({ detail: 'Not authorized.' }),
clone: () => ({ json: () => 'jsonResult' }),
}; };
global.fetch.mockReturnValue(Promise.resolve(response)); global.fetch.mockReturnValue(Promise.resolve(response));
return apiRequest('route', {}, null) return apiRequest('route', {}, null)
.then(res => expect(res).toEqual('jsonResult')); .catch(res => expect(res).toEqual(new ServerError('Invalid status code: 403')));
}); });
it('should default to an English locales', () => { it('should default to an English locales', () => {
DeviceInfo.getDeviceLocale = () => 'fr'; DeviceInfo.getDeviceLocale = () => 'fr';
expect.assertions(1);
return apiRequest('route', {}, null) return apiRequest('route', {}, null)
.then(() => { .then(() => {
expect(global.fetch).toBeCalledWith(`${apiUrl}/route/`, expect(global.fetch).toBeCalledWith(`${apiUrl}/route/`,
......
...@@ -3,9 +3,7 @@ export const FETCHING = 'PROFILE_FETCHING'; ...@@ -3,9 +3,7 @@ export const FETCHING = 'PROFILE_FETCHING';
export const SUCCESS = 'PROFILE_SUCCESS'; export const SUCCESS = 'PROFILE_SUCCESS';
export const FAILURE = 'PROFILE_FAILURE'; export const FAILURE = 'PROFILE_FAILURE';
export const UPDATE = 'PROFILE_UPDATE'; export const UPDATE = 'PROFILE_UPDATE';
export const UPDATING = 'PROFILE_UPDATING';
export const UPDATE_SUCCESS = 'PROFILE_UPDATE_SUCCESS'; export const UPDATE_SUCCESS = 'PROFILE_UPDATE_SUCCESS';
export const UPDATE_FAIL = 'PROFILE_UPDATE_FAIL';
export const CHANGE_AVATAR = 'PROFILE_CHANGE_AVATAR'; export const CHANGE_AVATAR = 'PROFILE_CHANGE_AVATAR';
export function profile(member = 'me') { export function profile(member = 'me') {
...@@ -27,35 +25,17 @@ export function changeAvatar() { ...@@ -27,35 +25,17 @@ export function changeAvatar() {
}; };
} }
export function update() {
return {
type: UPDATE,
};
}
export function updating() {
return {
type: UPDATING,
};
}
export function updateSuccess(profileData) { export function updateSuccess(profileData) {
return { return {
type: UPDATE_SUCCESS, type: UPDATE_SUCCESS,
payload: { profileData }, payload: profileData,
};
}
export function updateFail() {
return {
type: UPDATE_FAIL,
}; };
} }
export function success(profileData) { export function success(profileData) {
return { return {
type: SUCCESS, type: SUCCESS,
payload: { profileData }, payload: profileData,
}; };
} }
......
...@@ -23,11 +23,10 @@ const initialState = { ...@@ -23,11 +23,10 @@ const initialState = {
}, },
success: false, success: false,
hasLoaded: false, hasLoaded: false,
updating: false,
}; };
export default function profile(state = initialState, action = {}) { export default function profile(state = initialState, { type, payload } = {}) {
switch (action.type) { switch (type) {
case profileActions.FETCHING: case profileActions.FETCHING:
return { return {
...state, ...state,
...@@ -36,7 +35,7 @@ export default function profile(state = initialState, action = {}) { ...@@ -36,7 +35,7 @@ export default function profile(state = initialState, action = {}) {
case profileActions.SUCCESS: case profileActions.SUCCESS:
return { return {
...state, ...state,
profile: action.payload.profileData, profile: payload,
success: true, success: true,
hasLoaded: true, hasLoaded: true,
}; };
...@@ -46,16 +45,13 @@ export default function profile(state = initialState, action = {}) { ...@@ -46,16 +45,13 @@ export default function profile(state = initialState, action = {}) {
success: false, success: false,
hasLoaded: true, hasLoaded: true,
}; };
case profileActions.UPDATING:
return {
...state,
updating: true,
};
case profileActions.UPDATE_FAIL:
case profileActions.UPDATE_SUCCESS: case profileActions.UPDATE_SUCCESS:
return { return {
...state, ...state,
updating: false, profile: {
...state.profile,
...payload,
},
}; };
default: default:
return state; return state;
......
import { import {
call, put, select, takeEvery, call, put, select, takeEvery,
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import { Sentry } from 'react-native-sentry';
import { apiRequest } from '../utils/url'; import { apiRequest } from '../utils/url';
import * as calendarActions from '../actions/calendar'; import * as calendarActions from '../actions/calendar';
import { tokenSelector } from '../selectors/session'; import { tokenSelector } from '../selectors/session';
import reportError from '../utils/errorReporting';
const calendar = function* calendar() { const calendar = function* calendar() {
const token = yield select(tokenSelector); const token = yield select(tokenSelector);
...@@ -31,17 +31,15 @@ const calendar = function* calendar() { ...@@ -31,17 +31,15 @@ const calendar = function* calendar() {
partner: true, partner: true,
})); }));
} catch (error) { } catch (error) {
Sentry.captureException(error); yield call(reportError, error);
} }
yield put(calendarActions.success(events.concat(partnerEvents))); yield put(calendarActions.success(events.concat(partnerEvents)));
} catch (error) { } catch (error) {
Sentry.captureException(error); yield call(reportError, error);
yield put(calendarActions.failure()); yield put(calendarActions.failure());
} }
}; };
const calendarSaga = function* eventSaga() { export default function* () {
yield takeEvery(calendarActions.REFRESH, calendar); yield takeEvery(calendarActions.REFRESH, calendar);
}; }
export default calendarSaga;
...@@ -81,8 +81,6 @@ const deepLink = function* deepLink(action) { ...@@ -81,8 +81,6 @@ const deepLink = function* deepLink(action) {
} }
}; };
const deepLinkingSaga = function* deepLinkingSaga() { export default function* () {
yield takeEvery(deepLinkingActions.DEEPLINK, deepLink); yield takeEvery(deepLinkingActions.DEEPLINK, deepLink);
}; }
export default deepLinkingSaga;
import { import {
call, put, select, takeEvery, call, put, select, takeEvery,
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import { Sentry } from 'react-native-sentry';
import { apiRequest } from '../utils/url'; import { apiRequest } from '../utils/url';
import * as eventActions from '../actions/event'; import * as eventActions from '../actions/event';
import { tokenSelector } from '../selectors/session'; import { tokenSelector } from '../selectors/session';
import reportError from '../utils/errorReporting';
function* event(action) { function* event(action) {
const { pk, navigateToEventScreen } = action.payload; const { pk, navigateToEventScreen } = action.payload;
...@@ -40,7 +40,7 @@ function* event(action) { ...@@ -40,7 +40,7 @@ function* event(action) {
eventRegistrations, eventRegistrations,
)); ));
} catch (error) { } catch (error) {
Sentry.captureException(error); yield call(reportError, error);
yield put(eventActions.failure()); yield put(eventActions.failure());
} }
} }
...@@ -69,14 +69,12 @@ function* updateRegistration(action) { ...@@ -69,14 +69,12 @@ function* updateRegistration(action) {
yield put(eventActions.done()); yield put(eventActions.done());
} catch (error) { } catch (error) {
Sentry.captureException(error); yield call(reportError, error);
yield put(eventActions.failure()); yield put(eventActions.failure());
} }
} }
function* eventSaga() { export default function* () {
yield takeEvery(eventActions.EVENT, event); yield takeEvery(eventActions.EVENT, event);
yield takeEvery(eventActions.UPDATE_REGISTRATION, updateRegistration); yield takeEvery(eventActions.UPDATE_REGISTRATION, updateRegistration);
} }
export default eventSaga;
...@@ -13,7 +13,7 @@ import deepLinkingSaga from './deepLinking'; ...@@ -13,7 +13,7 @@ import deepLinkingSaga from './deepLinking';
import membersSaga from './members'; import membersSaga from './members';
import settingsSaga from './settings'; import settingsSaga from './settings';
const sagas = function* sagas() { export default function* () {
yield all([ yield all([
fork(sessionSaga), fork(sessionSaga),
fork(navigationSaga), fork(navigationSaga),
...@@ -28,6 +28,4 @@ const sagas = function* sagas() { ...@@ -28,6 +28,4 @@ const sagas = function* sagas() {
fork(membersSaga), fork(membersSaga),
fork(settingsSaga), fork(settingsSaga),
]); ]);
}; }
export default sagas;
...@@ -2,7 +2,6 @@ import { Dimensions } from 'react-native'; ...@@ -2,7 +2,6 @@ import { Dimensions } from 'react-native';
import { import {
call, put, select, takeEvery, call, put, select, takeEvery,
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import { Sentry } from 'react-native-sentry';
import { TOTAL_BAR_HEIGHT } from '../ui/components/standardHeader/style/StandardHeader'; import { TOTAL_BAR_HEIGHT } from '../ui/components/standardHeader/style/StandardHeader';
import { memberSize } from '../ui/screens/memberList/style/MemberList'; import { memberSize } from '../ui/screens/memberList/style/MemberList';
...@@ -10,6 +9,7 @@ import { memberSize } from '../ui/screens/memberList/style/MemberList'; ...@@ -10,6 +9,7 @@ import { memberSize } from '../ui/screens/memberList/style/MemberList';
import { apiRequest } from '../utils/url';