Skip to content

Commit

Permalink
Bob/7436 syphillis card support pt 2 (#7651)
Browse files Browse the repository at this point in the history
* refactoring and setup

* compile

* add a test

* lint and snapshots

* add compare func

* try to be a better react dev

* lint and snapshots

* sonar suggestions

* remove unnecessary prop

* move column logic to the COVID form

* snapshots

* undo covid aoe form changes

* one more

* actually

* revert snaps

* test name

* one more test name
  • Loading branch information
fzhao99 authored May 8, 2024
1 parent dc7d2c5 commit 34d9e9a
Show file tree
Hide file tree
Showing 13 changed files with 827 additions and 695 deletions.
12 changes: 12 additions & 0 deletions frontend/src/app/commonComponents/Checkboxes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { generateCheckboxColumns } from "./Checkboxes";

describe("generateCheckboxColumns", () => {
it("creates array of arrays, subarrays of length n, when passed an array of items and n", () => {
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9];
expect(generateCheckboxColumns(testArray, 3)).toEqual([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]);
});
});
116 changes: 82 additions & 34 deletions frontend/src/app/commonComponents/Checkboxes.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from "react";
import classnames from "classnames";
import { UIDConsumer } from "react-uid";

import Required from "../commonComponents/Required";
import Optional from "../commonComponents/Optional";
Expand All @@ -13,21 +12,43 @@ export type CheckboxProps = {
};
type InputProps = JSX.IntrinsicElements["input"];
type Checkbox = CheckboxProps & InputProps;
interface Props {
boxes: Checkbox[];

type Props = FragmentProps & {
legend: React.ReactNode;
legendSrOnly?: boolean;
hintText?: string;
name: string;
disabled?: boolean;
className?: string;
errorMessage?: string;
validationStatus?: "error" | "success";
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
required?: boolean;
inputRef?: React.RefObject<HTMLInputElement>;
numColumnsToDisplay?: number;
};

// Take the list of checkboxes and generate an array (ie columns) of checkbox
// arrays to be displayed on the final card
export function generateCheckboxColumns<T>(
items: T[],
numColumnsToDisplay: number
): T[][] {
const subarrayLength = Math.ceil(items.length / numColumnsToDisplay);
return generateSubarraysFromItemList(items, subarrayLength);
}

function generateSubarraysFromItemList<T>(items: T[], subarrayLength: number) {
const arrayOfArrays: T[][] = [];
let curSubarray: T[] = [];
items.forEach((item) => {
curSubarray.push(item);
if (curSubarray.length === subarrayLength) {
arrayOfArrays.push(curSubarray);
curSubarray = [];
}
});
if (curSubarray.length > 0) arrayOfArrays.push(curSubarray);
return arrayOfArrays;
}

const DEFAULT_COLUMN_DISPLAY_NUMBER = 1;
const Checkboxes = (props: Props) => {
const {
boxes,
Expand All @@ -40,8 +61,29 @@ const Checkboxes = (props: Props) => {
required,
inputRef,
hintText,
numColumnsToDisplay = DEFAULT_COLUMN_DISPLAY_NUMBER,
} = props;

const checkboxFragmentToRender = (boxes: Checkbox[]) => (
<CheckboxesFragment
boxes={boxes}
name={name}
onChange={onChange}
inputRef={inputRef}
/>
);

const checkboxColumns: Checkbox[][] = generateCheckboxColumns(
boxes,
numColumnsToDisplay
);

const checkboxesToDisplay = checkboxColumns.map((boxes) => (
<div className="tablet:grid-col" key={boxes.map((b) => b.value).join()}>
{checkboxFragmentToRender(boxes)}
</div>
));

return (
<div
className={classnames(
Expand All @@ -68,36 +110,42 @@ const Checkboxes = (props: Props) => {
{errorMessage}
</div>
)}
<UIDConsumer>
{(_, uid) => (
<div className="checkboxes">
{boxes.map(
({ value, label, disabled, checked, ...inputProps }, i) => (
<div className="usa-checkbox" key={uid(i)}>
<input
className="usa-checkbox__input"
checked={checked}
id={uid(i)}
onChange={onChange}
type="checkbox"
value={value}
name={name}
ref={inputRef}
disabled={disabled || props.disabled}
{...inputProps}
/>
<label className="usa-checkbox__label" htmlFor={uid(i)}>
{label}
</label>
</div>
)
)}
</div>
)}
</UIDConsumer>
<div className="grid-row checkboxes">{checkboxesToDisplay}</div>
</fieldset>
</div>
);
};

type FragmentProps = {
boxes: Checkbox[];
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
inputRef?: React.RefObject<HTMLInputElement>;
name: string;
disabled?: boolean;
};

const CheckboxesFragment = (props: FragmentProps) => {
const { boxes, name, inputRef, onChange } = props;

return boxes.map(({ value, label, disabled, checked, ...inputProps }) => (
<div className="usa-checkbox" key={value}>
<input
className="usa-checkbox__input"
checked={checked}
id={`symptom-${value}`}
onChange={onChange}
type="checkbox"
value={value}
name={name}
ref={inputRef}
disabled={disabled || props.disabled}
{...inputProps}
/>
<label className="usa-checkbox__label" htmlFor={`symptom-${value}`}>
{label}
</label>
</div>
));
};

export default Checkboxes;
Original file line number Diff line number Diff line change
Expand Up @@ -72,34 +72,38 @@ Object {
class="usa-legend"
/>
<div
class="checkboxes"
class="grid-row checkboxes"
>
<div
class="usa-checkbox"
class="tablet:grid-col"
>
<input
aria-label="acknowledged"
class="usa-checkbox__input"
id="1-val-0"
name="acknowledge"
type="checkbox"
value="acknowledged"
/>
<label
class="usa-checkbox__label"
for="1-val-0"
<div
class="usa-checkbox"
>
My organization is submitting test results to a
<a
href="https://simplereport.gov/using-simplereport/manage-facility-info/find-supported-jurisdictions"
rel="noopener noreferrer"
target="_blank"
<input
aria-label="acknowledged"
class="usa-checkbox__input"
id="symptom-acknowledged"
name="acknowledge"
type="checkbox"
value="acknowledged"
/>
<label
class="usa-checkbox__label"
for="symptom-acknowledged"
>
state that is connected to SimpleReport
</a>
.
</label>
My organization is submitting test results to a
<a
href="https://simplereport.gov/using-simplereport/manage-facility-info/find-supported-jurisdictions"
rel="noopener noreferrer"
target="_blank"
>
state that is connected to SimpleReport
</a>
.
</label>
</div>
</div>
</div>
</fieldset>
Expand Down
13 changes: 10 additions & 3 deletions frontend/src/app/testQueue/TestCard/TestCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ jest.mock("../../TelemetryService", () => ({
getAppInsights: jest.fn(),
}));

const mockDiseaseEnabledFlag = (diseaseName: string) =>
jest
.spyOn(flaggedMock, "useFeature")
.mockImplementation((flagName: string) => {
return flagName === `${diseaseName.toLowerCase()}Enabled`;
});

const mockNavigate = jest.fn();
jest.mock("react-router-dom", () => {
const original = jest.requireActual("react-router-dom");
Expand Down Expand Up @@ -1396,7 +1403,7 @@ describe("TestCard", () => {
});

it("shows radio buttons for HIV when an HIV device is chosen", async function () {
jest.spyOn(flaggedMock, "useFeature").mockReturnValue(true);
mockDiseaseEnabledFlag("HIV");

const mocks = [
{
Expand Down Expand Up @@ -1443,7 +1450,7 @@ describe("TestCard", () => {
});

it("shows required HIV AOE questions when a positive HIV result is present", async function () {
jest.spyOn(flaggedMock, "useFeature").mockReturnValue(true);
mockDiseaseEnabledFlag("HIV");

const mocks = [
{
Expand Down Expand Up @@ -1500,7 +1507,7 @@ describe("TestCard", () => {
});

it("hides AOE questions when there is no positive HIV result", async function () {
jest.spyOn(flaggedMock, "useFeature").mockReturnValue(true);
mockDiseaseEnabledFlag("HIV");

const mocks = [
{
Expand Down
17 changes: 10 additions & 7 deletions frontend/src/app/testQueue/TestCardForm/TestCardForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import TextInput from "../../commonComponents/TextInput";
import { formatDate } from "../../utils/date";
import { TextWithTooltip } from "../../commonComponents/TextWithTooltip";
import Dropdown from "../../commonComponents/Dropdown";
import { TEST_RESULTS } from "../../testResults/constants";
import { MULTIPLEX_DISEASES, TEST_RESULTS } from "../../testResults/constants";
import {
MultiplexResultInput,
useEditQueueItemMutation,
Expand All @@ -35,7 +35,7 @@ import {
TestFormState,
} from "./TestCardFormReducer";
import CovidAoEForm, {
parseSymptoms,
parseRespiratorySymptoms,
} from "./diseaseSpecificComponents/CovidAoEForm";
import {
AOEFormOption,
Expand All @@ -50,9 +50,9 @@ import {
useTestOrderPatient,
} from "./TestCardForm.utils";
import { TestResultInputGroup } from "./diseaseSpecificComponents/TestResultInputGroup";
import { HIVAoEForm } from "./diseaseSpecificComponents/HIVAoEForm";
import { DevicesMap, QueriedFacility, QueriedTestOrder } from "./types";
import { IncompleteAOEWarningModal } from "./IncompleteAOEWarningModal";
import { HIVAoEForm } from "./diseaseSpecificComponents/HIVAoEForm";

const DEBOUNCE_TIME = 300;

Expand Down Expand Up @@ -84,7 +84,7 @@ const TestCardForm = ({
pregnancy: testOrder.pregnancy as PregnancyCode,
noSymptoms: testOrder.noSymptoms,
symptomOnset: testOrder.symptomOnset,
symptoms: JSON.stringify(parseSymptoms(testOrder.symptoms)),
symptoms: JSON.stringify(parseRespiratorySymptoms(testOrder.symptoms)),
genderOfSexualPartners: testOrder.genderOfSexualPartners,
},
};
Expand Down Expand Up @@ -116,12 +116,13 @@ const TestCardForm = ({
const { patientFullName } = useTestOrderPatient(testOrder);

const whichAOEFormOption = useAOEFormOption(state.deviceId, devicesMap);

// AOE responses required for HIV positive result
const hivAOEResponsesRequired =
whichAOEFormOption === AOEFormOption.HIV &&
state.testResults.some(
(x) => x.diseaseName === "HIV" && x.testResult === TEST_RESULTS.POSITIVE
(x) =>
x.diseaseName === MULTIPLEX_DISEASES.HIV &&
x.testResult === TEST_RESULTS.POSITIVE
);

/**
Expand Down Expand Up @@ -193,7 +194,9 @@ const TestCardForm = ({
patientId: testOrder.patient.internalId,
noSymptoms: state.aoeResponses.noSymptoms,
// automatically converts boolean strings like "false" to false
symptoms: JSON.stringify(parseSymptoms(state.aoeResponses.symptoms)),
symptoms: JSON.stringify(
parseRespiratorySymptoms(state.aoeResponses.symptoms)
),
symptomOnset: state.aoeResponses.symptomOnset
? state.aoeResponses.symptomOnset
: null,
Expand Down
15 changes: 9 additions & 6 deletions frontend/src/app/testQueue/TestCardForm/TestCardForm.utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { showError, showSuccess } from "../../utils/srToast";

import { TestFormState } from "./TestCardFormReducer";
import { parseSymptoms } from "./diseaseSpecificComponents/CovidAoEForm";
import { parseRespiratorySymptoms } from "./diseaseSpecificComponents/CovidAoEForm";
import {
DevicesMap,
QueriedDeviceType,
Expand Down Expand Up @@ -203,8 +203,7 @@ export const useAOEFormOption = (deviceId: string, devicesMap: DevicesMap) => {
devicesMap
.get(deviceId)
?.supportedDiseaseTestPerformed.filter(
(x) =>
x.supportedDisease.name.toUpperCase() === MULTIPLEX_DISEASES.SYPHILIS
(x) => x.supportedDisease.name === MULTIPLEX_DISEASES.SYPHILIS
).length === 1
) {
return AOEFormOption.SYPHILIS;
Expand Down Expand Up @@ -236,9 +235,11 @@ export const areAOEAnswersComplete = (
const isPregnancyAnswered = !!formState.aoeResponses.pregnancy;
const hasNoSymptoms = formState.aoeResponses.noSymptoms;
if (formState.aoeResponses.noSymptoms === false) {
const symptoms = parseSymptoms(formState.aoeResponses.symptoms);
const symptoms = parseRespiratorySymptoms(
formState.aoeResponses.symptoms
);
const areSymptomsFilledIn = Object.values(symptoms).some((x) =>
x.valueOf()
x?.valueOf()
);
const isSymptomOnsetDateAnswered = !!formState.aoeResponses.symptomOnset;
return (
Expand All @@ -251,7 +252,9 @@ export const areAOEAnswersComplete = (
return isPregnancyAnswered && hasNoSymptoms;
}
const hasPositiveHIVResult = formState.testResults.some(
(x) => x.diseaseName === "HIV" && x.testResult === TEST_RESULTS.POSITIVE
(x) =>
x.diseaseName === MULTIPLEX_DISEASES.HIV &&
x.testResult === TEST_RESULTS.POSITIVE
);
if (whichAOE === AOEFormOption.HIV && hasPositiveHIVResult) {
const isPregnancyAnswered = !!formState.aoeResponses.pregnancy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { MultiplexResultInput } from "../../../generated/graphql";

import { convertFromMultiplexResponse } from "./TestCardForm.utils";
import { parseSymptoms } from "./diseaseSpecificComponents/CovidAoEForm";
import { parseRespiratorySymptoms } from "./diseaseSpecificComponents/CovidAoEForm";
import { DevicesMap, QueriedTestOrder } from "./types";

export interface TestFormState {
Expand Down Expand Up @@ -175,7 +175,7 @@ export const testCardFormReducer = (
}
const aoeAnswers = {
noSymptoms: payload.noSymptoms,
symptoms: JSON.stringify(parseSymptoms(payload.symptoms)),
symptoms: JSON.stringify(parseRespiratorySymptoms(payload.symptoms)),
symptomOnset: payload.symptomOnset,
pregnancy: payload.pregnancy,
syphilisHistory: payload.syphilisHistory,
Expand Down
Loading

0 comments on commit 34d9e9a

Please sign in to comment.