From 99bfa372c4ffbe18b85ed5682726c855066b5937 Mon Sep 17 00:00:00 2001 From: wulanmantiri Date: Mon, 26 Apr 2021 18:48:06 +0800 Subject: [PATCH 1/6] [RED] Add tests for dietela cover loader --- src/components/core/Loader/index.test.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/core/Loader/index.test.tsx b/src/components/core/Loader/index.test.tsx index a7568fd..2e74ac8 100644 --- a/src/components/core/Loader/index.test.tsx +++ b/src/components/core/Loader/index.test.tsx @@ -1,10 +1,16 @@ import React from 'react'; import { render } from '@testing-library/react-native'; -import Loader from '.'; +import Loader, { DietelaCoverLoader } from '.'; describe('Loader component', () => { it('renders correctly', () => { render(); }); }); + +describe('DietelaCoverLoader component', () => { + it('renders correctly', () => { + render(); + }); +}); -- GitLab From f3b769b21bc082d74bdf62043d3ce9a4bdf9f9d8 Mon Sep 17 00:00:00 2001 From: wulanmantiri Date: Mon, 26 Apr 2021 18:56:16 +0800 Subject: [PATCH 2/6] [GREEN] Implement dietela cover loader --- src/components/core/Loader/index.tsx | 26 ++++++++++++++++++++++++++ src/components/core/index.ts | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/components/core/Loader/index.tsx b/src/components/core/Loader/index.tsx index b6daf4b..b53fd37 100644 --- a/src/components/core/Loader/index.tsx +++ b/src/components/core/Loader/index.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { ActivityIndicator, StyleSheet, View } from 'react-native'; import { colors } from 'styles'; +import { dietelaLogo } from 'assets/images'; +import { Image } from 'react-native-elements'; const Loader = () => ( @@ -8,11 +10,35 @@ const Loader = () => ( ); +const DietelaCoverLoader = () => ( + + + +); + const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', }, + dietelaCover: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: colors.primaryYellow, + }, + logo: { + width: 200, + height: 100, + }, + img: { + backgroundColor: colors.primaryYellow, + }, }); export default Loader; +export { DietelaCoverLoader }; diff --git a/src/components/core/index.ts b/src/components/core/index.ts index 0c60fed..a587993 100644 --- a/src/components/core/index.ts +++ b/src/components/core/index.ts @@ -3,7 +3,7 @@ export { default as BigButton } from './BigButton'; export { default as CarouselPagination } from './CarouselPagination'; export { default as InfoCard } from './InfoCard'; export { default as Link } from './Link'; -export { default as Loader } from './Loader'; +export { default as Loader, DietelaCoverLoader } from './Loader'; export { default as ResultCard } from './ResultCard'; export { default as Statistic } from './Statistic'; export { default as Toast } from 'react-native-toast-message'; -- GitLab From 32e85d9b9934b661101de9d2d950fdf47101ab96 Mon Sep 17 00:00:00 2001 From: wulanmantiri Date: Mon, 26 Apr 2021 19:22:21 +0800 Subject: [PATCH 3/6] [REFACTOR] Remove unnecessary code and change page transition animation --- src/app/index.test.tsx | 6 +----- src/app/styles.tsx | 18 +++++++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/index.test.tsx b/src/app/index.test.tsx index 887ae9c..272166d 100644 --- a/src/app/index.test.tsx +++ b/src/app/index.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { render } from '@testing-library/react-native'; -import { HeaderLeft, ErrorToast } from './styles'; +import { ErrorToast } from './styles'; import App from '.'; describe('Application', () => { @@ -9,10 +9,6 @@ describe('Application', () => { render(); }); - test('header left button renders correctly', () => { - render(); - }); - test('error toast renders correctly', () => { render(); }); diff --git a/src/app/styles.tsx b/src/app/styles.tsx index 6aec16d..e05042c 100644 --- a/src/app/styles.tsx +++ b/src/app/styles.tsx @@ -1,17 +1,12 @@ import React from 'react'; import { StyleSheet } from 'react-native'; import { - HeaderBackButton, - StackHeaderLeftButtonProps, StackNavigationOptions, + TransitionSpecs, } from '@react-navigation/stack'; import { BaseToast, BaseToastProps } from 'react-native-toast-message'; import { colors, typographyStyles, typography } from 'styles'; -export const HeaderLeft = (props: StackHeaderLeftButtonProps) => ( - -); - export const screenOptions: StackNavigationOptions = { cardStyle: { backgroundColor: '#fff', @@ -24,7 +19,16 @@ export const screenOptions: StackNavigationOptions = { headerTintColor: colors.primary, headerTitleStyle: typographyStyles.headingMedium, headerTitleAlign: 'center', - headerLeft: HeaderLeft, + transitionSpec: { + open: { + animation: 'timing', + config: { + duration: 1, + delay: 0, + }, + }, + close: TransitionSpecs.TransitionIOSSpec, + }, }; const styles = StyleSheet.create({ -- GitLab From 2385842118168ac90e84ffe6dee0d1d509242a50 Mon Sep 17 00:00:00 2001 From: wulanmantiri Date: Mon, 26 Apr 2021 19:25:55 +0800 Subject: [PATCH 4/6] [GREEN] Fetch user API on first open, link user data on create account --- src/provider/UserContext/index.ts | 119 +++++++++++++++++++++--------- src/provider/UserContext/types.ts | 10 ++- src/provider/index.test.tsx | 4 +- src/services/auth/index.ts | 13 ++++ src/services/auth/models.ts | 32 ++++++-- src/services/auth/urls.ts | 3 + 6 files changed, 138 insertions(+), 43 deletions(-) diff --git a/src/provider/UserContext/index.ts b/src/provider/UserContext/index.ts index bf07d3c..ecf0a4d 100644 --- a/src/provider/UserContext/index.ts +++ b/src/provider/UserContext/index.ts @@ -4,12 +4,20 @@ import { GoogleSignin } from '@react-native-google-signin/google-signin'; import { Toast } from 'components/core'; import CACHE_KEYS from 'constants/cacheKeys'; import { removeCache, getCache, setCache } from 'utils/cache'; -import { googleLoginApi, loginApi, signupApi } from 'services/auth'; + +import { + googleLoginApi, + loginApi, + signupApi, + retrieveUserApi, + linkUserDataApi, +} from 'services/auth'; import { User, RegistrationRequest, LoginRequest, LoginResponse, + UserRole, } from 'services/auth/models'; import { set401Callback, setAuthHeader, resetAuthHeader } from 'services/api'; @@ -19,25 +27,18 @@ const initialUser = { id: null, email: '', name: '', -}; - -const setUserFromResponse = async ( - success: boolean, - setUser: React.Dispatch>, - data?: LoginResponse, -) => { - if (success && data) { - await setCache(CACHE_KEYS.authToken, data.access_token); - await setCache(CACHE_KEYS.refreshToken, data.refresh_token); - - setUser(data.user); - } + role: null, }; export const UserContext = createContext({ user: initialUser, isAuthenticated: false, + isUnpaidClient: false, + isPaidClient: false, + isNutritionist: false, + isAdmin: false, isLoading: false, + isFirstLoading: false, signup: () => Promise.reject(), login: () => Promise.reject(), loginWithGoogle: () => Promise.reject(), @@ -47,36 +48,79 @@ export const UserContext = createContext({ export const useUserContext = (): iUserContext => { const [user, setUser] = useState(initialUser); const [isLoading, setIsLoading] = useState(false); + const [isFirstLoading, setIsFirstLoading] = useState(false); + const [clientHasPaid] = useState(false); + + const logout = useCallback(async () => { + await GoogleSignin.signOut(); + await removeCache(CACHE_KEYS.authToken); + await removeCache(CACHE_KEYS.refreshToken); + setUser(initialUser); + resetAuthHeader(); + }, []); const getUser = useCallback(async () => { + setIsFirstLoading(true); const token = await getCache(CACHE_KEYS.authToken); if (token) { setAuthHeader(token); - // TODO: fetch user data + const response = await retrieveUserApi(); + if (response.success && response.data) { + setUser(response.data); + } else { + await logout(); + Toast.show({ + type: 'error', + text1: 'Sesi Anda sudah berakhir.', + text2: 'Silakan coba masuk lagi.', + }); + } } - }, []); + setIsFirstLoading(false); + }, [logout]); + + const authSuccess = async (data: LoginResponse) => { + const accessToken = data.access_token; + await setCache(CACHE_KEYS.authToken, accessToken); + await setCache(CACHE_KEYS.refreshToken, data.refresh_token); + setUser(data.user); + setAuthHeader(accessToken); + }; + + const linkUserData = async (email: string) => { + const dietProfileId = await getCache(CACHE_KEYS.dietProfileId); + const cartId = await getCache(CACHE_KEYS.cartId); + if (dietProfileId && cartId) { + const response = await linkUserDataApi({ + email, + diet_profile_id: parseInt(dietProfileId, 10), + cart_id: parseInt(cartId, 10), + }); + return response; + } + return { + success: false, + }; + }; const signup = async (registerData: RegistrationRequest) => { const response = await signupApi(registerData); - await setUserFromResponse(response.success, setUser, response.data); + if (response.success && response.data) { + await authSuccess(response.data); + return await linkUserData(response.data.user.email); + } return response; }; const login = async (loginData: LoginRequest) => { const response = await loginApi(loginData); - await setUserFromResponse(response.success, setUser, response.data); + if (response.success && response.data) { + await authSuccess(response.data); + } return response; }; - const logout = useCallback(async () => { - await GoogleSignin.signOut(); - await removeCache(CACHE_KEYS.authToken); - setUser(initialUser); - resetAuthHeader(); - }, []); - const loginWithGoogle = async () => { - setIsLoading(true); try { await GoogleSignin.hasPlayServices(); await GoogleSignin.signIn(); @@ -85,16 +129,18 @@ export const useUserContext = (): iUserContext => { access_token: tokens.accessToken, }); if (response.success && response.data) { - await setCache(CACHE_KEYS.authToken, response.data.access_token); - await setCache(CACHE_KEYS.authToken, response.data.access_token); - setUser(response.data.user); + await authSuccess(response.data); + const linkResponse = await linkUserData(response.data.user.email); + if (!linkResponse.success) { + await logout(); + Toast.show({ + type: 'error', + text1: 'Gagal masuk dengan Google', + text2: 'Terjadi kesalahan di sisi kami. Silakan coba lagi', + }); + } } else { await logout(); - Toast.show({ - type: 'error', - text1: 'Sesi Anda sudah berakhir.', - text2: 'Silakan coba masuk lagi.', - }); } } catch (error) { console.log(error); @@ -116,7 +162,12 @@ export const useUserContext = (): iUserContext => { return { user, isAuthenticated: user.id !== null, + isUnpaidClient: user.role === UserRole.CLIENT, + isPaidClient: user.role === UserRole.CLIENT && clientHasPaid, + isNutritionist: user.role === UserRole.NUTRITIONIST, + isAdmin: user.role === UserRole.ADMIN, isLoading, + isFirstLoading, signup, login, loginWithGoogle, diff --git a/src/provider/UserContext/types.ts b/src/provider/UserContext/types.ts index 64db05c..f0008b7 100644 --- a/src/provider/UserContext/types.ts +++ b/src/provider/UserContext/types.ts @@ -4,13 +4,21 @@ import { LoginResponse, RegistrationRequest, User, + LinkUserDataResponse, } from 'services/auth/models'; export interface iUserContext { user: User; isAuthenticated: boolean; + isUnpaidClient: boolean; + isPaidClient: boolean; + isNutritionist: boolean; + isAdmin: boolean; isLoading: boolean; - signup: (data: RegistrationRequest) => ApiResponse; + isFirstLoading: boolean; + signup: ( + data: RegistrationRequest, + ) => ApiResponse; login: (data: LoginRequest) => ApiResponse; loginWithGoogle: () => Promise; logout: () => Promise; diff --git a/src/provider/index.test.tsx b/src/provider/index.test.tsx index fb235ef..11e2ea0 100644 --- a/src/provider/index.test.tsx +++ b/src/provider/index.test.tsx @@ -4,7 +4,7 @@ import { render } from '@testing-library/react-native'; import Provider from '.'; describe('Provider', () => { - it('renders correctly', () => { - render(); + it('renders correctly', async () => { + render(children); }); }); diff --git a/src/services/auth/index.ts b/src/services/auth/index.ts index ff598c3..e212d20 100644 --- a/src/services/auth/index.ts +++ b/src/services/auth/index.ts @@ -6,6 +6,9 @@ import { LoginRequest, LoginResponse, RegistrationRequest, + User, + LinkUserDataRequest, + LinkUserDataResponse, } from './models'; export const googleLoginApi = ( @@ -23,3 +26,13 @@ export const signupApi = ( export const loginApi = (body: LoginRequest): ApiResponse => { return api(RequestMethod.POST, apiUrls.login, body); }; + +export const retrieveUserApi = (): ApiResponse => { + return api(RequestMethod.GET, apiUrls.user); +}; + +export const linkUserDataApi = ( + body: LinkUserDataRequest, +): ApiResponse => { + return api(RequestMethod.POST, apiUrls.linkData, body); +}; diff --git a/src/services/auth/models.ts b/src/services/auth/models.ts index 6c24a9a..7b1edd7 100644 --- a/src/services/auth/models.ts +++ b/src/services/auth/models.ts @@ -1,3 +1,6 @@ +import { DietProfileResponse } from 'services/dietelaQuiz/models'; +import { CartResponse } from 'services/payment/models'; + export interface GoogleLoginRequest { access_token: string; } @@ -9,18 +12,23 @@ export interface RegistrationRequest { password2: string; } -export type Role = 'client' | 'nutritionist' | 'admin'; - -export interface LoginRequest { - email: string; - password: string; - role: Role; +export enum UserRole { + CLIENT = 'client', + NUTRITIONIST = 'nutritionist', + ADMIN = 'admin', } export interface User { id: number | null; email: string; name: string; + role: UserRole | null; +} + +export interface LoginRequest { + email: string; + password: string; + role: UserRole; } export interface LoginResponse { @@ -28,3 +36,15 @@ export interface LoginResponse { refresh_token: string; user: User; } + +export interface LinkUserDataRequest { + email: string; + diet_profile_id: number; + cart_id: number; +} + +export interface LinkUserDataResponse { + user: User; + diet_profile: DietProfileResponse; + cart: CartResponse; +} diff --git a/src/services/auth/urls.ts b/src/services/auth/urls.ts index 039677e..31f21fa 100644 --- a/src/services/auth/urls.ts +++ b/src/services/auth/urls.ts @@ -3,3 +3,6 @@ const auth = 'auth/'; export const google = `${auth}google/`; export const signup = `${auth}registration/`; export const login = `${auth}user-login/`; + +export const user = `${auth}user/`; +export const linkData = `${auth}link-data/`; -- GitLab From 556e48609370b5b1fca00261ef28c5e9f2fe70e4 Mon Sep 17 00:00:00 2001 From: wulanmantiri Date: Mon, 26 Apr 2021 19:28:16 +0800 Subject: [PATCH 5/6] [GREEN] Apply access management and handle loading state on page transition --- src/hooks/useAuthEffect/index.ts | 54 ++++++++++-- src/hooks/useAuthGuardEffect/index.ts | 6 +- src/scenes/auth/Login/index.test.tsx | 32 +++++-- src/scenes/auth/Login/index.tsx | 7 +- .../ManualRegistrationPage/index.test.tsx | 13 ++- src/scenes/cart/Checkout/index.tsx | 2 +- src/scenes/cart/ChoosePlan/index.tsx | 4 +- src/scenes/common/InitialPage/index.test.tsx | 50 +++++------ src/scenes/common/InitialPage/index.tsx | 83 +++++++++++-------- 9 files changed, 158 insertions(+), 93 deletions(-) diff --git a/src/hooks/useAuthEffect/index.ts b/src/hooks/useAuthEffect/index.ts index 473ea2b..413dc74 100644 --- a/src/hooks/useAuthEffect/index.ts +++ b/src/hooks/useAuthEffect/index.ts @@ -1,21 +1,61 @@ -import { useContext, useEffect } from 'react'; +import { useContext, useEffect, useCallback, useState } from 'react'; import { useNavigation } from '@react-navigation/native'; import { UserContext } from 'provider'; import * as ROUTES from 'constants/routes'; +import CACHE_KEYS from 'constants/cacheKeys'; +import { getCache } from 'utils/cache'; -const useAuthEffect = () => { - const { isAuthenticated } = useContext(UserContext); +const useAuthEffect = (isLogin?: boolean) => { + const { isAuthenticated, isUnpaidClient, isFirstLoading } = useContext( + UserContext, + ); const navigation = useNavigation(); + const [isLoading, setIsLoading] = useState(false); + + const checkCart = useCallback(async () => { + setIsLoading(true); + const dietProfileId = await getCache(CACHE_KEYS.dietProfileId); + const cartId = await getCache(CACHE_KEYS.cartId); + if (!dietProfileId) { + navigation.reset({ + index: 0, + routes: [ + { name: ROUTES.initial }, + { name: ROUTES.allAccessQuestionnaire }, + ], + }); + } else if (!cartId) { + navigation.reset({ + index: 0, + routes: [{ name: ROUTES.initial }, { name: ROUTES.choosePlan }], + }); + } + setIsLoading(false); + }, [navigation]); useEffect(() => { if (isAuthenticated) { - if (navigation.canGoBack()) { - navigation.goBack(); + if (isUnpaidClient) { + navigation.reset({ + index: 0, + routes: [{ name: ROUTES.checkout }], + }); } else { - navigation.navigate(ROUTES.profile); + navigation.reset({ + index: 0, + routes: [{ name: ROUTES.profile }], + }); } + } else if (isLogin) { + checkCart(); } - }, [isAuthenticated, navigation]); + + return () => { + setIsLoading(false); + }; + }, [checkCart, isLogin, isAuthenticated, isUnpaidClient, navigation]); + + return isFirstLoading || isLoading; }; export default useAuthEffect; diff --git a/src/hooks/useAuthGuardEffect/index.ts b/src/hooks/useAuthGuardEffect/index.ts index 4cf67fe..f5c828b 100644 --- a/src/hooks/useAuthGuardEffect/index.ts +++ b/src/hooks/useAuthGuardEffect/index.ts @@ -3,15 +3,15 @@ import { useNavigation } from '@react-navigation/native'; import { UserContext } from 'provider'; import * as ROUTES from 'constants/routes'; -const useAuthGuardEffect = () => { +const useAuthGuardEffect = (signupFallback?: boolean) => { const { isAuthenticated } = useContext(UserContext); const navigation = useNavigation(); useEffect(() => { if (!isAuthenticated) { - navigation.navigate(ROUTES.login); + navigation.navigate(signupFallback ? ROUTES.registration : ROUTES.login); } - }, [isAuthenticated, navigation]); + }, [isAuthenticated, signupFallback, navigation]); }; export default useAuthGuardEffect; diff --git a/src/scenes/auth/Login/index.test.tsx b/src/scenes/auth/Login/index.test.tsx index be8d4e0..8c0ff33 100644 --- a/src/scenes/auth/Login/index.test.tsx +++ b/src/scenes/auth/Login/index.test.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { render, fireEvent, waitFor } from 'utils/testing'; import * as ROUTES from 'constants/routes'; import axios from 'axios'; +import CACHE_KEYS from 'constants/cacheKeys'; +import { setCache } from 'utils/cache'; import Login from '.'; import { @@ -16,8 +18,23 @@ jest.mock('axios'); const mockAxios = axios as jest.Mocked; describe('Login page', () => { - it('renders correctly', () => { - render(, ROUTES.login); + it('shows dietela cover loader when is loading', async () => { + const { queryByText } = render(, ROUTES.login); + + await waitFor(() => + expect(queryByText(/Lanjut dengan Google/i)).toBeFalsy(), + ); + }); + + it('renders correctly if client has filled questionnaire and cart', async () => { + await setCache(CACHE_KEYS.cartId, 1); + await setCache(CACHE_KEYS.dietProfileId, 1); + + const { queryByText } = render(, ROUTES.login); + + await waitFor(() => + expect(queryByText(/Lanjut dengan Google/i)).toBeTruthy(), + ); }); it('success when field is valid and submit success', async () => { @@ -33,6 +50,10 @@ describe('Login page', () => { ROUTES.login, ); + await waitFor(() => + expect(queryByText(/Lanjut dengan Google/i)).toBeTruthy(), + ); + textField.map(({ name, placeholder }) => { const formField = getByPlaceholderText(placeholder as string); fireEvent.changeText(formField, validLoginValues[name]); @@ -40,9 +61,6 @@ describe('Login page', () => { const loginButton = getByTestId('loginButton'); await waitFor(() => fireEvent.press(loginButton)); - - const toastWarning = queryByText(/Profile/i); - expect(toastWarning).toBeTruthy(); }); it('fails when field is invalid and submit success', async () => { @@ -60,6 +78,10 @@ describe('Login page', () => { ROUTES.login, ); + await waitFor(() => + expect(queryByText(/Lanjut dengan Google/i)).toBeTruthy(), + ); + textField.map(({ name, placeholder }) => { const formField = getByPlaceholderText(placeholder as string); fireEvent.changeText(formField, invalidLoginValues[name]); diff --git a/src/scenes/auth/Login/index.tsx b/src/scenes/auth/Login/index.tsx index d603d56..f28f7d3 100644 --- a/src/scenes/auth/Login/index.tsx +++ b/src/scenes/auth/Login/index.tsx @@ -4,7 +4,7 @@ import { UserContext } from 'provider'; import { useAuthEffect, useForm } from 'hooks'; import { GoogleLoginButton } from '../components'; -import { BigButton, Toast } from 'components/core'; +import { BigButton, Toast, Loader } from 'components/core'; import { fieldValidation, initialValues, textField } from './schema'; import { generateValidationSchema } from 'utils/form'; @@ -45,8 +45,11 @@ const Login: FC = () => { }, }); - useAuthEffect(); + const isProcessing = useAuthEffect(true); + if (isProcessing) { + return ; + } return ( {textField.map(({ name, label, required, placeholder }, i) => ( diff --git a/src/scenes/auth/ManualRegistrationPage/index.test.tsx b/src/scenes/auth/ManualRegistrationPage/index.test.tsx index 1eee477..05ef33f 100644 --- a/src/scenes/auth/ManualRegistrationPage/index.test.tsx +++ b/src/scenes/auth/ManualRegistrationPage/index.test.tsx @@ -28,7 +28,7 @@ describe('ManualRegistrationPage', () => { }); mockAxios.request.mockImplementationOnce(signupApi); - const { getByPlaceholderText, queryByText, getByTestId } = render( + const { getByPlaceholderText, getByTestId } = render( , ROUTES.registration, ); @@ -40,9 +40,6 @@ describe('ManualRegistrationPage', () => { const submitButton = getByTestId('submitButton'); await waitFor(() => fireEvent.press(submitButton)); - - const toastWarning = queryByText(/Profile/i); - expect(toastWarning).toBeTruthy(); }); it('fails when field is valid and submit fails', async () => { @@ -50,7 +47,7 @@ describe('ManualRegistrationPage', () => { Promise.reject({ status: 400, response: { - error: 'error', + data: 'error', }, }); mockAxios.request.mockImplementationOnce(signupApi); @@ -68,7 +65,7 @@ describe('ManualRegistrationPage', () => { const submitButton = getByTestId('submitButton'); await waitFor(() => fireEvent.press(submitButton)); - const nextPageText = queryByText(/Profile/i); + const nextPageText = queryByText(/Checkout/i); expect(nextPageText).toBeFalsy(); }); @@ -79,7 +76,7 @@ describe('ManualRegistrationPage', () => { Promise.reject({ status: 400, response: { - error: { + data: { name: 'Wrong name', email: alreadyRegistered, password1: 'Wrong password', @@ -102,7 +99,7 @@ describe('ManualRegistrationPage', () => { const submitButton = getByTestId('submitButton'); await waitFor(() => fireEvent.press(submitButton)); - const nextPageText = queryByText(/Profile/i); + const nextPageText = queryByText(/Checkout/i); expect(nextPageText).toBeFalsy(); }); }); diff --git a/src/scenes/cart/Checkout/index.tsx b/src/scenes/cart/Checkout/index.tsx index bca12b8..9fceb15 100644 --- a/src/scenes/cart/Checkout/index.tsx +++ b/src/scenes/cart/Checkout/index.tsx @@ -26,7 +26,7 @@ const Checkout: FC = () => { const { isLoading, data } = useApi(fetchCart); - useAuthGuardEffect(); + useAuthGuardEffect(true); if (isLoading) { return ; diff --git a/src/scenes/cart/ChoosePlan/index.tsx b/src/scenes/cart/ChoosePlan/index.tsx index 8807a45..2c7da9f 100644 --- a/src/scenes/cart/ChoosePlan/index.tsx +++ b/src/scenes/cart/ChoosePlan/index.tsx @@ -25,7 +25,7 @@ const ChoosePlan: FC = () => { const [currentPage, setCurrentPage] = useState(1); const [isSubmitting, setIsSubmitting] = useState(false); const [values, setValues] = useState(initialValues); - const [programs, setPrograms] = useState(defaultProgramRecommendations); + const [programs, setPrograms] = useState(undefined); const handleSubmit = async () => { setIsSubmitting(true); @@ -73,7 +73,7 @@ const ChoosePlan: FC = () => { retrieveNutritionistsApi, ); - if (isLoading) { + if (isLoading || programs === undefined) { return ; } return ( diff --git a/src/scenes/common/InitialPage/index.test.tsx b/src/scenes/common/InitialPage/index.test.tsx index 952fc95..9d43f65 100644 --- a/src/scenes/common/InitialPage/index.test.tsx +++ b/src/scenes/common/InitialPage/index.test.tsx @@ -1,54 +1,46 @@ import React from 'react'; -import { fireEvent, render } from '@testing-library/react-native'; +import { render, fireEvent, waitFor } from 'utils/testing'; import * as ROUTES from 'constants/routes'; import InitialPage from '.'; -const createTestProps = (props: Object) => ({ - navigation: { - navigate: jest.fn(), - }, - ...props, -}); - describe('InitialPage', () => { - it('renders correctly', () => { - render(); + test('shows dietela cover loader when is loading', async () => { + const { queryByTestId } = render(, ROUTES.initial, { + isFirstLoading: true, + }); + + expect(queryByTestId('background')).toBeFalsy(); }); test('has background image', () => { - const { queryByTestId } = render(); + const { queryByTestId } = render(, ROUTES.initial); expect(queryByTestId('background')).toBeTruthy(); }); test('has Dietela logo', () => { - const { queryByTestId } = render(); + const { queryByTestId } = render(, ROUTES.initial); expect(queryByTestId('logo')).toBeTruthy(); }); test('has call-to-action button that navigates to Dietela Quiz', () => { - let props = createTestProps({}); - const { getByText } = render(); - - const button = getByText(/konsultasi sekarang/i); - fireEvent.press(button); + const { getByText, queryByText } = render(, ROUTES.initial); + expect(queryByText(/konsultasi sekarang/i)).toBeTruthy(); + fireEvent.press(getByText(/konsultasi sekarang/i)); - expect(button).toBeTruthy(); - expect(props.navigation.navigate).toHaveBeenCalledWith( - ROUTES.allAccessQuestionnaire, - ); + expect(queryByText(/Dietela Quiz/i)).toBeTruthy(); }); - test('has link button that navigates to Login Page', () => { - let props = createTestProps({}); - const { getByText } = render(); - - const link = getByText(/Login disini/i); - fireEvent.press(link); + test('has link button that navigates to Login Page', async () => { + const { getByText, queryByText, queryAllByText } = render( + , + ROUTES.initial, + ); + expect(queryByText(/Login disini/i)).toBeTruthy(); + await waitFor(() => fireEvent.press(getByText(/Login disini/i))); - expect(link).toBeTruthy(); - expect(props.navigation.navigate).toHaveBeenCalledWith(ROUTES.login); + expect(queryAllByText(/Login/i)).toBeTruthy(); }); }); diff --git a/src/scenes/common/InitialPage/index.tsx b/src/scenes/common/InitialPage/index.tsx index cc49653..58cad2d 100644 --- a/src/scenes/common/InitialPage/index.tsx +++ b/src/scenes/common/InitialPage/index.tsx @@ -1,46 +1,57 @@ -import React, { FC } from 'react'; - +import React, { FC, useContext } from 'react'; +import { useNavigation } from '@react-navigation/native'; import { View, Text, ImageBackground, Image } from 'react-native'; -import { BigButton, Link } from 'components/core'; +import { BigButton, Link, DietelaCoverLoader } from 'components/core'; +import { banner_girl_eating, logo_white_small } from 'assets/images'; +import { useAuthEffect } from 'hooks'; import * as ROUTES from 'constants/routes'; - +import { UserContext } from 'provider'; import { layoutStyles, typographyStyles } from 'styles'; + import { styles } from './styles'; -import { banner_girl_eating, logo_white_small } from 'assets/images'; +const InitialPage: FC = () => { + const navigation = useNavigation(); + const { isFirstLoading } = useContext(UserContext); -const InitialPage: FC = ({ navigation }) => ( - - - - - - Online Nutritionist Pertama di Indonesia - - - Hadir untuk mendefinisikan ulang kata “Diet” untuk Anda! - - - Apapun masalah diet Anda, konsultasikan bersama kami! - - - - navigation.navigate(ROUTES.allAccessQuestionnaire)} - /> - navigation.navigate(ROUTES.login)} - /> + useAuthEffect(); + + if (isFirstLoading) { + return ; + } + return ( + + + + + + Online Nutritionist Pertama di Indonesia + + + Hadir untuk mendefinisikan ulang kata “Diet” untuk Anda! + + + Apapun masalah diet Anda, konsultasikan bersama kami! + + + + navigation.navigate(ROUTES.allAccessQuestionnaire)} + /> + navigation.navigate(ROUTES.login)} + /> + - - -); + + ); +}; export default InitialPage; -- GitLab From f99026fc356c7ded938bd81e1442c3a868f5b535 Mon Sep 17 00:00:00 2001 From: wulanmantiri Date: Mon, 26 Apr 2021 19:30:05 +0800 Subject: [PATCH 6/6] [REFACTOR] Change test config for global provider --- jestSetup.js | 1 + src/__mocks__/auth.ts | 3 ++- src/utils/testing.tsx | 58 +++++++++++++++++++++++++------------------ 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/jestSetup.js b/jestSetup.js index 11d717c..8d0737a 100644 --- a/jestSetup.js +++ b/jestSetup.js @@ -4,6 +4,7 @@ import { NativeModules } from 'react-native'; jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper'); jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage); +jest.mock('axios'); NativeModules.RNGoogleSignin = { BUTTON_SIZE_ICON: 0, diff --git a/src/__mocks__/auth.ts b/src/__mocks__/auth.ts index 76f38bf..5bcb78d 100644 --- a/src/__mocks__/auth.ts +++ b/src/__mocks__/auth.ts @@ -1,4 +1,4 @@ -import { LoginResponse } from 'services/auth/models'; +import { LoginResponse, UserRole } from 'services/auth/models'; export const validRegistrationValues: { [_: string]: any } = { name: 'Doan Didinding', @@ -31,5 +31,6 @@ export const authResponse: LoginResponse = { id: 1, email: validRegistrationValues.email, name: validRegistrationValues.name, + role: UserRole.CLIENT, }, }; diff --git a/src/utils/testing.tsx b/src/utils/testing.tsx index 6d9a987..0fd9eaa 100644 --- a/src/utils/testing.tsx +++ b/src/utils/testing.tsx @@ -4,8 +4,8 @@ import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { navigation } from 'constants/navigation'; -import ContextProvider from 'provider'; -import { UserContext, useUserContext } from 'provider/UserContext'; +import { UserContext } from 'provider'; +import { UserRole } from 'services/auth/models'; const Stack = createStackNavigator(); @@ -16,34 +16,44 @@ interface TestProvider { children: ReactElement; } -const WithAuthProvider: FC = ({ children }) => { - const user = useUserContext(); - - return ( - - {children} - - ); -}; - const TestProvider: FC = ({ route, params, isAuth, children, }) => { - const Provider = isAuth ? WithAuthProvider : ContextProvider; + const user = isAuth + ? { + id: 1, + email: 'user.test@gmail.com', + name: 'User Test', + role: UserRole.CLIENT, + } + : { + id: null, + email: '', + name: '', + role: null, + }; + + const mockFn = (_?: any) => Promise.reject(); + return ( - + {navigation.map((nav, i) => ( @@ -60,7 +70,7 @@ const TestProvider: FC = ({ ))} - + ); }; -- GitLab