From 6ce8c62c217f0913559bc2e3628461add6bed544 Mon Sep 17 00:00:00 2001 From: Joe Agster Date: Thu, 24 Oct 2024 10:47:55 -0700 Subject: [PATCH 1/4] Implement operator select box --- .../OperatorSelect/OperatorSelect.spec.tsx | 61 +++++++++++++++++++ .../OperatorSelect/OperatorSelect.tsx | 26 ++++++++ .../FormInputs/OperatorSelect/index.ts | 0 .../src/options/operator/index.ts | 1 + .../src/options/operator/operators.ts | 26 ++++++++ 5 files changed, 114 insertions(+) create mode 100644 apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.spec.tsx create mode 100644 apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.tsx create mode 100644 apps/modernization-ui/src/components/FormInputs/OperatorSelect/index.ts create mode 100644 apps/modernization-ui/src/options/operator/index.ts create mode 100644 apps/modernization-ui/src/options/operator/operators.ts diff --git a/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.spec.tsx b/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.spec.tsx new file mode 100644 index 0000000000..7be599936b --- /dev/null +++ b/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.spec.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { OperatorSelect, OperatorSelectProps } from './OperatorSelect'; +import { defaultOperator, operators } from 'options/operator'; + +describe('OperatorSelect', () => { + const mockOnChange = jest.fn(); + + const defaultProps: OperatorSelectProps = { + id: 'operator-select', + value: null, + showLabel: false, + sizing: 'compact', + onChange: mockOnChange + }; + + it('renders without crashing', () => { + const { getByRole } = render(); + const selectElement = getByRole('combobox'); + expect(selectElement).toBeInTheDocument(); + }); + + it('displays the operator label when showLabel is true', () => { + const { getByLabelText } = render(); + const labelElement = getByLabelText('Operator'); + expect(labelElement).toBeInTheDocument(); + }); + + it('does not display the label when showLabel is false', () => { + const { queryByLabelText } = render(); + const labelElement = queryByLabelText('Operator'); + expect(labelElement).not.toBeInTheDocument(); + }); + + it('calls onChange when an option is selected', () => { + const { getByRole } = render(); + const selectElement = getByRole('combobox'); + fireEvent.change(selectElement, { target: { value: operators[0].value } }); + expect(mockOnChange).toHaveBeenCalledWith(operators[0]); + }); + + it('displays the EQUAL operator by default', () => { + const { getByRole } = render(); + const selectElement = getByRole('combobox') as HTMLSelectElement; + expect(selectElement.value).toBe(defaultOperator.value); + }); + + it('displays the correct initial value when specified', () => { + const { getByRole } = render(); + const selectElement = getByRole('combobox') as HTMLSelectElement; + expect(selectElement.value).toBe(operators[1].value); + }); + + it('renders all of the operator options', () => { + const { getAllByRole } = render(); + const options = getAllByRole('option'); + // all options + the placeholder + expect(options.length).toBe(operators.length + 1); + }); +}); diff --git a/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.tsx b/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.tsx new file mode 100644 index 0000000000..7ab529bbf1 --- /dev/null +++ b/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.tsx @@ -0,0 +1,26 @@ +import { Sizing } from 'components/Entry'; +import { SingleSelect } from 'design-system/select'; +import { Selectable } from 'options'; +import { operators, defaultOperator } from 'options/operator'; + +export type OperatorSelectProps = { + id: string; + value?: Selectable | null; + showLabel?: boolean; + sizing?: Sizing; + onChange: (value?: Selectable) => void; +}; + +export const OperatorSelect = ({ id, value, showLabel = false, sizing, onChange }: OperatorSelectProps) => { + return ( + + ); +}; diff --git a/apps/modernization-ui/src/components/FormInputs/OperatorSelect/index.ts b/apps/modernization-ui/src/components/FormInputs/OperatorSelect/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/modernization-ui/src/options/operator/index.ts b/apps/modernization-ui/src/options/operator/index.ts new file mode 100644 index 0000000000..4e26b60bc8 --- /dev/null +++ b/apps/modernization-ui/src/options/operator/index.ts @@ -0,0 +1 @@ +export * from './operators'; diff --git a/apps/modernization-ui/src/options/operator/operators.ts b/apps/modernization-ui/src/options/operator/operators.ts new file mode 100644 index 0000000000..3618d31cf3 --- /dev/null +++ b/apps/modernization-ui/src/options/operator/operators.ts @@ -0,0 +1,26 @@ +import { Selectable, asSelectable, findByValue } from 'options'; + +const STARTS_WITH_OPERATOR = asSelectable('S', 'Starts with'); +const CONTAINS_OPERATOR = asSelectable('C', 'Contains'); +const EQUAL_OPERATOR = asSelectable('E', 'Equal'); +const NOT_EQUAL_OPERATOR = asSelectable('N', 'Not equal'); +const SOUNDS_LIKE_OPERATOR = asSelectable('L', 'Sounds like'); + +const operators: Selectable[] = [ + STARTS_WITH_OPERATOR, + CONTAINS_OPERATOR, + EQUAL_OPERATOR, + NOT_EQUAL_OPERATOR, + SOUNDS_LIKE_OPERATOR +]; + +/** A subset of operators: equals, not equals, contains */ +const basicOperators: Selectable[] = [EQUAL_OPERATOR, NOT_EQUAL_OPERATOR, CONTAINS_OPERATOR]; + +/** The default operator to be used. Currently: EQUAL */ +const defaultOperator = EQUAL_OPERATOR; + +const asSelectableOperator = (value: string | null | undefined) => + (value && findByValue(operators, EQUAL_OPERATOR)(value)) || EQUAL_OPERATOR; + +export { operators, basicOperators, defaultOperator, asSelectableOperator }; From 2d696f79a7a0cdfb6873cb6cde89d75a94b914b6 Mon Sep 17 00:00:00 2001 From: Joe Agster Date: Thu, 24 Oct 2024 10:48:59 -0700 Subject: [PATCH 2/4] Incorporate operator select in patient search form --- .../PatientCriteria/BasicInformation.tsx | 42 ++++++++++++++++++- .../PatientCriteria/PatientCriteria.tsx | 3 +- .../search/patient/PatientCriteria/index.ts | 1 + .../src/apps/search/patient/criteria.ts | 2 + .../design-system/checkbox/CheckboxGroup.tsx | 6 +-- 5 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 apps/modernization-ui/src/apps/search/patient/PatientCriteria/index.ts diff --git a/apps/modernization-ui/src/apps/search/patient/PatientCriteria/BasicInformation.tsx b/apps/modernization-ui/src/apps/search/patient/PatientCriteria/BasicInformation.tsx index 07f087f55c..4110c217d8 100644 --- a/apps/modernization-ui/src/apps/search/patient/PatientCriteria/BasicInformation.tsx +++ b/apps/modernization-ui/src/apps/search/patient/PatientCriteria/BasicInformation.tsx @@ -8,12 +8,51 @@ import { validNameRule } from 'validation/entry'; import { Input } from 'components/FormInputs/Input'; import { DatePickerInput } from 'components/FormInputs/DatePickerInput'; import { genders } from 'options/gender'; +import { OperatorSelect } from 'components/FormInputs/OperatorSelect/OperatorSelect'; +import { EntryWrapper } from 'components/Entry'; +import { Grid } from '@trussworks/react-uswds'; export const BasicInformation = () => { const { control } = useFormContext>(); return ( + + + + + ( + + )} + /> + + + ( + + )} + /> + + + + + {/* { /> )} /> + */} { name={name} label={'Include records that are'} sizing="compact" - requried + required options={statusOptions} value={value} onChange={onChange} diff --git a/apps/modernization-ui/src/apps/search/patient/PatientCriteria/PatientCriteria.tsx b/apps/modernization-ui/src/apps/search/patient/PatientCriteria/PatientCriteria.tsx index ee32e6b4c1..9a67d3cea0 100644 --- a/apps/modernization-ui/src/apps/search/patient/PatientCriteria/PatientCriteria.tsx +++ b/apps/modernization-ui/src/apps/search/patient/PatientCriteria/PatientCriteria.tsx @@ -1,6 +1,5 @@ -import { BasicInformation } from './BasicInformation'; import { Accordion } from 'components/Accordion'; - +import { BasicInformation } from './BasicInformation'; import { Address } from './Address'; import { Contact } from './Contact'; import { RaceEthnicity } from './RaceEthnicity'; diff --git a/apps/modernization-ui/src/apps/search/patient/PatientCriteria/index.ts b/apps/modernization-ui/src/apps/search/patient/PatientCriteria/index.ts new file mode 100644 index 0000000000..37eae9ac6a --- /dev/null +++ b/apps/modernization-ui/src/apps/search/patient/PatientCriteria/index.ts @@ -0,0 +1 @@ +export { PatientCriteria } from './PatientCriteria'; diff --git a/apps/modernization-ui/src/apps/search/patient/criteria.ts b/apps/modernization-ui/src/apps/search/patient/criteria.ts index 244e5e6a20..9371085ae0 100644 --- a/apps/modernization-ui/src/apps/search/patient/criteria.ts +++ b/apps/modernization-ui/src/apps/search/patient/criteria.ts @@ -12,7 +12,9 @@ export { statusOptions }; type BasicInformation = { lastName?: string; + lastNameOperator?: Selectable; firstName?: string; + firstNameOperator?: Selectable; dateOfBirth?: string; gender?: Selectable; id?: string; diff --git a/apps/modernization-ui/src/design-system/checkbox/CheckboxGroup.tsx b/apps/modernization-ui/src/design-system/checkbox/CheckboxGroup.tsx index 45ec1607b3..4d8527124b 100644 --- a/apps/modernization-ui/src/design-system/checkbox/CheckboxGroup.tsx +++ b/apps/modernization-ui/src/design-system/checkbox/CheckboxGroup.tsx @@ -14,7 +14,7 @@ type Props = { options: Selectable[]; value?: Selectable[]; error?: string; - requried?: boolean; + required?: boolean; sizing?: Sizing; onChange?: (selected: Selectable[]) => void; onBlur?: (event: ReactFocusEvent) => void; @@ -30,7 +30,7 @@ export const CheckboxGroup = ({ disabled = false, className, error, - requried, + required, sizing }: Props) => { const { items, selected, select, deselect, reset } = useMultiSelection({ available: options }); @@ -56,7 +56,7 @@ export const CheckboxGroup = ({ return (
{label} From 84887fe5746c1d6c1ebcff78cb52456dfa041c4f Mon Sep 17 00:00:00 2001 From: Joe Agster Date: Thu, 24 Oct 2024 10:52:24 -0700 Subject: [PATCH 3/4] Comment out operator select until ready --- .../patient/PatientCriteria/BasicInformation.tsx | 13 ++++++++----- .../components/FormInputs/OperatorSelect/index.ts | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/modernization-ui/src/apps/search/patient/PatientCriteria/BasicInformation.tsx b/apps/modernization-ui/src/apps/search/patient/PatientCriteria/BasicInformation.tsx index 4110c217d8..0edf21ca5e 100644 --- a/apps/modernization-ui/src/apps/search/patient/PatientCriteria/BasicInformation.tsx +++ b/apps/modernization-ui/src/apps/search/patient/PatientCriteria/BasicInformation.tsx @@ -8,15 +8,19 @@ import { validNameRule } from 'validation/entry'; import { Input } from 'components/FormInputs/Input'; import { DatePickerInput } from 'components/FormInputs/DatePickerInput'; import { genders } from 'options/gender'; -import { OperatorSelect } from 'components/FormInputs/OperatorSelect/OperatorSelect'; -import { EntryWrapper } from 'components/Entry'; -import { Grid } from '@trussworks/react-uswds'; +// import { OperatorSelect } from 'components/FormInputs/OperatorSelect'; +// import { EntryWrapper } from 'components/Entry'; +// import { Grid } from '@trussworks/react-uswds'; export const BasicInformation = () => { const { control } = useFormContext>(); return ( + {/* + + // WHEN READY: Uncomment this section to add the operator select for last name, the copy for first name and any others + @@ -52,7 +56,7 @@ export const BasicInformation = () => { - {/* + */} { /> )} /> - */} Date: Thu, 24 Oct 2024 11:22:14 -0700 Subject: [PATCH 4/4] Support for basic operators --- .../FormInputs/OperatorSelect/OperatorSelect.spec.tsx | 11 +++++++++-- .../FormInputs/OperatorSelect/OperatorSelect.tsx | 7 ++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.spec.tsx b/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.spec.tsx index 7be599936b..08afe708e7 100644 --- a/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.spec.tsx +++ b/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.spec.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { OperatorSelect, OperatorSelectProps } from './OperatorSelect'; -import { defaultOperator, operators } from 'options/operator'; +import { basicOperators, defaultOperator, operators } from 'options/operator'; describe('OperatorSelect', () => { const mockOnChange = jest.fn(); @@ -52,10 +52,17 @@ describe('OperatorSelect', () => { expect(selectElement.value).toBe(operators[1].value); }); - it('renders all of the operator options', () => { + it('renders all of the operator options when mode is not specified', () => { const { getAllByRole } = render(); const options = getAllByRole('option'); // all options + the placeholder expect(options.length).toBe(operators.length + 1); }); + + it('renders only the basic operator options when mode is basic', () => { + const { getAllByRole } = render(); + const options = getAllByRole('option'); + // basic options + the placeholder + expect(options.length).toBe(basicOperators.length + 1); + }); }); diff --git a/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.tsx b/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.tsx index 7ab529bbf1..332eeec2dd 100644 --- a/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.tsx +++ b/apps/modernization-ui/src/components/FormInputs/OperatorSelect/OperatorSelect.tsx @@ -1,17 +1,18 @@ import { Sizing } from 'components/Entry'; import { SingleSelect } from 'design-system/select'; import { Selectable } from 'options'; -import { operators, defaultOperator } from 'options/operator'; +import { operators, defaultOperator, basicOperators } from 'options/operator'; export type OperatorSelectProps = { id: string; value?: Selectable | null; + mode?: 'basic' | 'all'; showLabel?: boolean; sizing?: Sizing; onChange: (value?: Selectable) => void; }; -export const OperatorSelect = ({ id, value, showLabel = false, sizing, onChange }: OperatorSelectProps) => { +export const OperatorSelect = ({ id, value, mode, showLabel = false, sizing, onChange }: OperatorSelectProps) => { return ( );