Skip to content

Commit

Permalink
feat: hoc tests
Browse files Browse the repository at this point in the history
  • Loading branch information
BJvdA committed Mar 15, 2021
1 parent 6a5cac5 commit 3fc3b29
Show file tree
Hide file tree
Showing 12 changed files with 521 additions and 75 deletions.
1 change: 1 addition & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('isomorphic-fetch');
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,12 @@
"eslint-plugin-react-hooks": "4.2.0",
"events": "^3.3.0",
"husky": "4.3.8",
"isomorphic-fetch": "^3.0.0",
"jest": "26.6.3",
"jest-axe": "^4.1.0",
"jest-styled-components": "^7.0.3",
"lint-staged": "10.5.3",
"msw": "^0.27.1",
"next": "10.0.5",
"node-mocks-http": "^1.10.1",
"prettier": "2.2.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EventEmitter } from 'events';
import { createMocks } from 'node-mocks-http';

import { loginHandler } from '~api/loginHandler';
import { loginHandler } from '../loginHandler';

describe('[api] loginHandler', () => {
it('should set cookie on correct password', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EventEmitter } from 'events';
import { createMocks } from 'node-mocks-http';
import { passwordCheckHandler } from '~api/passwordCheckHandler';

import { passwordCheckHandler } from '../passwordCheckHandler';

describe('[api] passwordCheckHandler', () => {
it('should succeed with correct cookie', async () => {
Expand Down
18 changes: 15 additions & 3 deletions src/hoc/LoginComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useState } from 'react';

interface LoginComponentProps {
apiPath: string;
apiUrl?: string;
}

export const LoginComponent = ({ apiPath }: LoginComponentProps) => {
export const LoginComponent = ({ apiUrl }: LoginComponentProps) => {
const [isBusy, setBusy] = useState(false);
const [error, setError] = useState('');

Expand All @@ -26,7 +26,7 @@ export const LoginComponent = ({ apiPath }: LoginComponentProps) => {

const formData = new FormData(form);

const res = await fetch(`/api${apiPath}`, {
const res = await fetch(apiUrl || `/api/login`, {
method: 'post',
credentials: 'include',
body: JSON.stringify({ password: formData.get('password') }),
Expand Down Expand Up @@ -116,6 +116,12 @@ export const LoginComponent = ({ apiPath }: LoginComponentProps) => {
animation: shake .4s linear;
}
#password-form .error {
color: #DD4A4A;
margin-top: 12px;
font-size: 18px;
}
@keyframes shake {
8%, 41% {
transform: translateX(-10px);
Expand Down Expand Up @@ -161,6 +167,7 @@ export const LoginComponent = ({ apiPath }: LoginComponentProps) => {
>
<h1 style={{ margin: '0 0 24px', color: '#111' }}>Login</h1>
<form
data-testid="form"
onSubmit={onSubmit}
style={{
display: 'flex',
Expand Down Expand Up @@ -195,6 +202,11 @@ export const LoginComponent = ({ apiPath }: LoginComponentProps) => {
height: '48px',
}}
/>
{!!error && (
<div className="error" data-testid="error">
{error}
</div>
)}

<button
type="submit"
Expand Down
113 changes: 113 additions & 0 deletions src/hoc/__tests__/LoginComponent.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react';
import {
act,
render,
cleanup,
screen,
waitFor,
fireEvent,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import { setupServer } from 'msw/node';

import { LoginComponent } from '../LoginComponent';

const server = setupServer();

describe('[hoc] LoginComponent', () => {
beforeAll(() => server.listen());
afterEach(() => {
server.resetHandlers();
cleanup();
jest.restoreAllMocks();
});
afterAll(() => server.close());

it('should reload on successful login', async () => {
server.use(
rest.post('http://localhost/api/login', async (req, res, ctx) => {
return res(ctx.status(200), ctx.json({}));
}),
);

const reloadMock = jest.fn();
delete window.location;
window.location = { ...window.location, reload: reloadMock };

render(<LoginComponent apiUrl="http://localhost/api/login" />);

act(() => {
userEvent.click(screen.getByRole('button'));
});

await waitFor(() => expect(reloadMock).toBeCalled());
});

it('should show error state on failed login', async () => {
server.use(
rest.post('http://localhost/api/login', async (req, res, ctx) => {
return res(
ctx.status(400),
ctx.json({ message: 'Incorrect password' }),
);
}),
);

render(<LoginComponent apiUrl="http://localhost/api/login" />);

act(() => {
userEvent.click(screen.getByRole('button'));
});

await waitFor(() =>
expect(screen.getByLabelText('Password')).toHaveClass('invalid'),
);

expect(screen.getByTestId('error')).toHaveTextContent('Incorrect password');
});

it('should show error state on error', async () => {
server.use(
rest.post('/api/login', async (req, res, ctx) => {
return res(ctx.status(500));
}),
);

render(<LoginComponent />);

act(() => {
userEvent.click(screen.getByRole('button'));
});

await waitFor(() =>
expect(screen.getByLabelText('Password')).toHaveClass('invalid'),
);

expect(screen.getByTestId('error')).toHaveTextContent(
'An error has occured.',
);
});

it('should not check if already checking', async () => {
server.use(
rest.post('http://localhost/api/login', async (req, res, ctx) => {
return res(ctx.status(500), ctx.json({}));
}),
);

const fetchMock = jest.fn(() => new Promise(() => {}));
jest.spyOn(global, 'fetch').mockImplementation(fetchMock as any);

render(<LoginComponent apiUrl="http://localhost/api/login" />);

await act(async () => {
await fireEvent.submit(screen.getByTestId('form'));
await fireEvent.submit(screen.getByTestId('form'));
await fireEvent.submit(screen.getByTestId('form'));
await fireEvent.submit(screen.getByTestId('form'));
});

expect(fetchMock).toBeCalledTimes(1);
});
});
153 changes: 153 additions & 0 deletions src/hoc/__tests__/withPasswordProtect.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React from 'react';
import { act, render, cleanup, screen, waitFor } from '@testing-library/react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import * as hooks from 'next/amp';

import { withPasswordProtect } from '../withPasswordProtect';

const App = ({ Component }) => <Component />;

const server = setupServer();

describe('[hoc] withPasswordProtect', () => {
beforeAll(() => server.listen());
afterEach(() => {
server.resetHandlers();
cleanup();
jest.restoreAllMocks();
});
afterAll(() => server.close());

it('should render normally if logged in', async () => {
server.use(
rest.get('http://localhost/api/check', async (req, res, ctx) => {
return res(ctx.status(200));
}),
);

const Wrapped = withPasswordProtect(App, {
checkApiUrl: 'http://localhost/api/check',
});

await act(async () => {
render(
<Wrapped
Component={() => <div>hidden</div>}
pageProps={{}}
router={{} as any}
/>,
);
});

await waitFor(() => {
expect(screen.getByText('hidden')).toBeInTheDocument();
});
});

it('should render login if logged out', async () => {
server.use(
rest.get('http://localhost/api/check', async (req, res, ctx) => {
return res(ctx.status(401));
}),
);

const Wrapped = withPasswordProtect(App, {
checkApiUrl: 'http://localhost/api/check',
});

await act(async () => {
render(
<Wrapped
Component={() => <div>hidden</div>}
pageProps={{}}
router={{} as any}
/>,
);
});

await waitFor(() => {
expect(screen.getByText('Password')).toBeInTheDocument();
});
});

it('should allow setting check api url', async () => {
server.use(
rest.get('http://localhost/api/check', async (req, res, ctx) => {
return res(ctx.status(200));
}),
);

const Wrapped = withPasswordProtect(App);

await act(async () => {
render(
<Wrapped
Component={() => <div>hidden</div>}
pageProps={{}}
router={{} as any}
/>,
);
});

await waitFor(() => {
expect(screen.getByText('Password')).toBeInTheDocument();
});
});

it('should render nothing if logged out and amp', async () => {
server.use(
rest.get('http://localhost/api/check', async (req, res, ctx) => {
return res(ctx.status(401));
}),
);

jest.spyOn(hooks, 'useAmp').mockImplementation(() => true);

const Wrapped = withPasswordProtect(App, {
checkApiUrl: 'http://localhost/api/check',
});

let container;

await act(async () => {
container = render(
<Wrapped
Component={() => <div>hidden</div>}
pageProps={{}}
router={{} as any}
/>,
).container;
});

await waitFor(() => {
expect(screen.queryByText('Password')).not.toBeInTheDocument();
expect(screen.queryByText('hidden')).not.toBeInTheDocument();
expect(container.firstChild).toBeNull();
});
});

it('should render login if error', async () => {
jest.spyOn(global, 'fetch').mockImplementation(() => {
throw new Error();
});

const Wrapped = withPasswordProtect(App, {
checkApiUrl: 'http://localhost/api/check',
});

await act(async () => {
render(
<Wrapped
Component={() => <div>hidden</div>}
pageProps={{}}
router={{} as any}
/>,
);
});

await waitFor(() => {
expect(screen.getByText('Password')).toBeInTheDocument();
});
});
});
Loading

0 comments on commit 3fc3b29

Please sign in to comment.