From bb8ceabbaffc9714e4266b4683805643b44fc1fa Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech Date: Wed, 13 Apr 2022 13:14:20 +0200 Subject: [PATCH] fix: improve selecting projects Adds tests to the form for creating API tokens. --- .../SelectAllButton/SelectAllButton.styles.ts | 1 + .../SelectAllButton/SelectAllButton.tsx | 6 +- .../SelectProjectInput.test.tsx | 105 +++++++++++++++++- .../SelectProjectInput/SelectProjectInput.tsx | 29 +++-- 4 files changed, 131 insertions(+), 10 deletions(-) diff --git a/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectAllButton/SelectAllButton.styles.ts b/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectAllButton/SelectAllButton.styles.ts index 84685e2fe5..06424e9dfe 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectAllButton/SelectAllButton.styles.ts +++ b/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectAllButton/SelectAllButton.styles.ts @@ -3,5 +3,6 @@ import { makeStyles } from '@material-ui/core/styles'; export const useStyles = makeStyles(theme => ({ selectOptionsLink: { cursor: 'pointer', + fontSize: theme.fontSizes.bodySize, }, })); diff --git a/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectAllButton/SelectAllButton.tsx b/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectAllButton/SelectAllButton.tsx index 6828f5d365..70ea7879ef 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectAllButton/SelectAllButton.tsx +++ b/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectAllButton/SelectAllButton.tsx @@ -15,7 +15,11 @@ export const SelectAllButton: FC = ({ return ( - + {isAllSelected ? 'Deselect all' : 'Select all'} diff --git a/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.test.tsx b/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.test.tsx index 39b8f65849..7c97626fcc 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.test.tsx +++ b/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { screen, within } from '@testing-library/react'; +import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { render } from 'utils/testRenderer'; import { @@ -14,6 +14,7 @@ const mockProps: ISelectProjectInputProps = { options: [ { label: 'Project1', value: 'project1' }, { label: 'Project2', value: 'project2' }, + { label: 'Project3', value: 'project3' }, ], defaultValue: ['*'], onChange, @@ -59,4 +60,106 @@ describe('SelectProjectInput', () => { expect(screen.getByLabelText('Projects')).toBeDisabled(); }); + + it('renders with autocomplete enabled if default value is not a wildcard', () => { + render( + + ); + + const checkbox = screen.getByLabelText( + /all current and future projects/i + ); + expect(checkbox).not.toBeChecked(); + + const selectInputContainer = screen.getByTestId('select-input'); + const input = within(selectInputContainer).getByRole('textbox'); + expect(input).toBeEnabled(); + }); + + describe('Select/Deselect projects in dropdown', () => { + it('selects and deselects all options', async () => { + const user = userEvent.setup(); + render(); + await user.click(screen.getByLabelText('Projects')); + + let button = screen.getByRole('button', { + name: /select all/i, + }); + expect(button).toBeInTheDocument(); + await user.click(button); + + expect(onChange).toHaveBeenCalledWith([ + 'project1', + 'project2', + 'project3', + ]); + + button = screen.getByRole('button', { + name: /deselect all/i, + }); + expect(button).toBeInTheDocument(); + await user.click(button); + expect(onChange).toHaveBeenCalledWith([]); + }); + + it("doesn't show up for less than 3 options", async () => { + const user = userEvent.setup(); + render( + + ); + await user.click(screen.getByLabelText('Projects')); + + const button = screen.queryByRole('button', { + name: /select all/i, + }); + expect(button).not.toBeInTheDocument(); + }); + }); + + it('can filter options', async () => { + const user = userEvent.setup(); + render( + + ); + const input = await screen.findByLabelText('Projects'); + user.type(input, 'alp'); + + await waitFor(() => { + expect(screen.getByText('Alpha')).toBeVisible(); + }); + await waitFor(() => { + expect(screen.queryByText('Bravo')).not.toBeInTheDocument(); + }); + await waitFor(() => { + expect(screen.queryByText('Charlie')).not.toBeInTheDocument(); + }); + await waitFor(() => { + expect(screen.getByText('Alpaca')).toBeVisible(); + }); + + user.clear(input); + user.type(input, 'bravo'); + await waitFor(() => { + expect(screen.getByText('Bravo')).toBeVisible(); + }); + await waitFor(() => { + expect(screen.queryByText('Alpha')).not.toBeInTheDocument(); + }); + }); }); diff --git a/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.tsx b/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.tsx index 54f72f0261..6b093a51c9 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.tsx +++ b/frontend/src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.tsx @@ -17,6 +17,7 @@ import CheckBoxIcon from '@material-ui/icons/CheckBox'; import { IAutocompleteBoxOption } from 'component/common/AutocompleteBox/AutocompleteBox'; import { useStyles } from '../ApiTokenForm.styles'; import { SelectAllButton } from './SelectAllButton/SelectAllButton'; +import ConditionallyRender from 'component/common/ConditionallyRender'; const ALL_PROJECTS = '*'; @@ -47,7 +48,10 @@ export const SelectProjectInput: VFC = ({ const [isWildcardSelected, selectWildcard] = useState( typeof defaultValue === 'string' || defaultValue.includes(ALL_PROJECTS) ); - const isAllSelected = projects.length === options.length; + const isAllSelected = + projects.length > 0 && + projects.length === options.length && + projects[0] !== ALL_PROJECTS; const onAllProjectsChange = ( e: ChangeEvent, @@ -62,6 +66,14 @@ export const SelectProjectInput: VFC = ({ } }; + const onSelectAllClick = () => { + const newProjects = isAllSelected + ? [] + : options.map(({ value }) => value); + setProjects(newProjects); + onChange(newProjects); + }; + const renderOption = ( option: IAutocompleteBoxOption, { selected }: AutocompleteRenderOptionState @@ -79,13 +91,14 @@ export const SelectProjectInput: VFC = ({ const renderGroup = ({ key, children }: AutocompleteRenderGroupParams) => ( - { - setProjects( - isAllSelected ? [] : options.map(({ value }) => value) - ); - }} + 2} + show={ + + } /> {children}