Fakultas Ilmu Komputer UI

Commit 02635f38 authored by Zafir Rasyidi Taufik's avatar Zafir Rasyidi Taufik
Browse files

[CHORES]Fixed merge conflict with zafir/google-signin

parents e784aca0 6367bb4f
Pipeline #80167 passed with stage
in 42 minutes and 48 seconds
......@@ -10,6 +10,7 @@ before_script:
API_MAIN_URL=$PRODUCTION_API_MAIN_URL
fi
- echo -e "API_MAIN_URL=${API_MAIN_URL}" >> .env
- echo -e "GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}" >> .env
- export ANDROID_SDK_ROOT=/usr/lib/android-sdk
- echo "${API_MAIN_URL}"
......
import { NativeModules } from 'react-native'
NativeModules.RNGoogleSignin = {
SIGN_IN_CANCELLED: '0',
IN_PROGRESS: '1',
PLAY_SERVICES_NOT_AVAILABLE: '2',
SIGN_IN_REQUIRED: '3',
BUTTON_SIZE_ICON: 0,
BUTTON_SIZE_STANDARD: 0,
BUTTON_SIZE_WIDE: 0,
BUTTON_COLOR_AUTO: 0,
BUTTON_COLOR_LIGHT: 0,
BUTTON_COLOR_DARK: 0,
configure: jest.fn(),
}
export { NativeModules }
\ No newline at end of file
......@@ -207,6 +207,8 @@ android {
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.facebook.react:react-native:+" // From node_modules
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'com.google.android.gms:play-services-auth:19.0.0'
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
......@@ -225,3 +227,4 @@ task copyDownloadableDepsToLibs(type: Copy) {
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
apply plugin: 'com.google.gms.google-services'
......@@ -6,6 +6,7 @@ buildscript {
minSdkVersion = 21
compileSdkVersion = 28
targetSdkVersion = 28
googlePlayServicesAuthVersion = "16.0.1"
}
repositories {
google()
......@@ -13,6 +14,7 @@ buildscript {
}
dependencies {
classpath("com.android.tools.build:gradle:3.4.2")
classpath('com.google.gms:google-services:4.1.0')
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
......
......@@ -3387,6 +3387,11 @@
"resolved": "https://registry.npmjs.org/@react-native-community/masked-view/-/masked-view-0.1.7.tgz",
"integrity": "sha512-9KbP7LTLFz9dx1heURJbO6nuVMdSjDez8znlrUzaB1nUwKVsTTwlKRuHxGUYIIkReLWrJQeCv9tidy+84z2eCw=="
},
"@react-native-google-signin/google-signin": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@react-native-google-signin/google-signin/-/google-signin-6.0.0.tgz",
"integrity": "sha512-xmrrG2muhwmoPy9AdfP+ceLBZtnrXkZwgN/jR5u3xOHadyerJfvD76w2BSmm7zeoiMB1wwSkZkzFSa9+i1BdKA=="
},
"@react-native/assets": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz",
......
......@@ -13,6 +13,7 @@
"@react-native-community/async-storage": "^1.9.0",
"@react-native-community/datetimepicker": "^2.3.2",
"@react-native-community/masked-view": "^0.1.7",
"@react-native-google-signin/google-signin": "^6.0.0",
"@react-navigation/native": "^5.0.9",
"@react-navigation/stack": "^5.1.1",
"@types/crypto-js": "^3.1.44",
......@@ -66,7 +67,8 @@
".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.ts"
},
"setupFiles": [
"./node_modules/react-native-gesture-handler/jestSetup.js"
"./node_modules/react-native-gesture-handler/jestSetup.js",
"./__mocks__/react-native-modules.ts"
],
"transformIgnorePatterns": [
"node_modules/(?!react-native|react-navigation)/"
......
......@@ -29,6 +29,7 @@ import initialCacheState, {
import { useMainService } from 'services'
import { Box, Text, Button, Gap } from 'components';
import ForgetPasswordPage from 'scenes/ForgetPasswordPage';
import OfficerSignupFormGoogleSignin from 'scenes/OfficerSignupFormGoogleSignin';
const StyledApp = styled.SafeAreaView`
height: 100%;
......@@ -311,6 +312,10 @@ const App = () => {
name="officer-signup-form"
component={OfficerSignupForm}
/>
<Stack.Screen
name="officer-signup-form-google-signin"
component={OfficerSignupFormGoogleSignin}
/>
<Stack.Screen
name="officer-signup-finish"
component={OfficerSignupFormFinishPage}
......
......@@ -163,7 +163,7 @@ exports[`DatePicker tests renders correctly 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -8,6 +8,7 @@ import LoginPage from '.';
import ReactTestRenderer, { act } from 'react-test-renderer';
import { AppContext } from 'contexts';
import { GoogleSignin, GoogleSigninButton } from '@react-native-google-signin/google-signin';
const Stack = createStackNavigator();
const testProps = {
......@@ -28,9 +29,26 @@ const testProps = {
cache: {},
}
const mockNavigation = jest.fn();
const mockReset = jest.fn();
jest.mock('@react-navigation/native', () => {
return {
...jest.requireActual('@react-navigation/native'),
useNavigation: () => ({
navigate: mockNavigation,
reset: mockReset
}),
};
});
jest.doMock('@react-native-google-signin/google-signin')
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
beforeEach(() => {
jest.clearAllMocks();
});
jest.setTimeout(30000);
it('renders correctly', () => {
const instance = ReactTestRenderer.create(
<NavigationContainer>
......@@ -214,4 +232,217 @@ it('inserts invalid (length<8) password', async () => {
});
expect(testProps.alert.message).toBe("Unknown Server Error");
});
it('goes to home if google sign in token already has corresponding user', async () => {
LocalStorage.setSecretKey("sssttt");
const instance = ReactTestRenderer.create(
<AppContext.Provider value={testProps}>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
header: () => <></>,
}}>
<Stack.Screen name="login" component={LoginPage} />
</Stack.Navigator>
</NavigationContainer>
</AppContext.Provider>,
);
expect(instance.toJSON()).toMatchSnapshot();
const mockHasPlayServices = jest.fn().mockImplementationOnce(() => new Promise(resolve => {
resolve(true);
}));
const mockSignin = jest.fn().mockImplementationOnce(() => new Promise(resolve => {
const data = {
idToken: "1234",
serverAuthCode: "4321",
user: {
email: "test@gmail.com",
id: "1234",
givenName: "test",
familyName: "test",
photo: "test.com", // url
name: "test" // full name
}
}
resolve(data);
}));
GoogleSignin.hasPlayServices = mockHasPlayServices
GoogleSignin.signIn = mockSignin
mockedAxios.request.mockImplementationOnce(
() => new Promise(resolve => {
resolve({
status: 200,
data: {
token: "mock token"
}
});
})
);
const googleSignin = instance.root.findByType(GoogleSigninButton);
act(() => {
googleSignin.props.onPress();
})
await expect(mockHasPlayServices).toBeCalledTimes(1);
await expect(mockSignin).toBeCalledTimes(1);
});
it('receives nothing when signin failed and fails to navigate to next scene', async () => {
LocalStorage.setSecretKey("sssttt");
const instance = ReactTestRenderer.create(
<AppContext.Provider value={testProps}>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
header: () => <></>,
}}>
<Stack.Screen name="login" component={LoginPage} />
</Stack.Navigator>
</NavigationContainer>
</AppContext.Provider>,
);
expect(instance.toJSON()).toMatchSnapshot();
const mockHasPlayServices = jest.fn().mockImplementationOnce(() => new Promise(resolve => {
resolve(true);
}));
const mockSignin = jest.fn().mockImplementationOnce(() => {
throw new Error("signin error");
});
GoogleSignin.hasPlayServices = mockHasPlayServices
GoogleSignin.signIn = mockSignin
const googleSignin = instance.root.findByType(GoogleSigninButton);
act(() => {
googleSignin.props.onPress();
})
await expect(mockHasPlayServices).toBeCalledTimes(1);
await expect(mockSignin).toBeCalledTimes(1);
await expect(mockNavigation).toBeCalledTimes(0);
});
it('receives nothing when has no play service failed and fails to navigate to next scene', async () => {
LocalStorage.setSecretKey("sssttt");
const instance = ReactTestRenderer.create(
<AppContext.Provider value={testProps}>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
header: () => <></>,
}}>
<Stack.Screen name="login" component={LoginPage} />
</Stack.Navigator>
</NavigationContainer>
</AppContext.Provider>,
);
expect(instance.toJSON()).toMatchSnapshot();
const mockHasPlayServices = jest.fn().mockImplementationOnce(() => {
throw new Error("has no play service error");
});
const mockSignin = jest.fn().mockImplementationOnce(() => new Promise(resolve => {
const data = {
idToken: "1234",
serverAuthCode: "4321",
user: {
email: "test@gmail.com",
id: "1234",
givenName: "test",
familyName: "test",
photo: "test.com", // url
name: "test" // full name
}
}
resolve(data);
}));
GoogleSignin.hasPlayServices = mockHasPlayServices
GoogleSignin.signIn = mockSignin
const googleSignin = instance.root.findByType(GoogleSigninButton);
act(() => {
googleSignin.props.onPress();
})
await expect(mockHasPlayServices).toBeCalledTimes(1);
await expect(mockSignin).toBeCalledTimes(0);
await expect(mockNavigation).toBeCalledTimes(0);
});
it('receives google token when signin is successful and succesfully navigates to form', async () => {
LocalStorage.setSecretKey("sssttt");
const instance = ReactTestRenderer.create(
<AppContext.Provider value={testProps}>
<NavigationContainer>
<Stack.Navigator
screenOptions={{
header: () => <></>,
}}>
<Stack.Screen name="login" component={LoginPage} />
</Stack.Navigator>
</NavigationContainer>
</AppContext.Provider>,
);
expect(instance.toJSON()).toMatchSnapshot();
const mockHasPlayServices = jest.fn().mockImplementationOnce(() => new Promise(resolve => {
resolve(true);
}));
const mockSignin = jest.fn().mockImplementationOnce(() => new Promise(resolve => {
const data = {
idToken: "1234",
serverAuthCode: "4321",
user: {
email: "test@gmail.com",
id: "1234",
givenName: "test",
familyName: "test",
photo: "test.com", // url
name: "test" // full name
}
}
resolve(data);
}));
GoogleSignin.hasPlayServices = mockHasPlayServices
GoogleSignin.signIn = mockSignin
mockedAxios.request.mockImplementationOnce(
() => new Promise(resolve => {
resolve({
status: 203
});
})
);
const googleSignin = instance.root.findByType(GoogleSigninButton);
act(() => {
googleSignin.props.onPress();
})
await expect(mockHasPlayServices).toBeCalledTimes(1);
await expect(mockSignin).toBeCalledTimes(1);
});
\ No newline at end of file
......@@ -11,6 +11,14 @@ import { useNavigation } from '@react-navigation/native';
import WaveBackground from 'assets/arts/background-wave.png';
import WonderingIllustration from 'assets/illustrations/wondering.png';
import { GoogleSignin, GoogleSigninButton, statusCodes } from '@react-native-google-signin/google-signin';
import {GOOGLE_CLIENT_ID} from "react-native-dotenv"
GoogleSignin.configure(
{webClientId: GOOGLE_CLIENT_ID, offlineAccess: true}
);
enum LoginResponse {
InvalidCredential = 'Username atau Password Salah.',
UnknownServerError = 'Unknown Server Error',
......@@ -30,6 +38,62 @@ const LoginPage = () => {
type: 'ANY',
},
});
const [userInfo, setUserInfo] = useState<any>();
const checkIfTokenHasCorrespondingUser = async (idToken: string | null) => {
setShouldLoading(true)
const checkRes = await services.main.checkGoogleUserExists({
token: idToken
});
setShouldLoading(false)
if (checkRes === undefined) {
setAlert({
illustration: WonderingIllustration,
message: LoginResponse.NoInternetConn,
})
} else {
if (checkRes.status === 200) {
LocalStorage.setItem('token', checkRes.data.token);
setToken(checkRes.data.token);
return false;
} else if (checkRes.status === 500) {
setAlert({
illustration: WonderingIllustration,
message: LoginResponse.UnknownServerError,
})
return false;
} else {
return true;
}
}
}
const signInWithGoogle = async () => {
try {
await GoogleSignin.hasPlayServices();
const tempUserInfo = await GoogleSignin.signIn();
setUserInfo(tempUserInfo);
const noGoogleUser = await checkIfTokenHasCorrespondingUser(tempUserInfo.idToken);
if(noGoogleUser)
navigation.navigate('officer-signup-form-google-signin', {idToken: tempUserInfo.idToken});
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
// user cancelled the login flow
} else if (error.code === statusCodes.IN_PROGRESS) {
// operation (e.g. sign in) is in progress already
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
setAlert({
illustration: WonderingIllustration,
message: "Play Service sedang tidak tersedia, coba lagi nanti",
})
} else {
}
}
};
return (
<Box
......@@ -153,6 +217,17 @@ const LoginPage = () => {
Ajukan Akun Kader
</Button>
</Box>
<Gap gap={24} axis={Gap.Axis.Vertical} />
<Box
mainAxis="center"
>
<GoogleSigninButton
style={{ width: 192, height: 48 }}
size={GoogleSigninButton.Size.Wide}
color={GoogleSigninButton.Color.Dark}
onPress={() => signInWithGoogle()}
/>
</Box>
</Action>
</Box>
);
......
......@@ -909,7 +909,7 @@ exports[`can press previous button 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......@@ -2365,7 +2365,7 @@ exports[`renders correctly 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......@@ -3821,7 +3821,7 @@ exports[`renders form with correct fields 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......@@ -5277,7 +5277,7 @@ exports[`submit data succesfully 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......
......@@ -663,7 +663,7 @@ exports[`can press previous button 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......@@ -881,7 +881,7 @@ exports[`can press previous button 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......@@ -2070,7 +2070,7 @@ exports[`renders correctly 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......@@ -2288,7 +2288,7 @@ exports[`renders correctly 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......@@ -3477,7 +3477,7 @@ exports[`renders form with correct fields 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......@@ -3695,7 +3695,7 @@ exports[`renders form with correct fields 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......@@ -4884,7 +4884,7 @@ exports[`submit data succesfully 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......@@ -5102,7 +5102,7 @@ exports[`submit data succesfully 1`] = `
]
}
>
17/5/2021
29/5/2021
</Text>
</View>
<View
......
const KECAMATAN_VALUES: {label: string; value: string}[] = [
{ label: 'Beji', value: 'Beji' },
{ label: 'Bojongsari', value: 'Bojongsari' },
{ label: 'Cilodong', value: 'Cilodong' },
{ label: 'Cimanggis', value: 'Cimanggis' },
{ label: 'Cinere', value: 'Cinere' },
{ label: 'Cipayung', value: 'Cipayung' },
{ label: 'Limo', value: 'Limo' },
{ label: 'Pancoran Mas', value: 'Pancoran Mas' },
{ label: 'Sawangan', value: 'Sawangan' },
{ label: 'Sukmajaya', value: 'Sukmajaya' },
{ label: 'Tapos', value: 'Tapos' },
];
const KELURAHAN_VALUES: { [key: string]: {label: string; value: string}[] } = {
'Beji': [
{ label: 'Beji', value: 'Beji' },
{ label: 'Beji Timur', value: 'Beji Timur' },
{ label: 'Kemirimuka', value: 'Kemirimuka' },
{ label: 'Kukusan', value: 'Kukusan' },
{ label: 'Pondok Cina', value: 'Pondok Cina' },
{ label: 'Tanah Baru', value: 'Tanah Baru' },
],
'Bojongsari': [
{ label: 'Bojongsari Baru', value