Skip to content

Commit

Permalink
aoe validation abstractions pass two
Browse files Browse the repository at this point in the history
  • Loading branch information
fzhao99 committed May 16, 2024
1 parent 24b2213 commit 68e4ca1
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 89 deletions.
69 changes: 39 additions & 30 deletions frontend/src/app/testQueue/TestCardForm/TestCardForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
import CovidAoEForm from "./diseaseSpecificComponents/CovidAoEForm";
import {
AOEFormOption,
areAOEAnswersComplete,
generateAoeValidationState,
convertFromMultiplexResponse,
showTestResultDeliveryStatusAlert,
useAOEFormOption,
Expand All @@ -49,6 +49,7 @@ import {
useFilteredDeviceTypes,
useSpecimenTypeOptions,
useTestOrderPatient,
AoeValidationErrorMessages,
} from "./TestCardForm.utils";
import { TestResultInputGroup } from "./diseaseSpecificComponents/TestResultInputGroup";
import { DevicesMap, QueriedFacility, QueriedTestOrder } from "./types";
Expand Down Expand Up @@ -271,37 +272,45 @@ const TestCardForm = ({
};

const whichAOEFormOption = useAOEFormOption(state.deviceId, devicesMap);
const someAoeResponseRequired =
const whichAoeFormRequiredForPositives =
state.testResults.some((x) => x.testResult === TEST_RESULTS.POSITIVE) &&
whichAOEFormOption !== AOEFormOption.COVID;

const whichAoeFormRequired = someAoeResponseRequired
? whichAOEFormOption
: null;
const validateAoeQuestions = () => {
if (whichAOEFormOption === AOEFormOption.COVID) {
const {
nonSymptomQuestionsComplete,
hasSymptomsQuestionAnswered,
symptomAoeQuestionsComplete,
} = areAOEAnswersComplete(state, whichAOEFormOption, true);

if (hasSymptomsQuestionAnswered && !symptomAoeQuestionsComplete) {
const errorMessage =
"Please complete symptom details or indicate patient doesn't have symptoms";
showError(errorMessage, "Invalid test questionnaire");
return errorMessage;
} else if (!nonSymptomQuestionsComplete || !hasSymptomsQuestionAnswered) {
whichAOEFormOption !== AOEFormOption.COVID
? whichAOEFormOption
: null;
const areAoeQuestionsComplete = () => {
const aoeValidationMessage = generateAoeValidationState(
state,
whichAOEFormOption
);
if (aoeValidationMessage === AoeValidationErrorMessages.COMPLETE)
return true;
if (aoeValidationMessage === AoeValidationErrorMessages.INCOMPLETE) {
if (whichAOEFormOption === AOEFormOption.COVID) {
setIsSubmitModalOpen(true);
return "Please verify submission";
return false;
} else {
showError(
"Please complete the required questions",
"Invalid test questionnaire"
);
return false;
}
} else if (
someAoeResponseRequired &&
!areAOEAnswersComplete(state, whichAOEFormOption)
}
if (
aoeValidationMessage ===
AoeValidationErrorMessages.SYMPTOM_VALIDATION_ERROR
) {
return "Please answer all required questions.";
showError(
"Please complete symptom details or indicate patient doesn't have symptoms",
"Invalid test questionnaire"
);
return false;
}
return null;
showError(
"Something unexpected went wrong. Please try again or contact support@simplereport.gov for help",
"Invalid test questionnaire"
);
return false;
};

const dateTestedError = getDateTestedError();
Expand Down Expand Up @@ -343,7 +352,7 @@ const TestCardForm = ({
setHasAttemptedSubmit(true);
const isValidForm = validateForm();
if (!isValidForm) return;
if (validateAoeQuestions() !== null && !forceSubmit) return;
if (!areAoeQuestionsComplete() && !forceSubmit) return;

trackSubmitTestResult();
setIsSubmitModalOpen(false);
Expand Down Expand Up @@ -567,7 +576,7 @@ const TestCardForm = ({
/>
</div>
)}
{whichAoeFormRequired === AOEFormOption.HIV && (
{whichAoeFormRequiredForPositives === AOEFormOption.HIV && (
<div className="grid-row grid-gap">
<HIVAoEForm
testOrder={testOrder}
Expand All @@ -582,7 +591,7 @@ const TestCardForm = ({
/>
</div>
)}
{whichAoeFormRequired === AOEFormOption.SYPHILIS && (
{whichAoeFormRequiredForPositives === AOEFormOption.SYPHILIS && (
<div className="grid-row grid-gap">
<SyphilisAoEForm
testOrder={testOrder}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import { TestFormState } from "./TestCardFormReducer";
import { AOEFormOption, areAOEAnswersComplete } from "./TestCardForm.utils";
import {
AOEFormOption,
generateAoeValidationState,
} from "./TestCardForm.utils";
import { devicesMap } from "./testUtils/testConstants";

describe("TestCardForm.utils", () => {
Expand Down Expand Up @@ -35,11 +37,14 @@ describe("TestCardForm.utils", () => {

it("should correctly evaluate whether COVID AOE answers are complete", () => {
expect(
areAOEAnswersComplete(incompleteState, AOEFormOption.COVID)
generateAoeValidationState(incompleteState, AOEFormOption.COVID)
).toEqual(false);

expect(
areAOEAnswersComplete(completeStateWithSymptoms, AOEFormOption.COVID)
generateAoeValidationState(
completeStateWithSymptoms,
AOEFormOption.COVID
)
).toEqual(true);

const completeStateNoSymptoms: TestFormState = {
Expand All @@ -54,7 +59,7 @@ describe("TestCardForm.utils", () => {
};

expect(
areAOEAnswersComplete(completeStateNoSymptoms, AOEFormOption.COVID)
generateAoeValidationState(completeStateNoSymptoms, AOEFormOption.COVID)
).toEqual(true);

const partialIncompleteState: TestFormState = {
Expand All @@ -69,7 +74,7 @@ describe("TestCardForm.utils", () => {
};

expect(
areAOEAnswersComplete(partialIncompleteState, AOEFormOption.COVID)
generateAoeValidationState(partialIncompleteState, AOEFormOption.COVID)
).toEqual(false);

const incompleteStateHasSymptomsButNoSymptomMap: TestFormState = {
Expand All @@ -84,7 +89,7 @@ describe("TestCardForm.utils", () => {
};

expect(
areAOEAnswersComplete(
generateAoeValidationState(
incompleteStateHasSymptomsButNoSymptomMap,
AOEFormOption.COVID
)
Expand All @@ -96,7 +101,7 @@ describe("TestCardForm.utils", () => {
...incompleteState,
};
expect(
areAOEAnswersComplete(positiveHIVState, AOEFormOption.HIV)
generateAoeValidationState(positiveHIVState, AOEFormOption.HIV)
).toEqual(false);
});

Expand All @@ -105,7 +110,10 @@ describe("TestCardForm.utils", () => {
...completeStateWithSymptoms,
};
expect(
areAOEAnswersComplete(positiveHIVStateCompleteAOE, AOEFormOption.HIV)
generateAoeValidationState(
positiveHIVStateCompleteAOE,
AOEFormOption.HIV
)
).toEqual(true);
});
});
Expand Down
138 changes: 88 additions & 50 deletions frontend/src/app/testQueue/TestCardForm/TestCardForm.utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,88 +227,126 @@ export const convertFromMultiplexResponse = (
});
};

export const AoeValidationErrorMessages = {
COMPLETE: "COMPLETE",
SYMPTOM_VALIDATION_ERROR: "SYMPTOM_VALIDATION_ERROR",
INCOMPLETE: "INCOMPLETE",
UNKNOWN: "UNKNOWN",
} as const;

const REQUIRED_AOE_QUESTIONS_BY_DISEASE: {
[key in AOEFormOption]: Array<keyof AoeQuestionResponses>;
} = {
// AOE responses for COVID not required, but include in completion validation
// to show "are you sure" modal if not filled
[AOEFormOption.COVID]: ["pregnancy"],
[AOEFormOption.COVID]: ["pregnancy", "noSymptoms"],
[AOEFormOption.HIV]: ["pregnancy", "genderOfSexualPartners"],
[AOEFormOption.SYPHILIS]: [
"pregnancy",
"syphilisHistory",
"genderOfSexualPartners",
"symptoms",
"noSymptoms",
],
[AOEFormOption.NONE]: [],
};

export function areAOEAnswersComplete<B extends boolean = true>(
export function generateAoeValidationState(
formState: TestFormState,
whichAOE: AOEFormOption,
aoeValidationVerbose?: B
): B extends true
? {
nonSymptomQuestionsComplete: boolean;
hasSymptomsQuestionAnswered: boolean;
symptomAoeQuestionsComplete: boolean;
}
: boolean;
export function areAOEAnswersComplete(
whichAOE: AOEFormOption
): keyof typeof AoeValidationErrorMessages {
const {
allNonSymptomAoeQuestionsAnswered,
hasSymptomsQuestionAnswered,
symptomOnsetAndSelectionQuestionsAnswered,
} = validateAoeQuestions(formState, whichAOE);

switch (true) {
case !allNonSymptomAoeQuestionsAnswered || !hasSymptomsQuestionAnswered:
return AoeValidationErrorMessages.INCOMPLETE;
case allNonSymptomAoeQuestionsAnswered &&
hasSymptomsQuestionAnswered &&
symptomOnsetAndSelectionQuestionsAnswered:
return AoeValidationErrorMessages.COMPLETE;
case allNonSymptomAoeQuestionsAnswered &&
hasSymptomsQuestionAnswered &&
!symptomOnsetAndSelectionQuestionsAnswered:
return AoeValidationErrorMessages.SYMPTOM_VALIDATION_ERROR;
default:
return AoeValidationErrorMessages.UNKNOWN;
}
}

export function validateAoeQuestions(
formState: TestFormState,
whichAOE: AOEFormOption,
aoeValidationVerbose?: boolean
):
| {
nonSymptomQuestionsComplete: boolean;
symptomAoeQuestionsComplete: boolean;
hasSymptomsQuestionAnswered: boolean;
}
| boolean {
whichAOE: AOEFormOption
) {
const aoeQuestionsToCheck = REQUIRED_AOE_QUESTIONS_BY_DISEASE[whichAOE];
const allNonSymptomAoeQuestionsAnswered = aoeQuestionsToCheck.every(
(aoeQuestionKey) => {
const aoeResponse = formState.aoeResponses[aoeQuestionKey];
if (Array.isArray(aoeResponse)) return aoeResponse.length > 0;
return Boolean(aoeResponse);
const nonSymptomAoeStatusMap: {
[key in keyof AoeQuestionResponses]: boolean;
} = {};
const symptomAoeStatusMap: {
[key in keyof AoeQuestionResponses]: boolean;
} = {};
aoeQuestionsToCheck.forEach((aoeQuestionKey) => {
if (aoeQuestionKey === "noSymptoms") {
const {
hasSymptomsQuestionAnswered,
symptomOnsetAndSelectionQuestionsAnswered,
} = areSymptomAoeQuestionsAnswered(formState, whichAOE);
symptomAoeStatusMap["noSymptoms"] = hasSymptomsQuestionAnswered;
symptomAoeStatusMap["symptoms"] =
symptomOnsetAndSelectionQuestionsAnswered;
symptomAoeStatusMap["symptomOnset"] =
symptomOnsetAndSelectionQuestionsAnswered;
} else {
nonSymptomAoeStatusMap[aoeQuestionKey] =
areNonSymptomAoeQuestionsAnswered(aoeQuestionKey, formState);
}
);
});
const allNonSymptomAoeQuestionsAnswered = Object.values(
nonSymptomAoeStatusMap
).every(Boolean);

const { symptomAoeQuestionsAnswered, hasSymptomsQuestionAnswered } =
areSymptomAoeQuestionsAnswered(formState, whichAOE);
// if undefined, symptoms questions are not required for that disease. Coerce the status of those questions to true
const hasSymptomsQuestionAnswered = symptomAoeStatusMap["noSymptoms"] ?? true;
const symptomOnsetAndSelectionQuestionsAnswered =
symptomAoeStatusMap["symptomOnset"] ??
symptomAoeStatusMap["symptoms"] ??
true;

if (aoeValidationVerbose) {
return {
nonSymptomQuestionsComplete: allNonSymptomAoeQuestionsAnswered,
symptomAoeQuestionsComplete: symptomAoeQuestionsAnswered,
hasSymptomsQuestionAnswered: hasSymptomsQuestionAnswered,
};
} else {
return (
allNonSymptomAoeQuestionsAnswered &&
hasSymptomsQuestionAnswered &&
symptomAoeQuestionsAnswered
);
}
return {
allNonSymptomAoeQuestionsAnswered,
hasSymptomsQuestionAnswered,
symptomOnsetAndSelectionQuestionsAnswered,
};
}

export function areNonSymptomAoeQuestionsAnswered(
aoeQuestionKey: keyof AoeQuestionResponses,
formState: TestFormState
) {
const aoeResponse = formState.aoeResponses[aoeQuestionKey];
if (Array.isArray(aoeResponse)) return aoeResponse.length > 0;
return Boolean(aoeResponse);
}

export const areSymptomAoeQuestionsAnswered = (
export function areSymptomAoeQuestionsAnswered(
formState: TestFormState,
whichAOE: AOEFormOption
): {
hasSymptomsQuestionAnswered: boolean;
symptomAoeQuestionsAnswered: boolean;
} => {
symptomOnsetAndSelectionQuestionsAnswered: boolean;
} {
if (formState.aoeResponses.noSymptoms == null) {
return {
hasSymptomsQuestionAnswered: false,
symptomAoeQuestionsAnswered: false,
symptomOnsetAndSelectionQuestionsAnswered: false,
};
}
if (formState.aoeResponses.noSymptoms) {
return {
hasSymptomsQuestionAnswered: true,
symptomAoeQuestionsAnswered: true,
symptomOnsetAndSelectionQuestionsAnswered: true,
};
}

Expand All @@ -320,10 +358,10 @@ export const areSymptomAoeQuestionsAnswered = (
const isSymptomOnsetDateAnswered = !!formState.aoeResponses.symptomOnset;
return {
hasSymptomsQuestionAnswered: true,
symptomAoeQuestionsAnswered:
symptomOnsetAndSelectionQuestionsAnswered:
areSymptomsFilledIn && isSymptomOnsetDateAnswered,
};
};
}

export const showTestResultDeliveryStatusAlert = (
deliverySuccess: boolean | null | undefined,
Expand Down

0 comments on commit 68e4ca1

Please sign in to comment.