Skip to content

Commit

Permalink
Add navigation back to search results (#2305)
Browse files Browse the repository at this point in the history
* move Mode to constants & add multiple-patients-results Mode

* add context for Mode constant

* rename component to ResultsView

* ensure unique keys

* add Mode import from constants

* add goBack button & setSingleUseCaseRequest

* [pre-commit.ci] auto fixes from pre-commit hooks

* linting

* [pre-commit.ci] auto fixes from pre-commit hooks

* update source of Mode

* formatting

* [pre-commit.ci] auto fixes from pre-commit hooks

* add separate button to go back to multiple patients

* [pre-commit.ci] auto fixes from pre-commit hooks

* update docstring + props

* update tests

* [pre-commit.ci] auto fixes from pre-commit hooks

* add test for returning multiple patients

* [pre-commit.ci] auto fixes from pre-commit hooks

* add test for new buttons

* [pre-commit.ci] auto fixes from pre-commit hooks

* add documentation for running a specific test by name

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
m-goggins and pre-commit-ci[bot] authored Aug 5, 2024
1 parent 8866dc8 commit 87e27f8
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 66 deletions.
44 changes: 42 additions & 2 deletions containers/tefca-viewer/e2e/query_workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ test.describe("querying with the TryTEFCA viewer", () => {
await expect(page.locator("tbody").locator("tr")).toHaveCount(5);

// Now let's use the return to search to go back to a blank form
await page.getByRole("link", { name: "Return to search" }).click();
await page.getByRole("link", { name: "New patient search" }).click();
await expect(
page.getByRole("heading", { name: "Search for a Patient" }),
).toBeVisible();
Expand Down Expand Up @@ -154,7 +154,7 @@ test.describe("querying with the TryTEFCA viewer", () => {
});
});

test.describe("test query user journey", () => {
test.describe("Test the user journey of a 'tester'", () => {
test.beforeEach(async ({ page }) => {
// Start every test on direct tester page
await page.goto("http://localhost:3000/tefca-viewer/query/test", {
Expand Down Expand Up @@ -232,4 +232,44 @@ test.describe("test query user journey", () => {
await expect(page.getByText("Patient Identifiers")).toBeVisible();
await expect(page.getByText("MRN: 18091")).toBeVisible();
});

test("Query with multiple patients returned", async ({ page }) => {
// Query for a patient with multiple results
await page
.getByLabel("Query", { exact: true })
.selectOption("Chlamydia case investigation");
await page
.getByLabel("FHIR Server (QHIN)", { exact: true })
.selectOption("JMC Meld: Direct");
await page.getByLabel("Last Name").fill("JMC");

await page.getByRole("button", { name: "Search for patient" }).click();
// Make sure all the elements for the multiple patients view appear
await expect(
page.getByRole("heading", { name: "Multiple Records Found" }),
).toBeVisible();
// Check that there is a Table element with the correct headers
await expect(page.locator("thead").locator("tr")).toHaveText(
"NameDOBContactAddressMRNActions",
);

// Check that there are multiple rows in the table
await expect(page.locator("tbody").locator("tr")).toHaveCount(9);

// Click on the first patient's "View Record" button
await page.locator(':nth-match(:text("View Record"), 1)').click();

// Make sure we have a results page with a single patient & appropriate back buttons
await expect(
page.getByRole("heading", { name: "Query Results" }),
).toBeVisible();
await expect(
page.getByRole("link", { name: "New patient search" }),
).toBeVisible();

await page.getByRole("link", { name: "Return to search results" }).click();
await expect(
page.getByRole("heading", { name: "Multiple Records Found" }),
).toBeVisible();
});
});
32 changes: 19 additions & 13 deletions containers/tefca-viewer/playwright.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,35 @@
The TEFCA Query Connector uses Playwright Test as its end-to-end testing framework. Playwright is a browser-based testing library that enables tests to run against a variety of different browsers under a variety of different conditions. To manage this suite, Playwright creates some helpful files (and commands) that can be used to tweak its configuration.

## Config and Directories

Playwright's configuration is managed by the file `playwright.config.ts`. This file has information on which browsers to test against, configuration options for those browsers, optional mobile browser ports, retry and other utility options, and a dev webserver. Changing this file will make global changes to Playwright's operations.

By default, Playwright will look for end to end tests in `/e2e`.

## Testing Commands and Demos

Playwright provides a number of different ways of executing end to end tests. From the `tefca-viewer/` directory, you can run several commands:

`npx playwright test`
Runs the end-to-end tests.
`npx playwright test`
Runs the end-to-end tests.

`npx playwright test --ui`
Starts the interactive UI mode.
`npx playwright test --ui`
Starts the interactive UI mode.

`npx playwright test --project=chromium`
Runs the tests only on Desktop Chrome.
`npx playwright test --project=chromium`
Runs the tests only on Desktop Chrome.

`npx playwright test example`
Runs the tests in a specific file.
`npx playwright test example`
Runs the tests in a specific file.

`npx playwright test --debug`
Runs the tests in debug mode.
`npx playwright test -g "test name"`
Runs the test with the name "test name", e.g., Query("test name") would run here.

`npx playwright codegen`
Auto generate tests with Codegen.
`npx playwright test --debug`
Runs the tests in debug mode.

`npx playwright codegen`
Auto generate tests with Codegen.

After running a test set on your local, you can also additionally type `npx playwright show-report` to view an HTML report page of different test statuses and results.

Expand All @@ -35,4 +40,5 @@ An example end to end test spec can be found in `e2e/example.spec.ts`.
A suite of end to end tests for a sample application called "Todo App" can be found in `/tests-examples/demo-todo-app.spec.ts`.

## Github Integration
Playwright is managed by an end-to-end job in the `.github/workflows/container-tefca-viewer.yaml` file of the project root. Since it requires browser installation to effectively test, and since it operates using an independent framework from jest, it is explicitly _not_ included in the basic `npm test` scripts (specified in `package.json`).

Playwright is managed by an end-to-end job in the `.github/workflows/container-tefca-viewer.yaml` file of the project root. Since it requires browser installation to effectively test, and since it operates using an independent framework from jest, it is explicitly _not_ included in the basic `npm test` scripts (specified in `package.json`).
8 changes: 8 additions & 0 deletions containers/tefca-viewer/src/app/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,11 @@ export const stateOptions = [
{ value: "AE", label: "AE - Armed Forces Middle East" },
{ value: "AP", label: "AP - Armed Forces Pacific" },
];

/* Mode that pages can be in; determines what is displayed to the user */
export type Mode =
| "search"
| "results"
| "multiple-patients"
| "no-patients"
| "multiple-patients-results";
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const Demographics: React.FC<DemographicsProps> = ({ patient }) => {
return (
<div>
{demographicData.map((item, index) => (
<DataDisplay item={item} key={index} />
<DataDisplay item={item} key={item.title} />
))}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { Fragment, useEffect, useState } from "react";
import { Table } from "@trussworks/react-uswds";
import { Patient } from "fhir/r4";
import {
Expand All @@ -12,41 +12,52 @@ import {
UseCaseQuery,
UseCaseQueryRequest,
} from "../../query-service";
import { Mode } from "../page";
import ResultsView from "./ResultsView";

/**
* The props for the MultiplePatientSearchResults component.
*/
export interface MultiplePatientSearchResultsProps {
patients: Patient[];
originalRequest: UseCaseQueryRequest;
setUseCaseQueryResponse: (UseCaseQueryResponse: UseCaseQueryResponse) => void;
setMode: (mode: Mode) => void;
setLoading: (loading: boolean) => void;
goBack: () => void;
}

/**
* Displays multiple patient search results in a table.
* @param root0 - MultiplePatientSearchResults props.
* @param root0.patients - The array of Patient resources.
* @param root0.originalRequest - The original request object.
* @param root0.setUseCaseQueryResponse - The function to set the use case query response.
* @param root0.setMode - The function to set the mode.
* @param root0.setLoading - The function to set the loading state.
* @param root0.goBack - The function to go back to the previous page.
* @returns - The MultiplePatientSearchResults component.
*/
const MultiplePatientSearchResults: React.FC<
MultiplePatientSearchResultsProps
> = ({
patients,
originalRequest,
setUseCaseQueryResponse,
setMode,
setLoading,
}) => {
> = ({ patients, originalRequest, setLoading, goBack }) => {
useEffect(() => {
window.scrollTo(0, 0);
}, []);
// Determines whether to show the results of a single patient (ResultsView) or
// go back to the multiple patients view (below)
const [singleUseCaseQueryResponse, setSingleUseCaseQueryResponse] =
useState<UseCaseQueryResponse>();

if (singleUseCaseQueryResponse) {
// If a single patient is selected, show the button for returning to the search results
// & take user back to the search results by setting the singleUseCaseQueryResponse to undefined
return (
<ResultsView
useCaseQueryResponse={singleUseCaseQueryResponse}
goBack={goBack}
goBackToMultiplePatients={() =>
setSingleUseCaseQueryResponse(undefined)
}
/>
);
}

return (
<>
<div className="multiple-patient-search-results">
Expand Down Expand Up @@ -79,8 +90,7 @@ const MultiplePatientSearchResults: React.FC<
patients,
index,
originalRequest,
setUseCaseQueryResponse,
setMode,
setSingleUseCaseQueryResponse,
setLoading,
)
}
Expand All @@ -93,7 +103,7 @@ const MultiplePatientSearchResults: React.FC<
</tbody>
</Table>
<h3>Not seeing what you are looking for?</h3>
<a href="#" onClick={() => setMode("search")}>
<a href="#" onClick={() => goBack()}>
Return to patient search
</a>
</div>
Expand All @@ -118,7 +128,9 @@ function searchResultsNote(request: UseCaseQueryRequest): JSX.Element {
searchElements = searchElements.filter((element) => element !== undefined);

let noteParts = [
<>The following records match by the values provided for </>,
<Fragment key="start">
The following records match by the values provided for{" "}
</Fragment>,
];
let comma = ", ";
if (searchElements.length <= 2) {
Expand All @@ -127,29 +139,35 @@ function searchResultsNote(request: UseCaseQueryRequest): JSX.Element {
for (let i = 0; i < searchElements.length; i++) {
if (i === searchElements.length - 1) {
if (searchElements.length > 1) {
noteParts.push(<>and </>);
noteParts.push(<Fragment key="and">and </Fragment>);
}
comma = "";
}
switch (searchElements[i]) {
case "first_name":
noteParts.push(
<strong style={{ fontWeight: 550 }}>{"First Name" + comma}</strong>,
<strong key={searchElements[i]} style={{ fontWeight: 550 }}>
{"First Name" + comma}
</strong>,
);
break;
case "last_name":
noteParts.push(
<strong style={{ fontWeight: 550 }}>{"Last Name" + comma}</strong>,
<strong key={searchElements[i]} style={{ fontWeight: 550 }}>
{"Last Name" + comma}
</strong>,
);
break;
case "dob":
noteParts.push(
<strong style={{ fontWeight: 550 }}>{"DOB" + comma}</strong>,
<strong key={searchElements[i]} style={{ fontWeight: 550 }}>
{"DOB" + comma}
</strong>,
);
break;
}
}
noteParts.push(<>:</>);
noteParts.push(<Fragment key=":">:</Fragment>);
return <p className="font-sans-lg text-light">{noteParts}</p>;
}

Expand All @@ -160,22 +178,20 @@ function searchResultsNote(request: UseCaseQueryRequest): JSX.Element {
* @param index - The index of the patient to view.
* @param originalRequest - The original request object.
* @param setUseCaseQueryResponse - The function to set the use case query response.
* @param setMode - The function to set the mode.
* @param setLoading - The function to set the loading state.
*/
async function viewRecord(
patients: Patient[],
index: number,
originalRequest: UseCaseQueryRequest,
setUseCaseQueryResponse: (UseCaseQueryResponse: UseCaseQueryResponse) => void,
setMode: (mode: Mode) => void,
setLoading: (loading: boolean) => void,
): Promise<void> {
setLoading(true);
const queryResponse = await UseCaseQuery(originalRequest, {
Patient: [patients[index]],
});
setUseCaseQueryResponse(queryResponse);
setMode("results");

setLoading(false);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect } from "react";
import { Mode } from "../page";
import { Mode } from "../../constants";

interface NoPatientsFoundProps {
setMode: (mode: Mode) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@ import { UseCaseQueryResponse } from "../../query-service";
import AccordionContainer from "./AccordionContainer";
import SideNav from "./SideNav";
import React, { useEffect } from "react";
import { Mode } from "../page";
import { Alert } from "@trussworks/react-uswds";
import { Alert, Icon } from "@trussworks/react-uswds";

type QueryViewProps = {
type ResultsViewProps = {
useCaseQueryResponse: UseCaseQueryResponse;
setMode: (mode: Mode) => void;
goBack: () => void;
goBackToMultiplePatients?: () => void;
};

/**
* The QueryView component to render the query results.
* @param props - The props for the QueryView component.
* @param props.useCaseQueryResponse - The response from the query service.
* @param props.setMode - The function to set the mode of the query page.
* @param props.goBack - The function to go back to the previous page.
* @param props.goBackToMultiplePatients - The function to go back to the multiple patients selection page.
* @returns The QueryView component.
*/
const QueryView: React.FC<QueryViewProps> = ({
const ResultsView: React.FC<ResultsViewProps> = ({
useCaseQueryResponse,
setMode,
goBack,
goBackToMultiplePatients,
}) => {
useEffect(() => {
window.scrollTo(0, 0);
Expand All @@ -40,10 +42,25 @@ const QueryView: React.FC<QueryViewProps> = ({
[email protected]
</a>
</Alert>

<div className="results-banner">
<div className="results-banner-content usa-nav-container">
<a href="#" onClick={() => setMode("search")}>
Return to search
{goBackToMultiplePatients && (
<>
<a
href="#"
onClick={() => goBackToMultiplePatients()}
className="back-link"
>
<Icon.ArrowBack />
Return to search results
</a>
<div className="results-banner-divider">|</div>
</>
)}

<a href="#" onClick={() => goBack()} className="back-link">
New patient search
</a>
</div>
</div>
Expand All @@ -69,4 +86,4 @@ const QueryView: React.FC<QueryViewProps> = ({
</>
);
};
export default QueryView;
export default ResultsView;
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import {
demoQueryOptions,
patientOptions,
stateOptions,
Mode,
} from "../../constants";
import {
UseCaseQueryResponse,
UseCaseQuery,
UseCaseQueryRequest,
} from "../../query-service";
import { Mode } from "../page";

import { FormatPhoneAsDigits } from "@/app/format-service";
import { useSearchParams } from "next/navigation";

Expand Down
Loading

0 comments on commit 87e27f8

Please sign in to comment.