diff --git a/src/api.test.ts b/src/api.test.ts new file mode 100644 index 0000000..8cc0694 --- /dev/null +++ b/src/api.test.ts @@ -0,0 +1,24 @@ +import { applyMiddleware, legacy_createStore as createStore } from 'redux'; +import { thunk } from 'redux-thunk'; + +import { requestLogin, requestUserData } from '@/api'; +import rootReducer from '@/reducers'; + +const createTestStore = () => createStore(rootReducer, undefined, applyMiddleware(thunk)); + +describe('dev fake users (__DEV__ only)', () => { + it.each([ + ['nonmember', false], + ['member', true], + ])('logs in "%s" with a fake user without hitting the network', async (username, isMember) => { + const store = createTestStore(); + + await store.dispatch(requestLogin(username as string, 'any-password')); + expect(store.getState().isAuthenticated).toBe(true); + + await store.dispatch(requestUserData(store.getState().userToken ?? '')); + expect(store.getState().user?.is_member).toBe(isMember); + + expect(global.fetch).not.toHaveBeenCalled(); + }); +}); diff --git a/src/api.ts b/src/api.ts index 43ae88a..e2c89cf 100644 --- a/src/api.ts +++ b/src/api.ts @@ -14,10 +14,45 @@ import { } from './actions'; import type { UserRegisterFormValues } from './modules/register/useUserRegister'; import { AppDispatch } from './reduxStore'; +import type { UserType } from './types/data'; import { fetchWithTimeout } from './utils'; +// Dev builds only (stripped from release builds via __DEV__): log in with one of +// these usernames and any password to get a fake user, for previewing membership +// screens without a real account in each state. +const DEV_FAKE_TOKEN_PREFIX = 'dev-fake-token:'; +const DEV_FAKE_USERS: Record = { + nonmember: { + username: 'nonmember', + email: 'nonmember@example.test', + first_name: 'Ikke', + last_name: 'Medlem', + is_member: false, + is_volunteer: false, + last_membership: null, + }, + member: { + username: 'member', + email: 'member@example.test', + first_name: 'Gyldig', + last_name: 'Medlem', + is_member: true, + is_volunteer: false, + last_membership: { + membership_type: 'standard', + // Far future, so useProof's expired-membership check doesn't refetch in a loop + end_date: '2099-08-01', + }, + }, +}; + export function requestLogin(username: string, password: string) { return (dispatch: AppDispatch) => { + if (__DEV__ && username in DEV_FAKE_USERS) { + dispatch(loginSuccess(username, DEV_FAKE_TOKEN_PREFIX + username)); + return Promise.resolve(); + } + // We are now logging in dispatch(loginRequest()); @@ -53,6 +88,14 @@ export function requestLogin(username: string, password: string) { export function requestUserData(authToken: string) { return (dispatch: AppDispatch) => { + if (__DEV__ && authToken.startsWith(DEV_FAKE_TOKEN_PREFIX)) { + const fakeUser = DEV_FAKE_USERS[authToken.slice(DEV_FAKE_TOKEN_PREFIX.length)]; + if (fakeUser) { + dispatch(userDataSuccess(fakeUser)); + return Promise.resolve(); + } + } + // We are now fetching user data dispatch(userDataRequest());