Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add jest mock #492

Merged
merged 4 commits into from
Oct 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ See this [issue](https://github.com/react-native-google-signin/google-signin/iss
- Support all 3 types of authentication methods (standard, with server-side validation or with offline access (aka server side access))
- Promise-based API consistent between Android and iOS
- Typings for TypeScript and Flow
- Mock of the native module for testing with Jest
- Native sign in buttons

## Requirements
Expand Down Expand Up @@ -325,6 +326,18 @@ Example `userInfo` which is returned after successful sign in.
}
```

## Jest module mock

If you use Jest for testing, you may need to mock the functionality of the native module. This library ships with a Jest mock that you can add to the `setupFiles` array in the Jest config.

By default, the mock behaves as if the calls were successful and returns mock user data.

```
"setupFiles": [
"./node_modules/@react-native-google-signin/google-signin/jest/build/setup.js"
],
```

## Want to contribute?

Check out the [contributor guide](docs/CONTRIBUTING.md)!
Expand Down
10 changes: 10 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @type {import('jest').Config} */
const config = {
preset: 'react-native',
modulePathIgnorePatterns: ['<rootDir>/example/node_modules', '<rootDir>/lib/'],
moduleNameMapper: {
'@react-native-google-signin/google-signin': '<rootDir>/src/index.ts',
},
};

module.exports = config;
60 changes: 60 additions & 0 deletions jest/setup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import { Pressable, Text } from 'react-native';
import type { User, GoogleSigninButtonProps } from '../src/types';
import type { GoogleSigninSingleton } from '../src/GoogleSignin';
import type { GoogleSigninButton } from '../src';

export const mockUserInfo: User = {
idToken: 'mockIdToken',
serverAuthCode: 'mockServerAuthCode',
scopes: [],
user: {
email: 'mockEmail',
id: 'mockId',
givenName: 'mockGivenName',
familyName: 'mockFamilyName',
photo: null,
name: 'mockFullName',
},
};

const MockGoogleSigninButton = (props: GoogleSigninButtonProps) => {
return (
<Pressable {...props}>
<Text>Mock Sign in with Google</Text>
</Pressable>
);
};
MockGoogleSigninButton.Size = { Standard: 0, Wide: 1, Icon: 2 };
MockGoogleSigninButton.Color = { Dark: 0, Light: 1 };

const MockGoogleSigninButtonTyped: typeof GoogleSigninButton = MockGoogleSigninButton;

const mockStatusCodes = {
SIGN_IN_CANCELLED: 'mock_SIGN_IN_CANCELLED',
IN_PROGRESS: 'mock_IN_PROGRESS',
PLAY_SERVICES_NOT_AVAILABLE: 'mock_PLAY_SERVICES_NOT_AVAILABLE',
SIGN_IN_REQUIRED: 'mock_SIGN_IN_REQUIRED',
};

const mockGoogleSignin: typeof GoogleSigninSingleton = {
configure: jest.fn(),
hasPlayServices: jest.fn().mockResolvedValue(true),
getTokens: jest
.fn()
.mockResolvedValue({ accessToken: 'mockAccessToken', idToken: 'mockIdToken' }),
signIn: jest.fn().mockResolvedValue(mockUserInfo),
signInSilently: jest.fn().mockResolvedValue(mockUserInfo),
revokeAccess: jest.fn().mockResolvedValue(null),
signOut: jest.fn().mockResolvedValue(null),
isSignedIn: jest.fn().mockResolvedValue(true),
addScopes: jest.fn().mockResolvedValue(mockUserInfo),
getCurrentUser: jest.fn().mockResolvedValue(mockUserInfo),
clearCachedAccessToken: jest.fn().mockResolvedValue(null),
};

jest.mock('@react-native-google-signin/google-signin', () => ({
statusCodes: mockStatusCodes,
GoogleSignin: mockGoogleSignin,
GoogleSigninButton: MockGoogleSigninButtonTyped,
}));
10 changes: 10 additions & 0 deletions jest/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "expo-module-scripts/tsconfig.plugin",
"compilerOptions": {
"outDir": "./build",
"jsx": "react",
"declaration": false,
"importsNotUsedAsValues": "error"
},
"include": ["./setup.tsx"],
}
24 changes: 12 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@
"!**/__mocks__",
"app.plugin.js",
"expo-module.config.json",
"plugin/build"
"plugin/build",
"jest/build/setup.js",
"README.md"
],
"scripts": {
"build:mock": "tsc --build jest && cp jest/build/jest/setup.js jest/build/setup.js",
"build:plugin": "tsc --build plugin",
"clean:plugin": "expo-module clean plugin",
"test": "jest",
"typescript": "tsc --noEmit",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"prepare": "bob build && yarn run build:plugin",
"prepare": "bob build && yarn build:plugin && yarn build:mock",
"release": "yarn prepare && npx semantic-release",
"example": "yarn --cwd example",
"pods": "cd example && pod-install --quiet",
Expand Down Expand Up @@ -78,7 +81,7 @@
"react-native": "^0.65.1",
"react-native-builder-bob": "^0.18.0",
"semantic-release": "^19.0.5",
"typescript": "^4.4.3"
"typescript": "^4.8.4"
},
"peerDependencies": {
"@expo/config-plugins": "^4.1.0",
Expand All @@ -90,13 +93,6 @@
"optional": true
}
},
"jest": {
"preset": "react-native",
"modulePathIgnorePatterns": [
"<rootDir>/example/node_modules",
"<rootDir>/lib/"
]
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
Expand All @@ -113,11 +109,15 @@
"extends": [
"@react-native-community",
"prettier"
]
],
"env": {
"jest": true
}
},
"eslintIgnore": [
"node_modules/",
"lib/"
"lib/",
"build/"
],
"react-native-builder-bob": {
"source": "src",
Expand Down
10 changes: 5 additions & 5 deletions src/GoogleSignin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ class GoogleSignin {
export const GoogleSigninSingleton = new GoogleSignin();

export const statusCodes = {
SIGN_IN_CANCELLED: RNGoogleSignin.SIGN_IN_CANCELLED,
IN_PROGRESS: RNGoogleSignin.IN_PROGRESS,
PLAY_SERVICES_NOT_AVAILABLE: RNGoogleSignin.PLAY_SERVICES_NOT_AVAILABLE,
SIGN_IN_REQUIRED: RNGoogleSignin.SIGN_IN_REQUIRED,
};
SIGN_IN_CANCELLED: RNGoogleSignin.SIGN_IN_CANCELLED as string,
IN_PROGRESS: RNGoogleSignin.IN_PROGRESS as string,
PLAY_SERVICES_NOT_AVAILABLE: RNGoogleSignin.PLAY_SERVICES_NOT_AVAILABLE as string,
SIGN_IN_REQUIRED: RNGoogleSignin.SIGN_IN_REQUIRED as string,
} as const;
96 changes: 39 additions & 57 deletions src/GoogleSigninButton.tsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,58 @@
import React, { PureComponent } from 'react';
import React, { useEffect } from 'react';

import {
NativeModules,
Platform,
DeviceEventEmitter,
StyleSheet,
EmitterSubscription,
ViewProps,
StyleProp,
ViewStyle,
} from 'react-native';
import { NativeModules, Platform, DeviceEventEmitter, StyleSheet } from 'react-native';
import { RNGoogleSigninButton } from './RNGoogleSiginButton';
import type { RNGoogleSignType } from './types';
import type { GoogleSigninButtonProps } from './types';

const RNGoogleSignin: RNGoogleSignType = NativeModules.RNGoogleSignin;

export interface GoogleSigninButtonProps extends ViewProps {
style?: StyleProp<ViewStyle>;
size?: 0 | 1 | 2;
color?: 0 | 1;
disabled?: boolean;
onPress?(): void;
interface RNGoogleSignStaticsType {
BUTTON_SIZE_STANDARD: number;
BUTTON_SIZE_WIDE: number;
BUTTON_SIZE_ICON: number;
BUTTON_COLOR_DARK: number;
BUTTON_COLOR_LIGHT: number;
}
const RNGoogleSignin: RNGoogleSignStaticsType = NativeModules.RNGoogleSignin;

export class GoogleSigninButton extends PureComponent<GoogleSigninButtonProps> {
_clickListener?: EmitterSubscription;

static Size = {
Icon: RNGoogleSignin.BUTTON_SIZE_ICON,
Standard: RNGoogleSignin.BUTTON_SIZE_STANDARD,
Wide: RNGoogleSignin.BUTTON_SIZE_WIDE,
};

static Color = {
Dark: RNGoogleSignin.BUTTON_COLOR_DARK,
Light: RNGoogleSignin.BUTTON_COLOR_LIGHT,
};

static defaultProps = {
size: RNGoogleSignin.BUTTON_SIZE_STANDARD,
};

componentDidMount() {
if (Platform.OS === 'android') {
this._clickListener = DeviceEventEmitter.addListener('RNGoogleSigninButtonClicked', () => {
this.props.onPress && this.props.onPress();
});
export const GoogleSigninButton = ({ onPress, ...props }: GoogleSigninButtonProps) => {
useEffect(() => {
if (Platform.OS === 'ios') {
return;
}
}
const clickListener = DeviceEventEmitter.addListener('RNGoogleSigninButtonClicked', () => {
onPress && onPress();
});
return () => {
clickListener.remove();
};
}, [onPress]);

componentWillUnmount() {
this._clickListener && this._clickListener.remove();
}

getRecommendedSize() {
switch (this.props.size) {
const recommendedSize = (() => {
switch (props.size) {
case RNGoogleSignin.BUTTON_SIZE_ICON:
return styles.iconSize;
case RNGoogleSignin.BUTTON_SIZE_WIDE:
return styles.wideSize;
default:
return styles.standardSize;
}
}
})();

render() {
const { style, ...props } = this.props;
const { style, ...other } = props;

// @ts-ignore style prop incompatible
return <RNGoogleSigninButton {...props} style={[this.getRecommendedSize(), style]} />;
}
}
// @ts-ignore style prop incompatible
return <RNGoogleSigninButton {...other} style={[recommendedSize, style]} />;
};

GoogleSigninButton.Size = {
Icon: RNGoogleSignin.BUTTON_SIZE_ICON,
Standard: RNGoogleSignin.BUTTON_SIZE_STANDARD,
Wide: RNGoogleSignin.BUTTON_SIZE_WIDE,
};

GoogleSigninButton.Color = {
Dark: RNGoogleSignin.BUTTON_COLOR_DARK,
Light: RNGoogleSignin.BUTTON_COLOR_LIGHT,
};

// sizes according to https://developers.google.com/identity/sign-in/ios/reference/Classes/GIDSignInButton
const styles = StyleSheet.create({
Expand Down
21 changes: 20 additions & 1 deletion src/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
it.todo('write a test');
import '../../jest/setup';
import {
GoogleSignin,
statusCodes,
GoogleSigninButton,
} from '@react-native-google-signin/google-signin';
import { mockUserInfo } from '../../jest/setup';

it('sanity check for exported mock methods', async () => {
expect(await GoogleSignin.isSignedIn()).toBe(true);
expect(await GoogleSignin.signIn()).toStrictEqual(mockUserInfo);
expect(await GoogleSignin.signOut()).toBeNull();
expect(GoogleSigninButton).toBeInstanceOf(Function);
expect(statusCodes).toStrictEqual({
IN_PROGRESS: 'mock_IN_PROGRESS',
PLAY_SERVICES_NOT_AVAILABLE: 'mock_PLAY_SERVICES_NOT_AVAILABLE',
SIGN_IN_CANCELLED: 'mock_SIGN_IN_CANCELLED',
SIGN_IN_REQUIRED: 'mock_SIGN_IN_REQUIRED',
});
});
File renamed without changes.
19 changes: 10 additions & 9 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
// Type definitions for @react-native-google-signin/google-signin 6.0
// Type definitions for @react-native-google-signin/google-signin
// Project: https://github.com/react-native-community/google-signin
// Definitions by: Jacob Froman <https://github.com/j-fro>
// Michele Bombardi <https://github.com/bm-software>
// Christian Chown <https://github.com/christianchown>
// Eric Chen <https://github.com/echentw>

export interface RNGoogleSignType {
BUTTON_SIZE_STANDARD: 0;
BUTTON_SIZE_WIDE: 1;
BUTTON_SIZE_ICON: 2;
BUTTON_COLOR_DARK: 0;
BUTTON_COLOR_LIGHT: 1;
}
import type { StyleProp, ViewProps, ViewStyle } from 'react-native';

export interface HasPlayServicesParams {
/**
Expand Down Expand Up @@ -39,7 +33,6 @@ export interface AddScopesParams {

export interface ConfigureParams {
/**
* ANDROID ONLY. Use AddScopes() on iOS
* The Google API scopes to request access to. Default is email and profile.
*/
scopes?: string[];
Expand Down Expand Up @@ -112,3 +105,11 @@ export interface User {
export interface NativeModuleError extends Error {
code: string;
}

export interface GoogleSigninButtonProps extends ViewProps {
style?: StyleProp<ViewStyle>;
size?: number;
color?: number;
disabled?: boolean;
onPress?(): void;
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14163,10 +14163,10 @@ typescript@^4.0.2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4"
integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==

typescript@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324"
integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==
typescript@^4.8.4:
version "4.8.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==

uglify-es@^3.1.9:
version "3.3.9"
Expand Down