From b00277550ed9c1bf7e1fd79395966d6d8feca4ca Mon Sep 17 00:00:00 2001 From: Stef Piatek Date: Tue, 15 Nov 2022 12:13:28 +0000 Subject: [PATCH 01/12] Add overall interpretation to report resource --- src/fhir/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fhir/api.ts b/src/fhir/api.ts index dfa2bc9..88d2fb6 100644 --- a/src/fhir/api.ts +++ b/src/fhir/api.ts @@ -86,7 +86,7 @@ export const createBundle = (form: FormValues, reportedGenes: RequiredCoding[]): reporter.identifier, authoriser.identifier, org.identifier, - variants.map((variant) => variant.identifier), + [overallInterpretation.identifier, ...variants.map((variant) => variant.identifier)], reportIdentifier, ); return { From 4ea4080c4215240ffec8d91995af4080f0c1e6b8 Mon Sep 17 00:00:00 2001 From: Stef Piatek Date: Tue, 15 Nov 2022 12:36:33 +0000 Subject: [PATCH 02/12] Add overall interpretation into results table --- .../results-list/ResultsDataFetcher.tsx | 47 ++++++++++++------- src/components/results-list/ResultsList.tsx | 7 +-- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/components/results-list/ResultsDataFetcher.tsx b/src/components/results-list/ResultsDataFetcher.tsx index ad39f65..5c6c51d 100644 --- a/src/components/results-list/ResultsDataFetcher.tsx +++ b/src/components/results-list/ResultsDataFetcher.tsx @@ -1,12 +1,11 @@ -import { FC, useEffect, useState, useContext } from "react"; +import { FC, useContext, useEffect, useState } from "react"; import { FhirContext } from "../fhir/FhirContext"; import LoadingSpinner from "../UI/LoadingSpinner"; -import ModalWrapper from "../UI/ModalWrapper"; -import { ModalState } from "../UI/ModalWrapper"; +import ModalWrapper, { ModalState } from "../UI/ModalWrapper"; import ResultsList from "../results-list/ResultsList"; -import { Patient, Observation } from "@smile-cdr/fhirts/dist/FHIR-R4/classes/models-r4"; +import { Observation, Patient } from "@smile-cdr/fhirts/dist/FHIR-R4/classes/models-r4"; export type TrimmedObservation = { cDnaChange: string; observationId: string }; type PatientResult = { @@ -14,11 +13,27 @@ type PatientResult = { officialPatientIdentifier: string; firstName: string; lastName: string; + overallInterpretation: string; observations: TrimmedObservation[]; }; export type ParsedResultsModel = PatientResult[]; const HGVS_CDNA_LOINC = "48004-6"; +const FH_CODE = "R134"; +const POSITIVE_FINDING = "LA6576-8"; +const VARIANT_PROFILE = "http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant"; +const INTERPRETATION_PROFILE = "http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/overall-interpretation"; + +function filterObservations(patientId: string, VARIANT_PROFILE: string, observations: Observation[]) { + return observations.filter((observation) => { + if (!observation.subject?.reference || !observation.meta?.profile) { + return; + } + const subjectIdLong = observation.subject.reference; + const profiles = observation.meta.profile; + return subjectIdLong.includes(patientId) && profiles.includes(VARIANT_PROFILE); + }); +} const ResultsDataFetcher: FC = () => { const ctx = useContext(FhirContext); @@ -27,7 +42,7 @@ const ResultsDataFetcher: FC = () => { const [parsedResults, setParsedResults] = useState(null); useEffect(() => { - const observationQueryUrl = `Observation?_profile=http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant&_include=Observation:subject`; + const observationQueryUrl = `DiagnosticReport/?_include=DiagnosticReport:result&_include=DiagnosticReport:subject`; setIsLoading(true); @@ -67,9 +82,7 @@ const ResultsDataFetcher: FC = () => { }) .map((entry) => entry.resource as Observation); - const readableResults = createReadableResults(patients, observations); - - return readableResults; + return createReadableResults(patients, observations); }; /** @@ -91,22 +104,17 @@ const ResultsDataFetcher: FC = () => { const firstName = patient.name[0].given[0]; const lastName = patient.name[0].family; - // return observations belonging to a patient based on the patient ID - const patientObservations = observations.filter((observation) => { - if (!observation.subject?.reference) return; - - const subjectIdLong = observation.subject.reference; + // return variants belonging to a patient based on the patient ID + const patientVariants = filterObservations(patientId, VARIANT_PROFILE, observations); + const overallInterpretation = filterObservations(patientId, INTERPRETATION_PROFILE, observations); - return subjectIdLong.includes(patientId); - }); - - if (!patientObservations || patientObservations.length === 0) { + if (!patientVariants || patientVariants.length === 0) { return; } // extract required data from each observation let trimmedObservations: TrimmedObservation[] = []; - patientObservations.forEach((observation) => { + patientVariants.forEach((observation) => { if (!observation?.id) { return; } @@ -126,6 +134,8 @@ const ResultsDataFetcher: FC = () => { trimmedObservations = [...trimmedObservations, { cDnaChange: cDnaChange, observationId: observation.id }]; }); + const interpretation = overallInterpretation.at(0)?.valueCodeableConcept?.coding?.at(0)?.display || "Unknown"; + readableResults = [ ...readableResults, { @@ -133,6 +143,7 @@ const ResultsDataFetcher: FC = () => { officialPatientIdentifier: officialPatientIdentifier, firstName: firstName, lastName: lastName, + overallInterpretation: interpretation, observations: trimmedObservations, }, ]; diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index 039f55d..bae731f 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -1,10 +1,9 @@ -import { FC, useState, useContext } from "react"; +import { FC, useContext, useState } from "react"; import { FhirContext } from "../fhir/FhirContext"; import { Patient } from "@smile-cdr/fhirts/dist/FHIR-R4/classes/models-r4"; import LoadingSpinner from "../UI/LoadingSpinner"; -import ModalWrapper from "../UI/ModalWrapper"; -import { ModalState } from "../UI/ModalWrapper"; +import ModalWrapper, { ModalState } from "../UI/ModalWrapper"; import { ParsedResultsModel, TrimmedObservation } from "./ResultsDataFetcher"; @@ -84,6 +83,7 @@ const ResultsList: FC = (props) => { First name Last name cDNA changes + Overall interpretation @@ -107,6 +107,7 @@ const ResultsList: FC = (props) => { ); })} + {patient.overallInterpretation} ); })} From d54f2523e565cf373aac6bf31944ffc395ae8c68 Mon Sep 17 00:00:00 2001 From: Stef Piatek Date: Tue, 15 Nov 2022 12:41:01 +0000 Subject: [PATCH 03/12] Filter results to FH only --- src/components/results-list/ResultsDataFetcher.test.tsx | 4 ++-- src/components/results-list/ResultsDataFetcher.tsx | 2 +- src/components/results-list/ResultsList.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/results-list/ResultsDataFetcher.test.tsx b/src/components/results-list/ResultsDataFetcher.test.tsx index 4ae4540..c27091e 100644 --- a/src/components/results-list/ResultsDataFetcher.test.tsx +++ b/src/components/results-list/ResultsDataFetcher.test.tsx @@ -111,9 +111,9 @@ describe("Results table", () => { }); /** - * Given the FHIR API is cleared and has 5 separate reports added with one variant + * Given the FHIR API is cleared and has 5 separate reports added with one variant each (one not FH) * When the Results list page is rendered - * Then there should be 5 variants listed + * Then there should be 4 FH variants listed */ test("patients are in table", async () => { render(} />); diff --git a/src/components/results-list/ResultsDataFetcher.tsx b/src/components/results-list/ResultsDataFetcher.tsx index 5c6c51d..b9023d4 100644 --- a/src/components/results-list/ResultsDataFetcher.tsx +++ b/src/components/results-list/ResultsDataFetcher.tsx @@ -42,7 +42,7 @@ const ResultsDataFetcher: FC = () => { const [parsedResults, setParsedResults] = useState(null); useEffect(() => { - const observationQueryUrl = `DiagnosticReport/?_include=DiagnosticReport:result&_include=DiagnosticReport:subject`; + const observationQueryUrl = `DiagnosticReport/?code=${FH_CODE}&_include=DiagnosticReport:result&_include=DiagnosticReport:subject`; setIsLoading(true); diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index bae731f..3df34ec 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -75,7 +75,7 @@ const ResultsList: FC = (props) => { setModal(null)} /> {isLoading && } -

Patient results table

+

Familial hypercholesterolemia patient results

From c050b8511bb17b2a39a641d339e1972f6f85ab56 Mon Sep 17 00:00:00 2001 From: Stef Piatek Date: Thu, 17 Nov 2022 13:49:04 +0000 Subject: [PATCH 04/12] Display specific modal information based on FHIR data --- .../results-list/ResultsDataFetcher.tsx | 19 +++++++----- src/components/results-list/ResultsList.tsx | 29 ++++++++++++++----- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/components/results-list/ResultsDataFetcher.tsx b/src/components/results-list/ResultsDataFetcher.tsx index b9023d4..b27b988 100644 --- a/src/components/results-list/ResultsDataFetcher.tsx +++ b/src/components/results-list/ResultsDataFetcher.tsx @@ -10,7 +10,7 @@ import { Observation, Patient } from "@smile-cdr/fhirts/dist/FHIR-R4/classes/mod export type TrimmedObservation = { cDnaChange: string; observationId: string }; type PatientResult = { patientId: string; - officialPatientIdentifier: string; + familyIdentifier?: string; firstName: string; lastName: string; overallInterpretation: string; @@ -20,9 +20,9 @@ export type ParsedResultsModel = PatientResult[]; const HGVS_CDNA_LOINC = "48004-6"; const FH_CODE = "R134"; -const POSITIVE_FINDING = "LA6576-8"; const VARIANT_PROFILE = "http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/variant"; const INTERPRETATION_PROFILE = "http://hl7.org/fhir/uv/genomics-reporting/StructureDefinition/overall-interpretation"; +const FAMILY_NUMBER_SYSTEM = "http://fhir.nhs.uk/Id/nhs-family-number"; function filterObservations(patientId: string, VARIANT_PROFILE: string, observations: Observation[]) { return observations.filter((observation) => { @@ -95,12 +95,12 @@ const ResultsDataFetcher: FC = () => { // extract the required data and store in a trimmed down data structure patients.forEach((patient) => { - if (!(patient.id && patient.identifier?.[0]?.value && patient.name?.[0]?.given && patient.name?.[0]?.family)) { + if (!(patient.id && patient.name?.[0]?.given && patient.name?.[0]?.family)) { return; } const patientId = patient.id; - const officialPatientIdentifier = patient.identifier[0].value; + const familyNumber = patient.identifier?.find((id) => id.system === FAMILY_NUMBER_SYSTEM)?.value; const firstName = patient.name[0].given[0]; const lastName = patient.name[0].family; @@ -140,7 +140,7 @@ const ResultsDataFetcher: FC = () => { ...readableResults, { patientId: patientId, - officialPatientIdentifier: officialPatientIdentifier, + familyIdentifier: familyNumber, firstName: firstName, lastName: lastName, overallInterpretation: interpretation, @@ -152,16 +152,19 @@ const ResultsDataFetcher: FC = () => { return readableResults; }; - if (!parsedResults) { + if (!parsedResults && !isLoading) { return
Something went wrong getting observations. Please try again later.
; } + let resultsComponent = <>; + if (parsedResults) { + resultsComponent = ; + } return ( <> setModal(null)} /> {isLoading && } - - + {resultsComponent} ); }; diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index 3df34ec..1643eb4 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -6,7 +6,6 @@ import LoadingSpinner from "../UI/LoadingSpinner"; import ModalWrapper, { ModalState } from "../UI/ModalWrapper"; import { ParsedResultsModel, TrimmedObservation } from "./ResultsDataFetcher"; - import classes from "./ResultsList.module.css"; interface Props { @@ -20,8 +19,15 @@ const ResultsList: FC = (props) => { const { results } = props; - const checkCascadeTestingHandler = async (patientIdentifier: string) => { - const observationQueryUrl = `/Patient?identifier=${patientIdentifier}`; + const checkCascadeTestingHandler = async (overallInterpretation: string, familyNumber?: string) => { + if (!familyNumber) { + setModal({ + message: "This patient has no family number recorded.", + isError: false, + }); + return; + } + const observationQueryUrl = `/Patient?identifier=${familyNumber}`; setIsLoading(true); @@ -36,7 +42,7 @@ const ResultsList: FC = (props) => { return; } - displayCascadeAdvice(response.entry, patientIdentifier); + displayCascadeAdvice(response.entry, overallInterpretation); }) .catch((error) => { setModal({ @@ -50,17 +56,24 @@ const ResultsList: FC = (props) => { }); }; - const displayCascadeAdvice = (patients: Patient[], patientIdentifier: string) => { + const displayCascadeAdvice = (patients: Patient[], overallInterpretation: string) => { + if (overallInterpretation === "Positive") { + setModal({ + message: `Patient has no pathogenic variants reported`, + isError: false, + }); + } + if (patients.length === 1) { setModal({ - message: `Patient with identifier ${patientIdentifier} has a known pathogenic variant, please offer cascade testing to family. Currently this is the only family member with a test result.`, + message: `Patient has a known pathogenic variant, please offer cascade testing to family. Currently this is the only family member with a test result.`, isError: false, }); } if (patients.length > 1) { setModal({ - message: `Cascade testing has been performed for the family of patient with identifier ${patientIdentifier}. No need for any further action.`, + message: `Cascade testing has been performed for this patient. No need for any further action.`, isError: false, }); } @@ -91,7 +104,7 @@ const ResultsList: FC = (props) => { return (
checkCascadeTestingHandler(patient.officialPatientIdentifier)} + onClick={() => checkCascadeTestingHandler(patient.overallInterpretation, patient.familyIdentifier)} > From 70553151d4a4f6d88a5d1fcacd9b8b58f9d505c6 Mon Sep 17 00:00:00 2001 From: Stef Piatek Date: Thu, 17 Nov 2022 14:01:31 +0000 Subject: [PATCH 05/12] Use test variants from clinvar --- .../results-list/ResultsDataFetcher.test.tsx | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/components/results-list/ResultsDataFetcher.test.tsx b/src/components/results-list/ResultsDataFetcher.test.tsx index c27091e..004612f 100644 --- a/src/components/results-list/ResultsDataFetcher.test.tsx +++ b/src/components/results-list/ResultsDataFetcher.test.tsx @@ -15,7 +15,7 @@ type OverridingFields = { nhsNumber: string; firstName: string; lastName: string; - familyNumber: string; + familyNumber?: string; }; sample: { specimenCode: string; @@ -31,14 +31,17 @@ const clearFhirAndSendReports = async () => { await deleteFhirData(); const overridingValues = [ - // not FH - createPatientOverrides("Daffy", "Duck", ["R59"], "HGNC:4389", "c.115A>T"), + // not FH so shouldn't be in the list + createPatientOverrides("Daffy", "Duck", ["R59"], "HGNC:4389", "c.140G>A"), // Has a family member who has been tests - createPatientOverrides("Bugs", "Bunny", ["R134"], "HGNC:6547", "c.113A>T", "F12345"), - createPatientOverrides("Betty", "Bunny", ["R134"], "HGNC:6547", "c.113A>T", "F12345"), + createPatientOverrides("Bugs", "Bunny", ["R134"], "HGNC:6547", "NM_000527.5:c.259T>G", "F12345"), + createPatientOverrides("Betty", "Bunny", ["R134"], "HGNC:6547", "NM_000527.5:c.259T>G", "F12345"), // No family member who has been tested - createPatientOverrides("Road", "Runner", ["R134"], "HGNC:6547", "c.110A>T", "F10000"), - createPatientOverrides("Wile", "Coyote", ["R134"], "HGNC:6547", "c.112A>T"), + createPatientOverrides("Road", "Runner", ["R134"], "HGNC:6547", "NM_000527.5:c.27C>T", "F10000"), + // No family member + createPatientOverrides("Wile", "Coyote", ["R134"], "HGNC:6547", "NM_000527.5:c.58G>A"), + // Benign + createPatientOverrides("Yosemite", "Sam", ["R134"], "HGNC:6547", "NM_000527.5:c.9C>T"), ]; for (const override of overridingValues) { @@ -58,13 +61,13 @@ const createPatientOverrides = ( ): OverridingFields => { return { patient: { - mrn: generateNumber("mrn"), - nhsNumber: generateNumber("nhs"), + mrn: generateId("mrn"), + nhsNumber: generateId("nhs"), firstName: firstName, lastName: lastName, - familyNumber: familyNumber ? familyNumber : generateNumber("family"), + familyNumber: familyNumber, }, - sample: { specimenCode: generateNumber("specimen"), reasonForTest: testReason }, + sample: { specimenCode: generateId("specimen"), reasonForTest: testReason }, variant: { cDnaHgvs: cDnaHgvs, gene: gene, @@ -72,15 +75,13 @@ const createPatientOverrides = ( }; }; -const generateNumber = (type?: "nhs" | "family" | "specimen" | "mrn") => { +const generateId = (type?: "nhs" | "specimen" | "mrn") => { let num; const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const randomCharacter = characters[Math.floor(Math.random() * characters.length)]; if (type === "nhs") { num = Math.floor(Math.random() * Math.pow(10, 10)); - } else if (type === "family") { - num = randomCharacter + Math.floor(Math.random() * Math.pow(10, 6)); } else if (type === "specimen") { num = randomCharacter + Math.floor(Math.random() * Math.pow(10, 10)); } else if (type === "mrn") { From 70587975742a570e9f5652e191f2ae7ec10cb661 Mon Sep 17 00:00:00 2001 From: Stef Piatek Date: Thu, 17 Nov 2022 14:22:29 +0000 Subject: [PATCH 06/12] Generate negative FH report for list tests --- .../results-list/ResultsDataFetcher.test.tsx | 23 ++++++++++++++++--- src/components/results-list/ResultsList.tsx | 5 ++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/components/results-list/ResultsDataFetcher.test.tsx b/src/components/results-list/ResultsDataFetcher.test.tsx index 004612f..d3202d9 100644 --- a/src/components/results-list/ResultsDataFetcher.test.tsx +++ b/src/components/results-list/ResultsDataFetcher.test.tsx @@ -24,6 +24,8 @@ type OverridingFields = { variant: { cDnaHgvs: string; gene: string; + isPathogenic: boolean; + transcript?: string; }; }; @@ -41,7 +43,7 @@ const clearFhirAndSendReports = async () => { // No family member createPatientOverrides("Wile", "Coyote", ["R134"], "HGNC:6547", "NM_000527.5:c.58G>A"), // Benign - createPatientOverrides("Yosemite", "Sam", ["R134"], "HGNC:6547", "NM_000527.5:c.9C>T"), + createPatientOverrides("Yosemite", "Sam", ["R134"], "HGNC:6547", "NM_000527.5:c.9C>T", "F54321", false), ]; for (const override of overridingValues) { @@ -58,7 +60,12 @@ const createPatientOverrides = ( gene: string, cDnaHgvs: string, familyNumber?: string, + isPathogenic = true, ): OverridingFields => { + let transcript: string | undefined = undefined; + if (cDnaHgvs.includes(":")) { + transcript = cDnaHgvs.split(":")[0]; + } return { patient: { mrn: generateId("mrn"), @@ -71,6 +78,8 @@ const createPatientOverrides = ( variant: { cDnaHgvs: cDnaHgvs, gene: gene, + isPathogenic: isPathogenic, + transcript: transcript, }, }; }; @@ -103,6 +112,14 @@ const changePatientInfo = (valuesToUpdate: OverridingFields) => { newPatient.sample.reasonForTest = valuesToUpdate.sample.reasonForTest; newPatient.variant[0].cDnaHgvs = valuesToUpdate.variant.cDnaHgvs; newPatient.variant[0].gene = valuesToUpdate.variant.gene; + if (!valuesToUpdate.variant.isPathogenic) { + newPatient.result.reportFinding = "LA6577-6"; // negative + newPatient.variant[0].classification = "LA26334-5"; // likely benign + } + if (valuesToUpdate.variant.transcript) { + newPatient.variant[0].transcript = valuesToUpdate.variant.transcript; + } + return newPatient; }; @@ -112,9 +129,9 @@ describe("Results table", () => { }); /** - * Given the FHIR API is cleared and has 5 separate reports added with one variant each (one not FH) + * Given the FHIR API is cleared and has 6 separate reports added with one variant each (one not FH) * When the Results list page is rendered - * Then there should be 4 FH variants listed + * Then there should be 5 FH variants listed */ test("patients are in table", async () => { render(} />); diff --git a/src/components/results-list/ResultsList.tsx b/src/components/results-list/ResultsList.tsx index 1643eb4..c4eed12 100644 --- a/src/components/results-list/ResultsList.tsx +++ b/src/components/results-list/ResultsList.tsx @@ -57,11 +57,12 @@ const ResultsList: FC = (props) => { }; const displayCascadeAdvice = (patients: Patient[], overallInterpretation: string) => { - if (overallInterpretation === "Positive") { + if (overallInterpretation !== "Positive") { setModal({ message: `Patient has no pathogenic variants reported`, isError: false, }); + return; } if (patients.length === 1) { @@ -95,7 +96,7 @@ const ResultsList: FC = (props) => { - + From 1a274f514970a8cbff5454ea43315952fe8e25cb Mon Sep 17 00:00:00 2001 From: Stef Piatek Date: Thu, 17 Nov 2022 15:04:33 +0000 Subject: [PATCH 07/12] Make test setup and teardown usage more specific That way we don't have to run unnecessary requests --- src/components/reports/ReportForm.test.tsx | 11 +++++++++++ .../results-list/ResultsDataFetcher.test.tsx | 8 ++++---- src/fhir/testUtilities.tsx | 1 - src/setupTests.ts | 17 +---------------- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/components/reports/ReportForm.test.tsx b/src/components/reports/ReportForm.test.tsx index 268091e..642910a 100644 --- a/src/components/reports/ReportForm.test.tsx +++ b/src/components/reports/ReportForm.test.tsx @@ -165,6 +165,17 @@ const renderTestReportForm = () => { render(} />, { wrapper: BrowserRouter }); }; +beforeAll(() => { + /* + * For some reason on first load of FHIR server, creating a batch of requests doesn't seem to validate + * Sending a resource before any test are run fixed the issue + */ + const practitioner = new Practitioner(); + practitioner.resourceType = "Practitioner"; + practitioner.identifier = [createIdentifier("initial")]; + return createPractitioner(practitioner); +}); + describe("Report form", () => { beforeEach(() => { return deleteFhirData(); diff --git a/src/components/results-list/ResultsDataFetcher.test.tsx b/src/components/results-list/ResultsDataFetcher.test.tsx index d3202d9..85e3065 100644 --- a/src/components/results-list/ResultsDataFetcher.test.tsx +++ b/src/components/results-list/ResultsDataFetcher.test.tsx @@ -123,11 +123,11 @@ const changePatientInfo = (valuesToUpdate: OverridingFields) => { return newPatient; }; -describe("Results table", () => { - beforeEach(() => { - return clearFhirAndSendReports(); - }); +beforeAll(() => { + return clearFhirAndSendReports(); +}); +describe("Results table", () => { /** * Given the FHIR API is cleared and has 6 separate reports added with one variant each (one not FH) * When the Results list page is rendered diff --git a/src/fhir/testUtilities.tsx b/src/fhir/testUtilities.tsx index 777980e..2850b16 100644 --- a/src/fhir/testUtilities.tsx +++ b/src/fhir/testUtilities.tsx @@ -20,7 +20,6 @@ export const createPractitioner = async (practitioner: Practitioner) => { "Content-Type": "application/json", }, }); - // await new Promise((r) => setTimeout(r, 500)); return checkResponseOK(sendPractitioner); }; diff --git a/src/setupTests.ts b/src/setupTests.ts index b785579..dc6afbb 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -2,12 +2,9 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import { Practitioner } from "@smile-cdr/fhirts/dist/FHIR-R4/classes/practitioner"; import "@testing-library/jest-dom"; import { enableFetchMocks } from "jest-fetch-mock"; -import { createIdentifier } from "./fhir/resource_helpers"; -import { createPractitioner } from "./fhir/testUtilities"; jest.resetAllMocks(); /** @@ -19,20 +16,8 @@ jest.mock("react-router-dom", () => ({ useNavigate: () => mockedNavigate, })); -beforeAll(async () => { - /* - * For some reason on first load of FHIR server, creating a batch of requests doesn't seem to validate - * Sending a resource before any test are run fixed the issue - */ - const practitioner = new Practitioner(); - practitioner.resourceType = "Practitioner"; - practitioner.identifier = [createIdentifier("initial")]; - await createPractitioner(practitioner); - +beforeEach(() => { enableFetchMocks(); -}); - -global.beforeEach(() => { fetchMock.resetMocks(); fetchMock.mockIf(/clinicaltables\.nlm\.nih\.gov\/api\/genes\/v4\/search/, (request: Request) => { let requestData: (number | string[] | string[][] | null)[] = [0, [], null, []]; From 5683313774874321f7cdeedcf2eca009bdeeed64 Mon Sep 17 00:00:00 2001 From: Stef Piatek Date: Thu, 17 Nov 2022 15:26:20 +0000 Subject: [PATCH 08/12] Add automated tests for modal upon click of results list --- .../results-list/ResultsDataFetcher.test.tsx | 74 +++++++++++++++++-- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/src/components/results-list/ResultsDataFetcher.test.tsx b/src/components/results-list/ResultsDataFetcher.test.tsx index 85e3065..66d2eae 100644 --- a/src/components/results-list/ResultsDataFetcher.test.tsx +++ b/src/components/results-list/ResultsDataFetcher.test.tsx @@ -4,6 +4,8 @@ import { createBundle } from "../../fhir/api"; import { ContextAndModal, deleteFhirData, sendBundle } from "../../fhir/testUtilities"; import { geneCoding } from "../../code_systems/hgnc"; import ResultsDataFetcher from "./ResultsDataFetcher"; +import { act } from "react-dom/test-utils"; +import userEvent from "@testing-library/user-event"; const reportedGenes = [geneCoding("HGNC:4389", "GNA01"), geneCoding("HGNC:6547", "LDLR")]; @@ -38,7 +40,7 @@ const clearFhirAndSendReports = async () => { // Has a family member who has been tests createPatientOverrides("Bugs", "Bunny", ["R134"], "HGNC:6547", "NM_000527.5:c.259T>G", "F12345"), createPatientOverrides("Betty", "Bunny", ["R134"], "HGNC:6547", "NM_000527.5:c.259T>G", "F12345"), - // No family member who has been tested + // No family member who has been tested, cascading testing required createPatientOverrides("Road", "Runner", ["R134"], "HGNC:6547", "NM_000527.5:c.27C>T", "F10000"), // No family member createPatientOverrides("Wile", "Coyote", ["R134"], "HGNC:6547", "NM_000527.5:c.58G>A"), @@ -137,12 +139,68 @@ describe("Results table", () => { render(} />); // Can put a breakpoint here to allow for development with 5 patients added to the result list - await waitFor( - () => { - const resultsTable = screen.getAllByText(/NM_/); - expect(resultsTable.length).toEqual(5); - }, - { timeout: 15000 }, - ); + await waitFor(() => { + const resultsTable = screen.getAllByText(/NM_/); + expect(resultsTable.length).toEqual(5); + }); + }); +}); + +describe("Modal from results table", () => { + beforeEach(() => { + render(} />); + }); + + const findPatientAndClickThem = async (patientRegex: RegExp) => { + const patient = await screen.findByText(patientRegex); + await act(async () => { + userEvent.click(patient); + }); + }; + + const textIsInDocument = async (regExp: RegExp) => { + await waitFor(() => { + expect(screen.queryByText(regExp)).toBeInTheDocument(); + }); + }; + + /** + * Given that there are two patients reports with the same family number and pathogenic variants + * When one of the patients is clicked + * Then the modal shows cascade testing complete + */ + test("Cascade testing has been carried out is shown", async () => { + await findPatientAndClickThem(/Betty/); + await textIsInDocument(/Cascade testing has been performed for this patient/); + }); + + /** + * Given that there is one patient with a family number and a pathogenic variant + * When the patient is clicked + * Then the modal shows cascade testing required + */ + test("Cascade testing required is shown", async () => { + await findPatientAndClickThem(/Runner/); + await textIsInDocument(/please offer cascade testing/); + }); + + /** + * Given that there is one patient with a pathogenic variant but no family number + * When the patient is clicked + * Then the modal shows no family number recorded + */ + test("No family number is shown", async () => { + await findPatientAndClickThem(/Coyote/); + await textIsInDocument(/no family number recorded/); + }); + + /** + * Given that there is one patient with a negative overall interpretation + * When the patient is clicked + * Then the modal shows no pathogenic variants + */ + test("Negative result is shown", async () => { + await findPatientAndClickThem(/Negative/); + await textIsInDocument(/Patient has no pathogenic variants reported/); }); }); From 53b805f82d767d9aeeb46161f85f5a60bcda4e2e Mon Sep 17 00:00:00 2001 From: Stef Piatek Date: Thu, 17 Nov 2022 15:41:12 +0000 Subject: [PATCH 09/12] Increase timeout for result list tests for CI --- .../results-list/ResultsDataFetcher.test.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/results-list/ResultsDataFetcher.test.tsx b/src/components/results-list/ResultsDataFetcher.test.tsx index 66d2eae..88b1bc8 100644 --- a/src/components/results-list/ResultsDataFetcher.test.tsx +++ b/src/components/results-list/ResultsDataFetcher.test.tsx @@ -139,10 +139,13 @@ describe("Results table", () => { render(} />); // Can put a breakpoint here to allow for development with 5 patients added to the result list - await waitFor(() => { - const resultsTable = screen.getAllByText(/NM_/); - expect(resultsTable.length).toEqual(5); - }); + await waitFor( + () => { + const resultsTable = screen.getAllByText(/NM_/); + expect(resultsTable.length).toEqual(5); + }, + { timeout: 15000 }, + ); }); }); @@ -159,9 +162,12 @@ describe("Modal from results table", () => { }; const textIsInDocument = async (regExp: RegExp) => { - await waitFor(() => { - expect(screen.queryByText(regExp)).toBeInTheDocument(); - }); + await waitFor( + () => { + expect(screen.queryByText(regExp)).toBeInTheDocument(); + }, + { timeout: 15000 }, + ); }; /** From d4bd00051a0fddb316df86a8367324be6a11abd5 Mon Sep 17 00:00:00 2001 From: Stef Piatek Date: Thu, 17 Nov 2022 15:56:30 +0000 Subject: [PATCH 10/12] Make no results message more specific --- src/components/results-list/ResultsDataFetcher.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/results-list/ResultsDataFetcher.tsx b/src/components/results-list/ResultsDataFetcher.tsx index b27b988..454dc70 100644 --- a/src/components/results-list/ResultsDataFetcher.tsx +++ b/src/components/results-list/ResultsDataFetcher.tsx @@ -153,7 +153,7 @@ const ResultsDataFetcher: FC = () => { }; if (!parsedResults && !isLoading) { - return
Something went wrong getting observations. Please try again later.
; + return
Familial hypercholesterolemia patient results were found on the FHIR server
; } let resultsComponent = <>; From 41a60e4af3b5d71e1d2c3338fa75b15676cf7b7d Mon Sep 17 00:00:00 2001 From: Stef Piatek Date: Thu, 17 Nov 2022 16:03:37 +0000 Subject: [PATCH 11/12] Sort list view results by last name --- src/components/results-list/ResultsDataFetcher.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/results-list/ResultsDataFetcher.tsx b/src/components/results-list/ResultsDataFetcher.tsx index 454dc70..9745c69 100644 --- a/src/components/results-list/ResultsDataFetcher.tsx +++ b/src/components/results-list/ResultsDataFetcher.tsx @@ -158,7 +158,8 @@ const ResultsDataFetcher: FC = () => { let resultsComponent = <>; if (parsedResults) { - resultsComponent = ; + const sortedResults = parsedResults.sort((a, b) => a.lastName.localeCompare(b.lastName)); + resultsComponent = ; } return ( <> From 54a5bcf961a261cb9aae7fe6224137d7541617ca Mon Sep 17 00:00:00 2001 From: Stefan Piatek Date: Mon, 21 Nov 2022 10:43:20 +0000 Subject: [PATCH 12/12] Update no FH patients found comment --- src/components/results-list/ResultsDataFetcher.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/results-list/ResultsDataFetcher.tsx b/src/components/results-list/ResultsDataFetcher.tsx index 9745c69..3ac7f26 100644 --- a/src/components/results-list/ResultsDataFetcher.tsx +++ b/src/components/results-list/ResultsDataFetcher.tsx @@ -153,7 +153,7 @@ const ResultsDataFetcher: FC = () => { }; if (!parsedResults && !isLoading) { - return
Familial hypercholesterolemia patient results were found on the FHIR server
; + return
No familial hypercholesterolemia patient results were found on the FHIR server
; } let resultsComponent = <>;
{patient.firstName} {patient.lastName}
First name Last namecDNA changescDNA change(s) Overall interpretation