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

Multiselect's value is an array of strings #80

Merged
merged 3 commits into from
Dec 8, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@
},
"eslintConfig": {
"root": true,
"plugins": [
"testing-library"
],
"extends": [
"airbnb",
"plugin:jest/recommended"
"plugin:jest/recommended",
"plugin:testing-library/react"
],
"ignorePatterns": [
"build/*",
Expand Down Expand Up @@ -117,6 +121,7 @@
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-react": "^7.20.5",
"eslint-plugin-react-hooks": "^4.0.8",
"eslint-plugin-testing-library": "^3.10.1",
"fetch-mock": "^9.10.7",
"history": "^5.0.0",
"jest-fetch-mock": "^3.0.3",
Expand Down
Empty file added frontend/src/.eslintrc
Empty file.
10 changes: 5 additions & 5 deletions frontend/src/__tests__/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import '@testing-library/jest-dom';
import join from 'url-join';
import {
screen, render, waitFor, fireEvent,
screen, render, fireEvent,
} from '@testing-library/react';
import fetchMock from 'fetch-mock';
import App from '../App';
Expand All @@ -21,13 +21,13 @@ describe('App', () => {
});

it('displays the logout button', async () => {
expect(await waitFor(() => screen.getByText('Logout'))).toBeVisible();
expect(await screen.findByText('Logout')).toBeVisible();
});

it('can log the user out when "logout" is pressed', async () => {
const logout = await waitFor(() => screen.getByText('Logout'));
const logout = await screen.findByText('Logout');
fireEvent.click(logout);
expect(await waitFor(() => screen.getByText('HSES Login'))).toBeVisible();
expect(await screen.findByText('HSES Login')).toBeVisible();
});
});

Expand All @@ -38,7 +38,7 @@ describe('App', () => {
});

