Skip to content

Commit

Permalink
[SDK-1577] [SDK-1578] Add login functionality (#5)
Browse files Browse the repository at this point in the history
Add login functionality
  • Loading branch information
adamjmcgrath authored May 7, 2020
1 parent 009a909 commit ceb2f16
Show file tree
Hide file tree
Showing 20 changed files with 471 additions and 80 deletions.
9 changes: 9 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,17 @@ module.exports = {
extends: [
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
rules: {
'@typescript-eslint/camelcase': 'off',
},
overrides: [
{
files: ['*.test.tsx'],
rules: {
'@typescript-eslint/ban-ts-ignore': 'off',
},
},
],
};
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ npm install react react-dom @auth0/auth0-spa-js auth0/auth0-react
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { AuthProvider } from '@auth0/auth0-react';
import { Auth0Provider } from '@auth0/auth0-react';

ReactDOM.render(
<AuthProvider
<Auth0Provider
domain="YOUR_AUTH0_DOMAIN"
client_id="YOUR_AUTH0_CLIENT_ID"
redirect_uri={window.location.origin}
>
<App />
</AuthProvider>,
</Auth0Provider>,
document.getElementById('app')
);
```
Expand Down
19 changes: 19 additions & 0 deletions __mocks__/@auth0/auth0-spa-js.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const handleRedirectCallback = jest
.fn()
.mockResolvedValue({ appState: {} });
export const getTokenSilently = jest.fn();
export const getUser = jest.fn();
export const isAuthenticated = jest.fn().mockResolvedValue(false);
export const loginWithRedirect = jest.fn();
export const logout = jest.fn();

export const Auth0Client = jest.fn().mockImplementation(() => {
return {
handleRedirectCallback,
getTokenSilently,
getUser,
isAuthenticated,
loginWithRedirect,
logout,
};
});
174 changes: 174 additions & 0 deletions __tests__/auth-provider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { useContext } from 'react';
import Auth0Context from '../src/auth0-context';
import { renderHook } from '@testing-library/react-hooks';
import {
Auth0Client,
// @ts-ignore
getTokenSilently,
// @ts-ignore
isAuthenticated,
// @ts-ignore
getUser,
// @ts-ignore
handleRedirectCallback,
// @ts-ignore
loginWithRedirect,
// @ts-ignore
logout,
} from '@auth0/auth0-spa-js';
import { createWrapper } from './helpers';

describe('Auth0Provider', () => {
it('should provide the Auth0Provider result', async () => {
const wrapper = createWrapper();
const { result, waitForNextUpdate } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
expect(result.current).toBeDefined();
await waitForNextUpdate();
});

it('should configure an instance of the Auth0Client', async () => {
const opts = {
client_id: 'foo',
domain: 'bar',
};
const wrapper = createWrapper(opts);
const { waitForNextUpdate } = renderHook(() => useContext(Auth0Context), {
wrapper,
});
expect(Auth0Client).toHaveBeenCalledWith(opts);
await waitForNextUpdate();
});

it('should get token silently when logged out', async () => {
const wrapper = createWrapper();
const { waitForNextUpdate, result } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
expect(result.current.isLoading).toBe(true);
await waitForNextUpdate();
expect(result.current.isLoading).toBe(false);
expect(getTokenSilently).toHaveBeenCalled();
expect(result.current.isAuthenticated).toBe(false);
});

it('should get token silently when logged in', async () => {
isAuthenticated.mockResolvedValue(true);
getUser.mockResolvedValue('__test_user__');
const wrapper = createWrapper();
const { waitForNextUpdate, result } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
await waitForNextUpdate();
expect(getTokenSilently).toHaveBeenCalled();
expect(result.current.isAuthenticated).toBe(true);
expect(result.current.user).toBe('__test_user__');
});

it('should handle login_required errors when getting token', async () => {
getTokenSilently.mockRejectedValue({ error: 'login_required' });
const wrapper = createWrapper();
const { waitForNextUpdate, result } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
await waitForNextUpdate();
expect(getTokenSilently).toHaveBeenCalled();
expect(result.current.error).toBeUndefined();
expect(result.current.isAuthenticated).toBe(false);
});

it('should handle other errors when getting token', async () => {
getTokenSilently.mockRejectedValue({ error: '__test_error__' });
const wrapper = createWrapper();
const { waitForNextUpdate, result } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
await waitForNextUpdate();
expect(getTokenSilently).toHaveBeenCalled();
expect(result.current.error).toStrictEqual({ error: '__test_error__' });
expect(result.current.isAuthenticated).toBe(false);
});

it('should handle redirect callback success and clear the url', async () => {
window.history.pushState(
{},
document.title,
'/?code=__test_code__&state=__test_state__'
);
expect(window.location.href).toBe(
'https://www.example.com/?code=__test_code__&state=__test_state__'
);
const wrapper = createWrapper();
const { waitForNextUpdate } = renderHook(() => useContext(Auth0Context), {
wrapper,
});
await waitForNextUpdate();
expect(handleRedirectCallback).toHaveBeenCalled();
expect(window.location.href).toBe('https://www.example.com/');
});

it('should handle redirect callback errors', async () => {
window.history.pushState({}, document.title, '/?error=__test_error__');
handleRedirectCallback.mockRejectedValue('__test_error__');
const wrapper = createWrapper();
const { waitForNextUpdate, result } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
await waitForNextUpdate();
expect(handleRedirectCallback).toHaveBeenCalled();
expect(result.current.error).toStrictEqual('__test_error__');
});

it('should handle redirect and call a custom handler', async () => {
window.history.pushState(
{},
document.title,
'/?code=__test_code__&state=__test_state__'
);
handleRedirectCallback.mockResolvedValue({ appState: { foo: 'bar' } });
const onRedirectCallback = jest.fn();
const wrapper = createWrapper({
onRedirectCallback,
});
const { waitForNextUpdate } = renderHook(() => useContext(Auth0Context), {
wrapper,
});
await waitForNextUpdate();
expect(onRedirectCallback).toHaveBeenCalledWith({ foo: 'bar' });
});

it('should provide a login method', async () => {
const wrapper = createWrapper();
const { waitForNextUpdate, result } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
await waitForNextUpdate();
expect(result.current.login).toBeInstanceOf(Function);
await result.current.login({ redirect_uri: '__redirect_uri__' });
expect(loginWithRedirect).toHaveBeenCalledWith({
redirect_uri: '__redirect_uri__',
});
});

it('should provide a logout method', async () => {
const wrapper = createWrapper();
const { waitForNextUpdate, result } = renderHook(
() => useContext(Auth0Context),
{ wrapper }
);
await waitForNextUpdate();
expect(result.current.logout).toBeInstanceOf(Function);
await result.current.logout({ returnTo: '__return_to__' });
expect(logout).toHaveBeenCalledWith({
returnTo: '__return_to__',
});
});
});
42 changes: 42 additions & 0 deletions __tests__/auth-reducer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { reducer } from '../src/reducer';
import { initialAuthState } from '../src/auth-state';

describe('reducer', () => {
it('should initialise when authenticated', async () => {
const payload = {
isAuthenticated: true,
user: 'Bob',
};
expect(
reducer(initialAuthState, { type: 'INITIALISED', ...payload })
).toEqual({
...initialAuthState,
isLoading: false,
...payload,
});
});

it('should initialise when not authenticated', async () => {
const payload = {
isAuthenticated: false,
};
expect(
reducer(initialAuthState, { type: 'INITIALISED', ...payload })
).toEqual({
...initialAuthState,
isLoading: false,
...payload,
});
});

it('should handle error state', async () => {
const payload = {
error: new Error('__test_error__'),
};
expect(reducer(initialAuthState, { type: 'ERROR', ...payload })).toEqual({
...initialAuthState,
isLoading: false,
...payload,
});
});
});
29 changes: 0 additions & 29 deletions __tests__/auth0-provider.test.tsx

This file was deleted.

3 changes: 2 additions & 1 deletion __tests__/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import Auth0Provider from '../src/auth0-provider';
export const createWrapper = ({
client_id = '__test_client_id__',
domain = '__test_domain__',
...opts
}: Partial<Auth0ClientOptions> = {}) => ({
children,
}: PropsWithChildren<{}>): JSX.Element => (
<Auth0Provider domain={domain} client_id={client_id}>
<Auth0Provider domain={domain} client_id={client_id} {...opts}>
{children}
</Auth0Provider>
);
24 changes: 24 additions & 0 deletions __tests__/use-auth.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import useAuth0 from '../src/use-auth0';
import { renderHook } from '@testing-library/react-hooks';
import { createWrapper } from './helpers';

describe('useAuth0', () => {
it('should provide the auth context', async () => {
const wrapper = createWrapper();
const {
result: { current },
waitForNextUpdate,
} = renderHook(useAuth0, { wrapper });
await waitForNextUpdate();
expect(current).toBeDefined();
});

it('should throw with no provider', () => {
const {
result: { current },
} = renderHook(useAuth0);
expect(current.login).toThrowError(
'You forgot to wrap your component in <Auth0Provider>.'
);
});
});
16 changes: 0 additions & 16 deletions __tests__/use-auth0.test.tsx

This file was deleted.

Loading

0 comments on commit ceb2f16

Please sign in to comment.