it('displays the login button', async () => {
expect(await waitFor(() => screen.getByText('HSES Login'))).toBeVisible();
expect(await screen.findByText('HSES Login')).toBeVisible();
});
});
});
47 changes: 33 additions & 14 deletions frontend/src/components/MultiSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,44 @@ const styles = {
function MultiSelect({
label, name, options, disabled, control, required,
}) {
const findLabel = (value) => {
const opt = options.find((o) => o.value === value);
if (!opt) {
return value;
}
return opt.label;
};

return (
<Label>
{label}
<Controller
render={({ onChange, value }) => (
<Select
id={name}
value={value}
onChange={onChange}
styles={styles}
components={{ DropdownIndicator }}
options={options}
isDisabled={disabled}
placeholder=""
isMulti
/>
)}
render={({ onChange, value }) => {
let values = value;
if (value) {
values = value.map((v) => ({
value: v, label: findLabel(v),
}));
}
return (
<Select
id={name}
value={values}
onChange={(e) => {
const newValue = e ? e.map((v) => v.value) : null;
onChange(newValue);
}}
styles={styles}
components={{ DropdownIndicator }}
options={options}
isDisabled={disabled}
placeholder=""
isMulti
/>
);
}}
control={control}
defaultValue=""
defaultValue={[]}
rules={{
required,
}}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Navigator/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('Navigator', () => {
renderNavigator();
const firstInput = screen.getByTestId('first');
userEvent.click(firstInput);
const second = await waitFor(() => screen.getByText('second page'));
const second = await screen.findByText('second page');
userEvent.click(second);
const first = screen.getByText('first page');
await waitFor(() => expect(within(first.nextSibling).getByText('In progress')).toBeVisible());
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/__tests__/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('DatePicker', () => {
render(<RenderDatePicker />);
const openCalendar = screen.getByRole('button');
fireEvent.click(openCalendar);
const button = await waitFor(() => screen.getByLabelText('Move backward to switch to the previous month.'));
const button = await screen.findByLabelText('Move backward to switch to the previous month.');
await waitFor(() => expect(button).toBeVisible());
});
});
63 changes: 63 additions & 0 deletions frontend/src/components/__tests__/MutliSelect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import '@testing-library/jest-dom';
import React from 'react';
import { render, screen } from '@testing-library/react';
import selectEvent from 'react-select-event';
import { act } from 'react-dom/test-utils';
import { useForm } from 'react-hook-form';
import userEvent from '@testing-library/user-event';

import MultiSelect from '../MultiSelect';

const options = [
{ label: 'one', value: 'one' },
{ label: 'two', value: 'two' },
];

describe('MultiSelect', () => {
// eslint-disable-next-line react/prop-types
const TestMultiSelect = ({ onSubmit, defaultValues }) => {
const { control, handleSubmit } = useForm({
defaultValues,
mode: 'all',
});

const submit = (data) => {
onSubmit(data);
};

return (
<form onSubmit={handleSubmit(submit)}>
<MultiSelect
control={control}
label="label"
name="name"
options={options}
required={false}
/>
<button data-testid="submit" type="submit">submit</button>
</form>
);
};

it('selected value is an array of strings', async () => {
const onSubmit = jest.fn();
render(<TestMultiSelect onSubmit={onSubmit} />);
await selectEvent.select(screen.getByLabelText('label'), ['one']);
await act(async () => {
userEvent.click(screen.getByTestId('submit'));
});
expect(onSubmit).toHaveBeenCalledWith({ name: ['one'] });
});

it('null values do not cause an error', async () => {
const onSubmit = jest.fn();
render(<TestMultiSelect onSubmit={onSubmit} />);
const select = screen.getByLabelText('label');
await selectEvent.select(select, ['one']);
await selectEvent.clearAll(select);
await act(async () => {
userEvent.click(screen.getByTestId('submit'));
});
expect(onSubmit).toHaveBeenCalledWith({ name: null });
});
});
26 changes: 13 additions & 13 deletions frontend/src/pages/ActivityReport/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const formData = () => ({
'activity-type': ['training'],
duration: '1',
'end-date': moment(),
grantees: 'Grantee Name 1',
grantees: ['Grantee Name 1'],
'number-of-participants': '1',
'participant-category': 'grantee',
participants: ['CEO / CFO / Executive'],
Expand All @@ -31,33 +31,33 @@ describe('ActivityReport', () => {
describe('changes the participant selection to', () => {
it('Grantee', async () => {
render(<ActivityReport />);
const information = await waitFor(() => screen.getByRole('group', { name: 'Who was the activity for?' }));
const information = await screen.findByRole('group', { name: 'Who was the activity for?' });
const grantee = within(information).getByLabelText('Grantee');
fireEvent.click(grantee);
const granteeSelectbox = await waitFor(() => screen.getByRole('textbox', { name: 'Grantee name(s)' }));
const granteeSelectbox = await screen.findByRole('textbox', { name: 'Grantee name(s)' });
reactSelectEvent.openMenu(granteeSelectbox);
expect(await waitFor(() => screen.getByText(withText('Grantee Name 1')))).toBeVisible();
expect(await screen.findByText(withText('Grantee Name 1'))).toBeVisible();
});

it('Non-grantee', async () => {
render(<ActivityReport />);
const information = await waitFor(() => screen.getByRole('group', { name: 'Who was the activity for?' }));
const information = await screen.findByRole('group', { name: 'Who was the activity for?' });
const nonGrantee = within(information).getByLabelText('Non-Grantee');
fireEvent.click(nonGrantee);
const granteeSelectbox = await waitFor(() => screen.getByRole('textbox', { name: 'Grantee name(s)' }));
const granteeSelectbox = await screen.findByRole('textbox', { name: 'Grantee name(s)' });
reactSelectEvent.openMenu(granteeSelectbox);
expect(await waitFor(() => screen.getByText(withText('QRIS System')))).toBeVisible();
expect(await screen.findByText(withText('QRIS System'))).toBeVisible();
});
});

it('when non-grantee is selected', async () => {
render(<ActivityReport />);
const enabled = screen.getByRole('textbox', { name: 'Grantee name(s)' });
expect(enabled).toBeDisabled();
const information = await waitFor(() => screen.getByRole('group', { name: 'Who was the activity for?' }));
const information = await screen.findByRole('group', { name: 'Who was the activity for?' });
const grantee = within(information).getByLabelText('Grantee');
fireEvent.click(grantee);
const disabled = await waitFor(() => screen.getByRole('textbox', { name: 'Grantee name(s)' }));
const disabled = await screen.findByRole('textbox', { name: 'Grantee name(s)' });
expect(disabled).not.toBeDisabled();
});
});
Expand All @@ -68,8 +68,8 @@ describe('ActivityReport', () => {
delete data['activity-method'];

render(<ActivityReport initialData={data} />);
expect(await waitFor(() => screen.getByText('Continue'))).toBeDisabled();
const box = await waitFor(() => screen.getByLabelText('Virtual'));
expect(await screen.findByText('Continue')).toBeDisabled();
const box = await screen.findByLabelText('Virtual');
fireEvent.click(box);
await waitFor(() => expect(screen.getByText('Continue')).not.toBeDisabled());
});
Expand All @@ -81,8 +81,8 @@ describe('ActivityReport', () => {
delete data['activity-type'];

render(<ActivityReport initialData={data} />);
expect(await waitFor(() => screen.getByText('Continue'))).toBeDisabled();
const box = await waitFor(() => screen.getByLabelText('Training'));
expect(await screen.findByText('Continue')).toBeDisabled();
const box = await screen.findByLabelText('Training');
fireEvent.click(box);
await waitFor(() => expect(screen.getByText('Continue')).not.toBeDisabled());
});
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/pages/Admin/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import '@testing-library/jest-dom';
import React from 'react';
import { Router } from 'react-router';
import {
render, screen, waitFor, within,
render, screen, within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createMemoryHistory } from 'history';
Expand All @@ -17,7 +17,7 @@ describe('UserInfo', () => {
});

it('user list is filterable', async () => {
const filter = await waitFor(() => screen.getByLabelText('Filter Users'));
const filter = await screen.findByLabelText('Filter Users');
userEvent.type(filter, 'Harry');
const sideNav = screen.getByTestId('sidenav');
const links = within(sideNav).getAllByRole('link');
Expand All @@ -26,27 +26,27 @@ describe('UserInfo', () => {
});

it('new user button properly sets url', async () => {
const newUser = await waitFor(() => screen.getByText('Create New User'));
const newUser = await screen.findByText('Create New User');
userEvent.click(newUser);
expect(history.location.pathname).toBe('/admin/new');
});

it('allows a user to be selected', async () => {
const button = await waitFor(() => screen.getByText('Harry Potter'));
const button = await screen.findByText('Harry Potter');
userEvent.click(button);
expect(history.location.pathname).toBe('/admin/3');
});
});

it('displays a new user', async () => {
render(<Router history={history}><Admin match={{ path: '', url: '', params: { userId: 'new' } }} /></Router>);
const userInfo = await waitFor(() => screen.getByRole('group', { name: 'User Info' }));
const userInfo = await screen.findByRole('group', { name: 'User Info' });
expect(userInfo).toBeVisible();
});

it('displays an existing user', async () => {
render(<Router history={history}><Admin match={{ path: '', url: '', params: { userId: '3' } }} /></Router>);
const userInfo = await waitFor(() => screen.getByRole('group', { name: 'User Info' }));
const userInfo = await screen.findByRole('group', { name: 'User Info' });
expect(userInfo).toBeVisible();
});
});
44 changes: 44 additions & 0 deletions frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,17 @@
eslint-scope "^5.0.0"
eslint-utils "^2.0.0"

"@typescript-eslint/experimental-utils@^3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686"
integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/types" "3.10.1"
"@typescript-eslint/typescript-estree" "3.10.1"
eslint-scope "^5.0.0"
eslint-utils "^2.0.0"

"@typescript-eslint/parser@^2.10.0":
version "2.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8"
Expand All @@ -1836,6 +1847,11 @@
"@typescript-eslint/typescript-estree" "2.34.0"
eslint-visitor-keys "^1.1.0"

"@typescript-eslint/[email protected]":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==

"@typescript-eslint/[email protected]":
version "2.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5"
Expand All @@ -1849,6 +1865,27 @@
semver "^7.3.2"
tsutils "^3.17.1"

"@typescript-eslint/[email protected]":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853"
integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==
dependencies:
"@typescript-eslint/types" "3.10.1"
"@typescript-eslint/visitor-keys" "3.10.1"
debug "^4.1.1"
glob "^7.1.6"
is-glob "^4.0.1"
lodash "^4.17.15"
semver "^7.3.2"
tsutils "^3.17.1"

"@typescript-eslint/[email protected]":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931"
integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==
dependencies:
eslint-visitor-keys "^1.1.0"

"@webassemblyjs/[email protected]":
version "1.8.5"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
Expand Down Expand Up @@ -4674,6 +4711,13 @@ eslint-plugin-react@^7.20.5:
resolve "^1.18.1"
string.prototype.matchall "^4.0.2"

eslint-plugin-testing-library@^3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.1.tgz#4dd02306d601c3238fdabf1d1dbc5f2a8e85d531"
integrity sha512-nQIFe2muIFv2oR2zIuXE4vTbcFNx8hZKRzgHZqJg8rfopIWwoTwtlbCCNELT/jXzVe1uZF68ALGYoDXjLczKiQ==
dependencies:
"@typescript-eslint/experimental-utils" "^3.10.1"

eslint-scope@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
Expand Down