diff --git a/jest.config.js b/jest.config.js index dc04860d0..7e11f045e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -10,7 +10,7 @@ module.exports = { "single-spa-react/parcel": "single-spa-react/lib/cjs/parcel.cjs", "\\.(css|scss)$": "identity-obj-proxy", }, - setupFilesAfterEnv: ["@testing-library/jest-dom"], + setupFilesAfterEnv: ["@testing-library/jest-dom", "/jest.setup.js"], globals: { "ts-jest": { tsconfig: { diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 000000000..9ede9d190 --- /dev/null +++ b/jest.setup.js @@ -0,0 +1 @@ +global.Request = jest.fn(); diff --git a/src/components/Home.tsx b/src/components/Home.tsx index 536443f3b..6b10e5ed5 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -1,5 +1,4 @@ import React, { Suspense, useEffect, useState } from "react"; -import { BrowserRouter } from "react-router-dom"; import RoutesWrapper from "./routes/RoutesWrapper"; import { ServiceConfig, ApiContextProvider } from "../api/ServiceContext"; import axios from "../api/axios-instance"; @@ -32,13 +31,11 @@ export default function Home() { const loadingState =
Loading...
; const loadedState = ( - - - loading}> - - - - + + loading}> + + + ); let result = serviceConfig === null ? loadingState : loadedState; diff --git a/src/components/editTestCase/qiCore/EditTestCase.test.tsx b/src/components/editTestCase/qiCore/EditTestCase.test.tsx index 8b37d9bf1..a5abc1fe5 100644 --- a/src/components/editTestCase/qiCore/EditTestCase.test.tsx +++ b/src/components/editTestCase/qiCore/EditTestCase.test.tsx @@ -7,7 +7,7 @@ import { waitFor, within, } from "@testing-library/react"; -import { MemoryRouter, Route, Routes } from "react-router-dom"; +import { createMemoryRouter, RouterProvider } from "react-router-dom"; import EditTestCase, { findEpisodeActualValue, isEmptyTestCaseJsonString, @@ -17,18 +17,14 @@ import { AxiosError, AxiosResponse } from "axios"; import axios from "../../../api/axios-instance"; import { ApiContextProvider, ServiceConfig } from "../../../api/ServiceContext"; import { - HapiOperationOutcome, Measure, MeasureErrorType, MeasureScoring, Model, - Population, PopulationExpectedValue, PopulationType, TestCase, } from "@madie/madie-models"; -import TestCaseRoutes from "../../routes/qiCore/TestCaseRoutes"; -import { act } from "react-dom/test-utils"; import { PopulationEpisodeResult } from "../../../api/CalculationService"; import { simpleMeasureFixture } from "../../createTestCase/__mocks__/simpleMeasureFixture"; import { testCaseFixture } from "../../createTestCase/__mocks__/testCaseFixture"; @@ -45,6 +41,11 @@ import { checkUserCanEdit } from "@madie/madie-util"; import { PopulationType as FqmPopulationType } from "fqm-execution/build/types/Enums"; import { addValues } from "../../../util/DefaultValueProcessor"; import { ResourceIdentifier } from "../../../api/models/ResourceIdentifier"; +import RedirectToList from "../../routes/RedirectToList"; +import TestCaseLandingWrapper from "../../testCaseLanding/common/TestCaseLandingWrapper"; +import TestCaseData from "../../testCaseConfiguration/testCaseData/TestCaseData"; +import NotFound from "../../notfound/NotFound"; +import TestCaseLanding from "../../testCaseLanding/qiCore/TestCaseLanding"; //temporary solution (after jest updated to version 27) for error: thrown: "Exceeded timeout of 5000 ms for a test. jest.setTimeout(60000); @@ -183,12 +184,33 @@ const defaultMeasure = { ], acls: [{ userId: "othertestuser@example.com", roles: ["SHARED_WITH"] }], } as unknown as Measure; + +const testCase: TestCase = { + id: "1234", + description: "Test IPP description", + series: "SeriesA", + createdBy: MEASURE_CREATEDBY, + createdAt: "01-01-2024", + lastModifiedAt: "01-10-2024", + lastModifiedBy: MEASURE_CREATEDBY, + title: "TestIPP", + name: "TestIPP", + executionStatus: "false", + json: null, + groupPopulations: [], + hapiOperationOutcome: undefined, + patientId: "", + validResource: false, +}; + const measureBundle = buildMeasureBundle(simpleMeasureFixture); const valueSets = [getExampleValueSet()]; const setMeasure = jest.fn(); const setMeasureBundle = jest.fn(); const setValueSets = jest.fn(); -const setError = jest.fn(); +const setErrors = jest.fn(); +const setImportWarnings = jest.fn(); +const errors = []; // Need this for our drag windows to work global.ResizeObserver = jest.fn().mockImplementation(() => ({ observe: jest.fn(), @@ -196,34 +218,81 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({ disconnect: jest.fn(), })); +const routesConfig = [ + { + children: [ + { + path: "/measures/:measureId/edit/test-cases", + element: , + }, + { + path: "/measures/:measureId/edit/test-cases/:id", + element: , + }, + { + path: "/measures/:measureId/edit/test-cases/list-page", + element: ( + + } + /> + ), + }, + { + path: "/measures/:measureId/edit/test-cases/list-page/:criteriaId", + element: ( + + } + /> + ), + }, + { + path: "/measures/:measureId/edit/test-cases/list-page/test-case-data", + element: ( + } /> + ), + }, + { path: "/404", element: }, + { path: "*", element: }, + ], + }, +]; const renderWithRouter = ( - initialEntries = [], - routePath: string, + initialEntries, measure: Measure = defaultMeasure ) => { - return render( - - - - - } - /> - - - - + const router = createMemoryRouter(routesConfig, { + initialEntries, + }); + render( + + + + + ); }; @@ -260,7 +329,6 @@ const resourceIdentifiers: ResourceIdentifier[] = [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-procedure", }, ]; - const testTitle = async (title: string, clear = false) => { const tcTitle = await screen.findByTestId("test-case-title"); expect(tcTitle).toBeInTheDocument(); @@ -322,7 +390,7 @@ describe("EditTestCase component", () => { ], }); } - return Promise.resolve({ data: null }); + return Promise.resolve({ data: testCase }); }); }); afterEach(() => { @@ -359,13 +427,9 @@ describe("EditTestCase component", () => { } as Measure; }); - const importTestCase = (file) => { - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases", - measure - ); - const importTestBtn = screen.getByRole("button", { + const importTestCase = async (file) => { + renderWithRouter(["/measures/m1234/edit/test-cases/123"], measure); + const importTestBtn = await screen.findByRole("button", { name: "Import", }); expect(importTestBtn).toBeInTheDocument(); @@ -377,12 +441,11 @@ describe("EditTestCase component", () => { }; it("Import button is shown", async () => { - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases" - ); - const importButton = screen.queryByRole("button", { name: "Import" }); - expect(importButton).toBeInTheDocument(); + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); + const importTestBtn = await screen.findByRole("button", { + name: "Import", + }); + expect(importTestBtn).toBeInTheDocument(); }); it("should import test case file successfully", async () => { @@ -398,7 +461,7 @@ describe("EditTestCase component", () => { }, }); - importTestCase(file); + await importTestCase(file); await waitFor(() => { expect(screen.getByTestId("success-toast")).toHaveTextContent( "Test Case JSON copied into editor. QI-Core Defaults have been added. Please review and save your Test Case." @@ -425,7 +488,7 @@ describe("EditTestCase component", () => { }, }); - importTestCase(file); + await importTestCase(file); await waitFor(() => { expect(screen.getByTestId("error-toast")).toHaveTextContent( "No test case resources were found in imported file." @@ -450,7 +513,7 @@ describe("EditTestCase component", () => { }, }); - importTestCase(file); + await importTestCase(file); await waitFor(() => { expect(screen.getByTestId("error-toast")).toHaveTextContent( "There was an error importing this file. Please contact the help desk for error code V100." @@ -467,7 +530,7 @@ describe("EditTestCase component", () => { data: "server error", }); - importTestCase(file); + await importTestCase(file); await waitFor(() => { expect(screen.getByTestId("error-toast")).toHaveTextContent( "An error occurred while importing the test case, please try again. If the error persists, please contact the help desk." @@ -475,82 +538,138 @@ describe("EditTestCase component", () => { }); }); }); +}); - // TODO: split these into separate groups - describe("EditTestCase other test cases", () => { - it("should render edit test case page", async () => { - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases" - ); +describe("EditTestCase other test cases", () => { + beforeEach(() => { + (checkUserCanEdit as jest.Mock).mockClear().mockImplementation(() => { + return true; + }); + mockedAxios.get.mockImplementation((args) => { + if (args && args.endsWith("series")) { + return Promise.resolve({ data: ["SeriesA"] }); + } else if (args && args.endsWith("resources")) { + return Promise.resolve({ + data: [ + { + id: "qicore-adverseevent", + type: "AdverseEvent", + title: "QICore AdverseEvent", + category: "Clinical.Summary", + profile: + "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-adverseevent", + }, + { + id: "qicore-medicationstatement", + type: "MedicationStatement", + title: "QICore MedicationStatement", + category: "Clinical.Medications", + profile: + "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationstatement", + }, + { + id: "qicore-claim", + type: "Claim", + title: "QICore Claim", + category: "Financial.Billing", + profile: + "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-claim", + }, + { + id: "qicore-procedure", + type: "Procedure", + title: "QICore Procedure", + category: "Clinical.Summary", + profile: + "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-procedure", + }, + ], + }); + } + return Promise.resolve({ data: testCase }); + }); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + it("should render edit test case page", async () => { + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); + + expect(screen.getByTestId("test-case-json-editor")).toBeInTheDocument(); + expect(screen.getByTestId("test-case-cql-editor")).toBeInTheDocument(); + userEvent.click(screen.getByTestId("details-tab")); + await waitFor( + () => { + expect(screen.getByTestId("test-case-title")).toBeInTheDocument(); + expect(screen.getByTestId("test-case-description")).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "Save" }) + ).toBeInTheDocument(); + }, + { timeout: 1500 } + ); + expect(screen.getByRole("button", { name: "Save" })).toBeDisabled(); + expect( + screen.getByRole("button", { name: "Discard Changes" }) + ).toBeDisabled(); + expect( + screen.getByRole("button", { name: "Discard Changes" }) + ).toBeInTheDocument(); + + userEvent.click(screen.getByTestId("measurecql-tab")); + expect(screen.getByTestId("test-case-cql-editor")).toBeInTheDocument(); + }); - expect(screen.getByTestId("test-case-json-editor")).toBeInTheDocument(); - expect(screen.getByTestId("test-case-cql-editor")).toBeInTheDocument(); - userEvent.click(screen.getByTestId("details-tab")); - await waitFor( - () => { - expect(screen.getByTestId("test-case-title")).toBeInTheDocument(); - expect( - screen.getByTestId("test-case-description") - ).toBeInTheDocument(); - expect( - screen.getByRole("button", { name: "Save" }) - ).toBeInTheDocument(); - }, - { timeout: 1500 } - ); - expect(screen.getByRole("button", { name: "Save" })).toBeDisabled(); - expect( - screen.getByRole("button", { name: "Discard Changes" }) - ).toBeDisabled(); - expect( - screen.getByRole("button", { name: "Discard Changes" }) - ).toBeInTheDocument(); + it("Navigating between elements tab and json tab", async () => { + const measure = { + ...defaultMeasure, + model: Model.QICORE_6_0_0, + } as unknown as Measure; + renderWithRouter(["/measures/m1234/edit/test-cases/123"], measure); - userEvent.click(screen.getByTestId("measurecql-tab")); - expect(screen.getByTestId("test-case-cql-editor")).toBeInTheDocument(); - }); + expect(screen.getByTestId("elements-content")).toBeInTheDocument(); + const firstResource = await screen.findByText("QICore AdverseEvent"); - it("Navigating between elements tab and json tab", async () => { - const measure = { - ...defaultMeasure, - model: Model.QICORE_6_0_0, - } as unknown as Measure; - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases", - measure - ); + expect(firstResource).toBeInTheDocument(); + expect(screen.getByText("Resources")).toBeInTheDocument(); - expect(screen.getByTestId("elements-content")).toBeInTheDocument(); - const firstResource = await screen.findByText("QICore AdverseEvent"); + userEvent.click(screen.getByTestId("json-tab")); + expect(screen.getByTestId("test-case-json-editor")).toBeInTheDocument(); + }); - expect(firstResource).toBeInTheDocument(); - expect(screen.getByText("Resources")).toBeInTheDocument(); + it("should update test case when save button is clicked", async () => { + const testCaseDescription = "TestCase123"; + mockedAxios.put.mockResolvedValue({ + data: { + ...testCase, + description: testCaseDescription, + }, + }); - userEvent.click(screen.getByTestId("json-tab")); - expect(screen.getByTestId("test-case-json-editor")).toBeInTheDocument(); + // The waitFor is to make sure the formik.values are updated with the response from API calls + await waitFor(() => { + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); }); + userEvent.click(screen.getByTestId("details-tab")); - it("should edit test case when save button is clicked", async () => { - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases" - ); - const testCaseDescription = "TestCase123"; - const testCaseTitle = "TestTitle"; - mockedAxios.post.mockResolvedValue({ - data: { - id: "testID", - createdBy: MEASURE_CREATEDBY, - description: testCaseDescription, - title: testCaseTitle, - hapiOperationOutcome: hapiOperationSuccessOutcome, - }, - }); + const descriptionInput = screen.getByTestId("test-case-description"); + expect(descriptionInput).toHaveTextContent("Test IPP description"); + userEvent.clear(descriptionInput); + userEvent.paste(descriptionInput, testCaseDescription); + expect(descriptionInput).toHaveValue(testCaseDescription); - userEvent.click(screen.getByTestId("details-tab")); + const saveButton = await screen.findByRole("button", { name: "Save" }); + userEvent.click(saveButton); + await waitFor(() => { + const debugOutput = screen.getByText("Test case updated successfully!"); + expect(debugOutput).toBeInTheDocument(); + }); + }); + it("Displaying successful message when Id is present in the JSON while editing a test case", async () => { + renderWithRouter(["/measures/m1234/edit/test-cases/1234"]); + // >> cur change + userEvent.click(screen.getByTestId("details-tab")); await testTitle("TC1"); await waitFor( @@ -561,213 +680,149 @@ describe("EditTestCase component", () => { { timeout: 1500 } ); - const saveButton = screen.getByRole("button", { name: "Save" }); - userEvent.click(saveButton); + const createBtn = screen.getByRole("button", { name: "Save" }); + userEvent.click(createBtn); - const debugOutput = await screen.findByText( - "Test case created successfully!" + const alert = await screen.findByTestId("error-toast"); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveTextContent( + "An error occurred while creating the test case." ); - expect(debugOutput).toBeInTheDocument(); + // >> curchange end + //>>>incoming changes + const testCaseDescription = "TestCase123"; + const testCaseTitle = "TestTitle"; + const testCaseJson = JSON.stringify({ + resourceType: "Bundle", + id: "43", }); - - it("Displaying successful message when Id is present in the JSON while editing a test case", async () => { - const testCase = { - id: "1234", - description: "Test IPP", - series: "SeriesA", + ///>>> incoming changes end + mockedAxios.put.mockResolvedValue({ + data: { + ...testCase, createdBy: MEASURE_CREATEDBY, - createdAt: "", - lastModifiedAt: "", - lastModifiedBy: "null", - title: "TestIPP", - name: "TestIPP", - executionStatus: "false", - json: null, - } as TestCase; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: [] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ - data: testCase, - }); - }); - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id" - ); + description: testCaseDescription, + title: testCaseTitle, + json: testCaseJson, + hapiOperationOutcome: hapiOperationSuccessOutcome, + }, + }); - const testCaseDescription = "TestCase123"; - const testCaseTitle = "TestTitle"; - const testCaseJson = JSON.stringify({ - resourceType: "Bundle", - id: "43", - }); + const editor = screen.getByTestId("test-case-json-editor"); + await waitFor(() => expect(editor).toHaveValue("")); + userEvent.paste(editor, testCaseJson); + expect(editor).toHaveValue(testCaseJson); + userEvent.click(screen.getByTestId("details-tab")); - mockedAxios.put.mockResolvedValue({ - data: { - ...testCase, - createdBy: MEASURE_CREATEDBY, - description: testCaseDescription, - title: testCaseTitle, - json: testCaseJson, - hapiOperationOutcome: hapiOperationSuccessOutcome, - }, - }); + await testTitle("TC1", true); - const editor = screen.getByTestId("test-case-json-editor"); - await waitFor(() => expect(editor).toHaveValue("")); - userEvent.paste(editor, testCaseJson); - expect(editor).toHaveValue(testCaseJson); - userEvent.click(screen.getByTestId("details-tab")); + const createBtn = await screen.findByRole("button", { + name: "Save", + }); + userEvent.click(createBtn); - await testTitle("TC1", true); + const debugOutput = await screen.findByText( + "Test case updated successfully!" + ); + expect(debugOutput).toBeInTheDocument(); + }); - const createBtn = await screen.findByRole("button", { - name: "Save", - }); - userEvent.click(createBtn); + it("Displaying successful message when Id is not present in the JSON while editing a test case", async () => { + const measure = { + id: "m1234", + createdBy: MEASURE_CREATEDBY, + testCases: [testCase], + groups: [ + { + id: "Group1_ID", + scoring: "Cohort", + population: { + initialPopulation: "Pop1", + }, + }, + ], + } as unknown as Measure; + renderWithRouter(["/measures/m1234/edit/test-cases/1234"], measure); - const debugOutput = await screen.findByText( - "Test case updated successfully!" - ); - expect(debugOutput).toBeInTheDocument(); + const testCaseDescription = "TestCase123"; + const testCaseTitle = "TestTitle"; + const testCaseJson = JSON.stringify({ + resourceType: "Bundle", }); - it("Displaying successful message when Id is not present in the JSON while editing a test case", async () => { - const testCase = { - id: "1234", - description: "Test IPP", - series: "SeriesA", - createdBy: MEASURE_CREATEDBY, - createdAt: "", - lastModifiedAt: "", - lastModifiedBy: "null", - title: "TestIPP", - name: "TestIPP", - executionStatus: "false", - json: null, - } as TestCase; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: [] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ - data: testCase, - }); - }); - const measure = { - id: "m1234", + mockedAxios.put.mockResolvedValue({ + data: { + ...testCase, createdBy: MEASURE_CREATEDBY, - testCases: [testCase], - groups: [ - { - id: "Group1_ID", - scoring: "Cohort", - population: { - initialPopulation: "Pop1", - }, - }, - ], - } as unknown as Measure; - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id", - measure - ); + description: testCaseDescription, + title: testCaseTitle, + json: testCaseJson, + hapiOperationOutcome: hapiOperationSuccessOutcome, + }, + }); - const testCaseDescription = "TestCase123"; - const testCaseTitle = "TestTitle"; - const testCaseJson = JSON.stringify({ - resourceType: "Bundle", - }); + const editor = screen.getByTestId("test-case-json-editor"); + await waitFor(() => expect(editor).toHaveValue("")); + userEvent.click(screen.getByTestId("details-tab")); + userEvent.paste(editor, testCaseJson); + expect(editor).toHaveValue(testCaseJson); - mockedAxios.put.mockResolvedValue({ - data: { - ...testCase, - createdBy: MEASURE_CREATEDBY, - description: testCaseDescription, - title: testCaseTitle, - json: testCaseJson, - hapiOperationOutcome: hapiOperationSuccessOutcome, - }, - }); + await testTitle("TC1", true); - const editor = screen.getByTestId("test-case-json-editor"); - await waitFor(() => expect(editor).toHaveValue("")); - userEvent.click(screen.getByTestId("details-tab")); - userEvent.paste(editor, testCaseJson); - expect(editor).toHaveValue(testCaseJson); + expect(screen.getByRole("button", { name: "Save" })).not.toBeDisabled(); + expect( + screen.getByRole("button", { name: "Discard Changes" }) + ).not.toBeDisabled(); + const createBtn = await screen.findByRole("button", { + name: "Save", + }); + userEvent.click(createBtn); - await testTitle("TC1", true); + const debugOutput = await screen.findByText( + "Test case updated successfully!" + ); + expect(debugOutput).toBeInTheDocument(); + }); - expect(screen.getByRole("button", { name: "Save" })).not.toBeDisabled(); - expect( - screen.getByRole("button", { name: "Discard Changes" }) - ).not.toBeDisabled(); - const createBtn = await screen.findByRole("button", { - name: "Save", - }); - userEvent.click(createBtn); - - const debugOutput = await screen.findByText( - "Test case updated successfully!" - ); - expect(debugOutput).toBeInTheDocument(); + it("should provide user alert when edit test case fails", async () => { + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); + const testCaseDescription = "TestCase123"; + mockedAxios.put.mockRejectedValue({ + data: { + error: "Random error", + }, }); - it("should provide user alert when edit test case fails", async () => { - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases" - ); - const testCaseDescription = "TestCase123"; - mockedAxios.post.mockRejectedValue({ - data: { - error: "Random error", - }, - }); - - userEvent.click(screen.getByTestId("details-tab")); - await testTitle("TC1"); + userEvent.click(screen.getByTestId("details-tab")); + await testTitle("TestIPPTC1", true); - await waitFor( - () => { - const descriptionInput = screen.getByTestId("test-case-description"); - userEvent.type(descriptionInput, testCaseDescription); - }, - { timeout: 1500 } - ); + await waitFor( + () => { + const descriptionInput = screen.getByTestId("test-case-description"); + userEvent.type(descriptionInput, testCaseDescription); + }, + { timeout: 1500 } + ); - const createBtn = screen.getByRole("button", { name: "Save" }); - userEvent.click(createBtn); + const createBtn = screen.getByRole("button", { name: "Save" }); + userEvent.click(createBtn); - const alert = await screen.findByTestId("error-toast"); - expect(alert).toBeInTheDocument(); - expect(alert).toHaveTextContent( - "An error occurred while creating the test case." - ); - }); + const alert = await screen.findByTestId("create-test-case-alert"); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveTextContent( + "An error occurred while updating the test case." + ); + }); - it("should provide user alert for a success result but response is missing ID attribute", async () => { - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases" - ); - const testCaseDescription = "TestCase123"; - mockedAxios.post.mockResolvedValue({ - data: `The requested URL was rejected. Please contact soc. + it("should provide user alert for a success result but response is missing ID attribute", async () => { + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); + const testCaseDescription = "TestCase123"; + mockedAxios.put.mockResolvedValue({ + data: `The requested URL was rejected. Please contact soc. Your support ID is: 12345678901234567890 `, +<<<<<<< HEAD }); userEvent.click(screen.getByTestId("details-tab")); @@ -798,118 +853,39 @@ describe("EditTestCase component", () => { ) ).not.toBeInTheDocument(); }); +======= +>>>>>>> 3972138c (MAT-7667 Updated Routing to make use of useBlocker and blocker internal routing of madie-patient) }); - it("should update test case when update button is clicked", async () => { - const testCase = { - id: "1234", - createdBy: MEASURE_CREATEDBY, - description: "Test IPP", - series: "SeriesA", - json: `{"test":"test"}`, - groupPopulations: [ - { - groupId: "Group1_ID", - scoring: MeasureScoring.CONTINUOUS_VARIABLE, - populationValues: [ - { - name: PopulationType.INITIAL_POPULATION, - expected: true, - actual: false, - }, - { - name: PopulationType.MEASURE_POPULATION, - expected: true, - actual: false, - }, - ], - }, - ], - } as unknown as TestCase; - const testCaseDescription = "modified description"; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); - const measure = { - id: "m1234", - createdBy: MEASURE_CREATEDBY, - testCases: [testCase], - groups: [ - { - id: "Group1_ID", - scoring: "Cohort", - population: { - initialPopulation: "Pop1", - }, - }, - ], - } as unknown as Measure; - - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id", - measure - ); - mockedAxios.put.mockResolvedValue({ - data: { - ...testCase, - description: testCaseDescription, - hapiOperationOutcome: hapiOperationSuccessOutcome, - }, - }); - - userEvent.click(screen.getByTestId("details-tab")); - await waitFor(() => { - expect( - screen.getByRole("button", { name: "Save" }) - ).toBeInTheDocument(); - }); - - const seriesInput = screen - .getByTestId("test-case-series") - .querySelector("input"); - expect(seriesInput).toHaveValue("SeriesA"); - - const descriptionInput = screen.getByTestId("test-case-description"); - expect(descriptionInput).toHaveTextContent(testCase.description); - userEvent.type( - descriptionInput, - `{selectall}{del}${testCaseDescription}` - ); + userEvent.click(screen.getByTestId("details-tab")); + await testTitle("TC1", true); - userEvent.click(seriesInput); - const list = await screen.findByRole("listbox"); - expect(list).toBeInTheDocument(); - const listItems = within(list).getAllByRole("option"); - expect(listItems[1]).toHaveTextContent("SeriesB"); - userEvent.click(listItems[1]); + await waitFor( + () => { + const descriptionInput = screen.getByTestId("test-case-description"); + userEvent.type(descriptionInput, testCaseDescription); + }, + { timeout: 1500 } + ); - userEvent.click(screen.getByTestId("details-tab")); - await testTitle("TC1"); - await waitFor(() => { - expect(screen.getByRole("button", { name: "Save" })).toBeEnabled(); - }); - userEvent.click(screen.getByRole("button", { name: "Save" })); + const createBtn = screen.getByRole("button", { name: "Save" }); + userEvent.click(createBtn); - const debugOutput = await screen.findByText( - "Test case updated successfully!" - ); - expect(debugOutput).toBeInTheDocument(); + const alert = await screen.findByTestId("create-test-case-alert"); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveTextContent( + "An error occurred while updating the test case." + ); + }); - const calls = mockedAxios.put.mock.calls; - expect(calls).toBeTruthy(); - expect(calls[0]).toBeTruthy(); - const updatedTestCase = calls[0][1] as TestCase; - expect(updatedTestCase).toBeTruthy(); - expect(updatedTestCase.series).toEqual("SeriesB"); - expect(updatedTestCase.groupPopulations).toEqual([ + it("should update test case when update button is clicked", async () => { + const testCase = { + id: "1234", + createdBy: MEASURE_CREATEDBY, + description: "Test IPP", + series: "SeriesA", + json: `{"test":"test"}`, + groupPopulations: [ { groupId: "Group1_ID", scoring: MeasureScoring.CONTINUOUS_VARIABLE, @@ -926,8 +902,20 @@ describe("EditTestCase component", () => { }, ], }, - ]); + ], + } as unknown as TestCase; + const testCaseDescription = "modified description"; + mockedAxios.get.mockClear().mockImplementation((args) => { + if (args && args.endsWith("series")) { + return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); + } else if (args && args.endsWith("resources")) { + return Promise.resolve({ + data: [...resourceIdentifiers], + }); + } + return Promise.resolve({ data: testCase }); }); +<<<<<<< HEAD it("should clear error alert when user clicks alert close button", async () => { renderWithRouter( @@ -1144,10 +1132,167 @@ describe("EditTestCase component", () => { expect(updatedTestCase.series).toEqual("SeriesB"); expect(updatedTestCase.title).toEqual("Updated Title"); expect(updatedTestCase.groupPopulations).toEqual([ +======= + const measure = { + id: "m1234", + createdBy: MEASURE_CREATEDBY, + testCases: [testCase], + groups: [ +>>>>>>> 3972138c (MAT-7667 Updated Routing to make use of useBlocker and blocker internal routing of madie-patient) + { + id: "Group1_ID", + scoring: "Cohort", + population: { + initialPopulation: "Pop1", + }, + }, + ], + } as unknown as Measure; + + await waitFor(() => { + renderWithRouter(["/measures/m1234/edit/test-cases/1234"], measure); + }); + mockedAxios.put.mockResolvedValue({ + data: { + ...testCase, + description: testCaseDescription, + }, + }); + + userEvent.click(screen.getByTestId("details-tab")); + expect( + await screen.findByRole("button", { name: "Save" }) + ).toBeInTheDocument(); + + const series = screen.getByTestId("test-case-series"); + const seriesInput = within(series).getByRole("combobox"); + + expect(seriesInput).toHaveValue(testCase?.series); + + const descriptionInput = screen.getByTestId("test-case-description"); + expect(descriptionInput).toHaveTextContent(testCase.description); + userEvent.type(descriptionInput, `{selectall}{del}${testCaseDescription}`); + + userEvent.click(seriesInput); + const list = await screen.findByRole("listbox"); + expect(list).toBeInTheDocument(); + const listItems = within(list).getAllByRole("option"); + expect(listItems[1]).toHaveTextContent("SeriesB"); + userEvent.click(listItems[1]); + + userEvent.click(screen.getByTestId("details-tab")); + await testTitle("TC1"); + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save" })).toBeEnabled(); + }); + userEvent.click(screen.getByRole("button", { name: "Save" })); + + const debugOutput = await screen.findByText( + "Test case updated successfully!" + ); + expect(debugOutput).toBeInTheDocument(); + + const calls = mockedAxios.put.mock.calls; + expect(calls).toBeTruthy(); + expect(calls[0]).toBeTruthy(); + const updatedTestCase = calls[0][1] as TestCase; + expect(updatedTestCase).toBeTruthy(); + expect(updatedTestCase.series).toEqual("SeriesB"); + expect(updatedTestCase.groupPopulations).toEqual([ + { + groupId: "Group1_ID", + scoring: MeasureScoring.CONTINUOUS_VARIABLE, + populationValues: [ + { + name: PopulationType.INITIAL_POPULATION, + expected: true, + actual: false, + }, + { + name: PopulationType.MEASURE_POPULATION, + expected: true, + actual: false, + }, + ], + }, + ]); + }); + + it("should clear error alert when user clicks alert close button", async () => { + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); + const testCaseDescription = "TestCase123"; + mockedAxios.put.mockRejectedValue({ + data: { + error: "Random error", + }, + }); + + userEvent.click(screen.getByTestId("details-tab")); + await testTitle("TC1", true); + const descriptionInput = screen.getByTestId("test-case-description"); + userEvent.type(descriptionInput, testCaseDescription); + + const saveButton = screen.getByRole("button", { name: "Save" }); + userEvent.click(saveButton); + + const alert = await screen.findByTestId("create-test-case-alert"); + expect(alert).toHaveTextContent( + "An error occurred while updating the test case." + ); + const closeAlertBtn = screen.findByTestId("close-create-test-case-alert"); + userEvent.click(await closeAlertBtn); + + const dismissedAlert = screen.queryByRole("alert"); + expect(dismissedAlert).not.toBeInTheDocument(); + }); + + it("should load existing test case data when viewing specific test case", async () => { + const testCase = { + id: "1234", + createdBy: MEASURE_CREATEDBY, + description: "Test IPP", + json: `{"test":"test"}`, + } as TestCase; + mockedAxios.get.mockClear().mockImplementation((args) => { + if (args && args.endsWith("series")) { + return Promise.resolve({ data: ["SeriesA"] }); + } else if (args && args.endsWith("resources")) { + return Promise.resolve({ + data: [...resourceIdentifiers], + }); + } + return Promise.resolve({ data: testCase }); + }); + + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); + + userEvent.click(screen.getByTestId("details-tab")); + await waitFor( + () => { + const descriptionTextArea = screen.getByTestId("test-case-description"); + expect(descriptionTextArea).toBeInTheDocument(); + expect(descriptionTextArea).toHaveTextContent(testCase.description); + }, + { timeout: 1500 } + ); + userEvent.click(screen.getByTestId("details-tab")); + expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "Discard Changes" }) + ).toBeInTheDocument(); + }); + + it("Displaying successful message when Id is not present in the JSON while updating test case when update button is clicked", async () => { + const testCase = { + id: "1234", + createdBy: MEASURE_CREATEDBY, + description: "Test IPP", + title: "Original Title", + series: "SeriesA", + groupPopulations: [ { groupId: "Group1_ID", scoring: MeasureScoring.CONTINUOUS_VARIABLE, - populationBasis: "boolean", populationValues: [ { name: PopulationType.INITIAL_POPULATION, @@ -1161,149 +1306,138 @@ describe("EditTestCase component", () => { }, ], }, - ]); + ], + } as unknown as TestCase; + const testCaseDescription = "modified description"; + mockedAxios.get.mockClear().mockImplementation((args) => { + if (args && args.endsWith("series")) { + return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); + } else if (args && args.endsWith("resources")) { + return Promise.resolve({ + data: [...resourceIdentifiers], + }); + } + return Promise.resolve({ data: testCase }); + }); + const measure = { + id: "m1234", + createdBy: MEASURE_CREATEDBY, + testCases: [testCase], + groups: [ + { + id: "Group1_ID", + scoring: "Continuous Variable", + populationBasis: "boolean", + populations: [ + { + name: PopulationType.INITIAL_POPULATION, + definition: "Pop1", + }, + { + name: PopulationType.MEASURE_POPULATION, + definition: "Measure Population", + }, + ], + }, + ], + } as unknown as Measure; + const testCaseJson = JSON.stringify({ + resourceType: "Bundle", }); - it("Displaying successful message when Id is present in the JSON while updating test case when update button is clicked", async () => { - const testCase = { - id: "1234", - createdBy: MEASURE_CREATEDBY, - description: "Test IPP", - series: "SeriesA", - groupPopulations: [ - { - groupId: "Group1_ID", - scoring: MeasureScoring.CONTINUOUS_VARIABLE, - populationValues: [ - { - name: PopulationType.INITIAL_POPULATION, - expected: true, - actual: false, - }, - { - name: PopulationType.MEASURE_POPULATION, - expected: true, - actual: false, - }, - ], - }, - ], - } as unknown as TestCase; - const testCaseDescription = "modified description"; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); - const measure = { - id: "m1234", - createdBy: MEASURE_CREATEDBY, - testCases: [], - groups: [ - { - id: "Group1_ID", - scoring: "Continuous Variable", - populationBasis: "boolean", - populations: [ - { - name: PopulationType.INITIAL_POPULATION, - definition: "Pop1", - }, - { - name: PopulationType.MEASURE_POPULATION, - definition: "measure population", - }, - ], - }, - ], - } as unknown as Measure; - const testCaseJson = JSON.stringify({ - resourceType: "Bundle", - id: "12", - }); + renderWithRouter(["/measures/m1234/edit/test-cases/1234"], measure); - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id", - measure - ); - userEvent.click(screen.getByTestId("details-tab")); + mockedAxios.put.mockResolvedValue({ + data: { + ...testCase, + json: testCaseJson, + hapiOperationOutcome: hapiOperationSuccessOutcome, + }, + }); - mockedAxios.put.mockResolvedValue({ - data: { - ...testCase, - json: testCaseJson, - description: testCaseDescription, - hapiOperationOutcome: hapiOperationSuccessOutcome, - }, - }); + userEvent.click(screen.getByTestId("details-tab")); + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument(); + }); - userEvent.click(screen.getByTestId("details-tab")); - await waitFor(() => { - expect( - screen.getByRole("button", { name: "Save" }) - ).toBeInTheDocument(); - }); + const seriesInput = screen + .getByTestId("test-case-series") + .querySelector("input"); + expect(seriesInput).toHaveValue("SeriesA"); - await testTitle("Updated Title", true); + await testTitle("Updated Title", true); - const seriesInput = screen - .getByTestId("test-case-series") - .querySelector("input"); - expect(seriesInput).toHaveValue("SeriesA"); + const descriptionInput = screen.getByTestId("test-case-description"); + expect(descriptionInput).toHaveTextContent(testCase.description); + userEvent.type(descriptionInput, `{selectall}{del}${testCaseDescription}`); - const descriptionInput = screen.getByTestId("test-case-description"); - expect(descriptionInput).toHaveTextContent(testCase.description); - userEvent.type( - descriptionInput, - `{selectall}{del}${testCaseDescription}` - ); + userEvent.click(seriesInput); + const list = await screen.findByRole("listbox"); + expect(list).toBeInTheDocument(); + const listItems = within(list).getAllByRole("option"); + expect(listItems[1]).toHaveTextContent("SeriesB"); + userEvent.click(listItems[1]); - userEvent.click(seriesInput); - const list = await screen.findByRole("listbox"); - expect(list).toBeInTheDocument(); - const listItems = within(list).getAllByRole("option"); - expect(listItems[1]).toHaveTextContent("SeriesB"); - userEvent.click(listItems[1]); + userEvent.click(screen.getByTestId("expectoractual-tab")); - userEvent.click(screen.getByTestId("expectoractual-tab")); - const ippExpectedCb = await screen.findByTestId( - "test-population-initialPopulation-expected" - ); - expect(ippExpectedCb).toBeChecked(); + const ippExpectedCb = await screen.findByTestId( + "test-population-initialPopulation-expected" + ); + expect(ippExpectedCb).toBeChecked(); - const editor = screen.getByTestId("test-case-json-editor"); - userEvent.paste(editor, testCaseJson); - expect(editor).toHaveValue(testCaseJson); + const editor = screen.getByTestId("test-case-json-editor"); + userEvent.paste(editor, testCaseJson); + expect(editor).toHaveValue(testCaseJson); - userEvent.click(screen.getByTestId("details-tab")); - await waitFor(() => { - expect(screen.getByRole("button", { name: "Save" })).toBeEnabled(); - }); - userEvent.click(screen.getByRole("button", { name: "Save" })); + userEvent.click(screen.getByTestId("details-tab")); + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save" })).toBeEnabled(); + }); + userEvent.click(screen.getByRole("button", { name: "Save" })); - const debugOutput = await screen.findByText( - "Test case updated successfully!" - ); - expect(debugOutput).toBeInTheDocument(); + const debugOutput = await screen.findByText( + "Test case updated successfully!" + ); + expect(debugOutput).toBeInTheDocument(); + + const calls = mockedAxios.put.mock.calls; + expect(calls).toBeTruthy(); + expect(calls[0]).toBeTruthy(); + const updatedTestCase = calls[0][1] as TestCase; + expect(updatedTestCase).toBeTruthy(); + expect(updatedTestCase.series).toEqual("SeriesB"); + expect(updatedTestCase.title).toEqual("Updated Title"); + expect(updatedTestCase.groupPopulations).toEqual([ + { + groupId: "Group1_ID", + scoring: MeasureScoring.CONTINUOUS_VARIABLE, + populationBasis: "boolean", + populationValues: [ + { + name: PopulationType.INITIAL_POPULATION, + expected: true, + actual: false, + }, + { + name: PopulationType.MEASURE_POPULATION, + expected: true, + actual: false, + }, + ], + }, + ]); + }); - const calls = mockedAxios.put.mock.calls; - expect(calls).toBeTruthy(); - expect(calls[0]).toBeTruthy(); - const updatedTestCase = calls[0][1] as TestCase; - expect(updatedTestCase).toBeTruthy(); - expect(updatedTestCase.title).toEqual("Updated Title"); - expect(updatedTestCase.series).toEqual("SeriesB"); - expect(updatedTestCase.groupPopulations).toEqual([ + it("Displaying successful message when Id is present in the JSON while updating test case when update button is clicked", async () => { + const testCase = { + id: "1234", + createdBy: MEASURE_CREATEDBY, + description: "Test IPP", + series: "SeriesA", + groupPopulations: [ { groupId: "Group1_ID", scoring: MeasureScoring.CONTINUOUS_VARIABLE, - populationBasis: "boolean", populationValues: [ { name: PopulationType.INITIAL_POPULATION, @@ -1317,439 +1451,323 @@ describe("EditTestCase component", () => { }, ], }, - ]); + ], + } as unknown as TestCase; + const testCaseDescription = "modified description"; + mockedAxios.get.mockClear().mockImplementation((args) => { + if (args && args.endsWith("series")) { + return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); + } else if (args && args.endsWith("resources")) { + return Promise.resolve({ + data: [...resourceIdentifiers], + }); + } + return Promise.resolve({ data: testCase }); + }); + const measure = { + id: "m1234", + createdBy: MEASURE_CREATEDBY, + testCases: [], + groups: [ + { + id: "Group1_ID", + scoring: "Continuous Variable", + populationBasis: "boolean", + populations: [ + { + name: PopulationType.INITIAL_POPULATION, + definition: "Pop1", + }, + { + name: PopulationType.MEASURE_POPULATION, + definition: "measure population", + }, + ], + }, + ], + } as unknown as Measure; + const testCaseJson = JSON.stringify({ + resourceType: "Bundle", + id: "12", }); - it("should display an error when test case update fails", async () => { - const testCase = { - id: "1234", - title: "Original Title", - createdBy: MEASURE_CREATEDBY, - description: "Test IPP", - json: `{"test":"test"}`, - } as TestCase; - const modifiedDescription = "modified description"; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); + renderWithRouter(["/measures/m1234/edit/test-cases/1234"], measure); + userEvent.click(screen.getByTestId("details-tab")); - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id" - ); + mockedAxios.put.mockResolvedValue({ + data: { + ...testCase, + json: testCaseJson, + description: testCaseDescription, + hapiOperationOutcome: hapiOperationSuccessOutcome, + }, + }); + + userEvent.click(screen.getByTestId("details-tab")); + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument(); + }); - const axiosError: AxiosError = { - response: { - status: 404, - data: {}, - } as AxiosResponse, - toJSON: jest.fn(), - } as unknown as AxiosError; + await testTitle("Updated Title", true); - mockedAxios.put.mockClear().mockRejectedValue(axiosError); + const seriesInput = screen + .getByTestId("test-case-series") + .querySelector("input"); + expect(seriesInput).toHaveValue("SeriesA"); - userEvent.click(screen.getByTestId("details-tab")); - await waitFor(() => { - expect( - screen.getByRole("button", { name: "Save" }) - ).toBeInTheDocument(); - }); + const descriptionInput = screen.getByTestId("test-case-description"); + expect(descriptionInput).toHaveTextContent(testCase.description); + userEvent.type(descriptionInput, `{selectall}{del}${testCaseDescription}`); - const descriptionInput = screen.getByTestId("test-case-description"); - expect(descriptionInput).toHaveTextContent(testCase.description); - userEvent.type( - descriptionInput, - `{selectall}{del}${modifiedDescription}` - ); + userEvent.click(seriesInput); + const list = await screen.findByRole("listbox"); + expect(list).toBeInTheDocument(); + const listItems = within(list).getAllByRole("option"); + expect(listItems[1]).toHaveTextContent("SeriesB"); + userEvent.click(listItems[1]); - await waitFor(() => { - expect(descriptionInput).toHaveTextContent(modifiedDescription); - }); - userEvent.click(screen.getByRole("button", { name: "Save" })); + userEvent.click(screen.getByTestId("expectoractual-tab")); + const ippExpectedCb = await screen.findByTestId( + "test-population-initialPopulation-expected" + ); + expect(ippExpectedCb).toBeChecked(); - const debugOutput = await screen.findByText( - "An error occurred while updating the test case." - ); - expect(debugOutput); + const editor = screen.getByTestId("test-case-json-editor"); + userEvent.paste(editor, testCaseJson); + expect(editor).toHaveValue(testCaseJson); + + userEvent.click(screen.getByTestId("details-tab")); + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save" })).toBeEnabled(); }); + userEvent.click(screen.getByRole("button", { name: "Save" })); - it("should display an error when test case update returns no data", async () => { - const testCase = { - id: "1234", - title: "Original Title", - createdBy: MEASURE_CREATEDBY, - description: "Test IPP", - json: `{"test":"test"}`, - } as TestCase; - const modifiedDescription = "modified description"; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); + const debugOutput = await screen.findByText( + "Test case updated successfully!" + ); + expect(debugOutput).toBeInTheDocument(); + + const calls = mockedAxios.put.mock.calls; + expect(calls).toBeTruthy(); + expect(calls[0]).toBeTruthy(); + const updatedTestCase = calls[0][1] as TestCase; + expect(updatedTestCase).toBeTruthy(); + expect(updatedTestCase.title).toEqual("Updated Title"); + expect(updatedTestCase.series).toEqual("SeriesB"); + expect(updatedTestCase.groupPopulations).toEqual([ + { + groupId: "Group1_ID", + scoring: MeasureScoring.CONTINUOUS_VARIABLE, + populationBasis: "boolean", + populationValues: [ + { + name: PopulationType.INITIAL_POPULATION, + expected: true, + actual: false, + }, + { + name: PopulationType.MEASURE_POPULATION, + expected: true, + actual: false, + }, + ], + }, + ]); + }); - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id" - ); + it("should display an error when test case update fails", async () => { + const testCase = { + id: "1234", + title: "Original Title", + createdBy: MEASURE_CREATEDBY, + description: "Test IPP", + json: `{"test":"test"}`, + } as TestCase; + const modifiedDescription = "modified description"; + mockedAxios.get.mockClear().mockImplementation((args) => { + if (args && args.endsWith("series")) { + return Promise.resolve({ data: ["SeriesA"] }); + } else if (args && args.endsWith("resources")) { + return Promise.resolve({ + data: [...resourceIdentifiers], + }); + } + return Promise.resolve({ data: testCase }); + }); - mockedAxios.put.mockResolvedValue({ - data: null, - }); + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); - userEvent.click(screen.getByTestId("details-tab")); - await waitFor(() => { - expect( - screen.getByRole("button", { name: "Save" }) - ).toBeInTheDocument(); - }); + const axiosError: AxiosError = { + response: { + status: 404, + data: {}, + } as AxiosResponse, + toJSON: jest.fn(), + } as unknown as AxiosError; - const descriptionInput = screen.getByTestId("test-case-description"); - expect(descriptionInput).toHaveTextContent(testCase.description); - userEvent.type( - descriptionInput, - `{selectall}{del}${modifiedDescription}` - ); + mockedAxios.put.mockClear().mockRejectedValue(axiosError); - expect(screen.getByRole("button", { name: "Save" })).toBeEnabled(); + userEvent.click(screen.getByTestId("details-tab")); + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument(); + }); - await waitFor(() => { - expect(descriptionInput).toHaveTextContent(modifiedDescription); - }); - userEvent.click(screen.getByTestId("details-tab")); - userEvent.click(screen.getByRole("button", { name: "Save" })); + const descriptionInput = screen.getByTestId("test-case-description"); + expect(descriptionInput).toHaveTextContent(testCase.description); + userEvent.type(descriptionInput, `{selectall}{del}${modifiedDescription}`); - const debugOutput = await screen.findByText( - "An error occurred while updating the test case." - ); - expect(debugOutput); + await waitFor(() => { + expect(descriptionInput).toHaveTextContent(modifiedDescription); }); + userEvent.click(screen.getByRole("button", { name: "Save" })); - it("should ignore supplied changes when cancel button is clicked during test case edit", async () => { - const testCase = { - id: "1234", - title: "Original Title", - createdBy: MEASURE_CREATEDBY, - description: "Test IPP", - json: `{"test":"test"}`, - } as TestCase; - const modifiedDescription = "modified description"; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); - - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id" - ); - - userEvent.click(screen.getByTestId("details-tab")); - await waitFor(() => { - expect( - screen.getByRole("button", { name: "Save" }) - ).toBeInTheDocument(); - }); - - const descriptionInput = screen.getByTestId("test-case-description"); - expect(descriptionInput).toHaveTextContent(testCase.description); - userEvent.type( - descriptionInput, - `{selectall}{del}${modifiedDescription}` - ); + const debugOutput = await screen.findByText( + "An error occurred while updating the test case." + ); + expect(debugOutput); + }); - await waitFor(() => { - expect(descriptionInput).toHaveTextContent(modifiedDescription); - }); - userEvent.click(screen.getByRole("button", { name: "Discard Changes" })); - expect(mockedAxios.put).toBeCalledTimes(0); + it("should display an error when test case update returns no data", async () => { + const testCase = { + id: "1234", + title: "Original Title", + createdBy: MEASURE_CREATEDBY, + description: "Test IPP", + json: `{"test":"test"}`, + } as TestCase; + const modifiedDescription = "modified description"; + mockedAxios.get.mockClear().mockImplementation((args) => { + if (args && args.endsWith("series")) { + return Promise.resolve({ data: ["SeriesA"] }); + } else if (args && args.endsWith("resources")) { + return Promise.resolve({ + data: [...resourceIdentifiers], + }); + } + return Promise.resolve({ data: testCase }); }); - it("should generate field level error for test case description more than 250 characters", async () => { - const testCase = { - id: "1234", - title: "Original Title", - createdBy: MEASURE_CREATEDBY, - description: "Test IPP", - json: `{"test":"test"}`, - } as TestCase; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); - - const measure = { - id: "m1234", - createdBy: MEASURE_CREATEDBY, - testCases: [testCase], - groups: [ - { - id: "Group1_ID", - scoring: "Cohort", - population: { - initialPopulation: "Pop1", - }, - }, - ], - } as unknown as Measure; + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id", - measure - ); + mockedAxios.put.mockResolvedValue({ + data: null, + }); - userEvent.click(screen.getByTestId("details-tab")); - const testCaseDescription = - "abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyz"; - const descriptionInput = screen.getByTestId("test-case-description"); - userEvent.type(descriptionInput, testCaseDescription); + userEvent.click(screen.getByTestId("details-tab")); + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument(); + }); - fireEvent.blur(descriptionInput); + const descriptionInput = screen.getByTestId("test-case-description"); + expect(descriptionInput).toHaveTextContent(testCase.description); + userEvent.type(descriptionInput, `{selectall}{del}${modifiedDescription}`); - testTitle("TC1"); + expect(screen.getByRole("button", { name: "Save" })).toBeEnabled(); - const createBtn = screen.getByRole("button", { name: "Save" }); - await waitFor(() => { - expect(createBtn).toBeDisabled; - expect( - screen.getByTestId("test-case-description-helper-text") - ).toHaveTextContent( - "Test Case Description cannot be more than 250 characters." - ); - }); + await waitFor(() => { + expect(descriptionInput).toHaveTextContent(modifiedDescription); }); + userEvent.click(screen.getByTestId("details-tab")); + userEvent.click(screen.getByRole("button", { name: "Save" })); - it("should allow special characters for test case description", async () => { - const testCaseDescription = - "{{[[{shift}{ctrl/}a{/shift}~!@#$% ^&*() _-+= }|] \\ :;,. <>?/ '\""; - const testCaseTitle = "TestTitle"; + const debugOutput = await screen.findByText( + "An error occurred while updating the test case." + ); + expect(debugOutput); + }); - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases" - ); + it("should ignore supplied changes when cancel button is clicked during test case edit", async () => { + const modifiedDescription = "modified description"; + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); - // mock update to test case - mockedAxios.post.mockResolvedValue({ - data: { - id: "testID", - createdBy: MEASURE_CREATEDBY, - description: testCaseDescription, - title: testCaseTitle, - hapiOperationOutcome: hapiOperationSuccessOutcome, - }, - }); + userEvent.click(screen.getByTestId("details-tab")); + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument(); + }); - userEvent.click(screen.getByTestId("details-tab")); + const descriptionInput = screen.getByTestId("test-case-description"); + expect(descriptionInput).toHaveTextContent(testCase.description); + userEvent.type(descriptionInput, `{selectall}{del}${modifiedDescription}`); - await testTitle("TC1"); + await waitFor(() => { + expect(descriptionInput).toHaveTextContent(modifiedDescription); + }); + userEvent.click(screen.getByRole("button", { name: "Discard Changes" })); + expect(mockedAxios.put).toBeCalledTimes(0); + }); - // description with special characters is added - await waitFor( - () => { - const descriptionInput = screen.getByTestId("test-case-description"); - userEvent.type(descriptionInput, testCaseDescription); + it("should generate field level error for test case description more than 250 characters", async () => { + const measure = { + id: "m1234", + createdBy: MEASURE_CREATEDBY, + testCases: [testCase], + groups: [ + { + id: "Group1_ID", + scoring: "Cohort", + population: { + initialPopulation: "Pop1", + }, }, - { timeout: 1500 } - ); + ], + } as unknown as Measure; - const saveButton = screen.getByRole("button", { name: "Save" }); - userEvent.click(saveButton); + renderWithRouter(["/measures/m1234/edit/test-cases/1234"], measure); - const debugOutput = await screen.findByText( - "Test case created successfully!" - ); - expect(debugOutput).toBeInTheDocument(); - }); + userEvent.click(screen.getByTestId("details-tab")); + const testCaseDescription = + "abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxyz"; + const descriptionInput = screen.getByTestId("test-case-description"); + userEvent.type(descriptionInput, testCaseDescription); - it("should display an error when test case series fail to load", async () => { - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.startsWith(serviceConfig.measureService.baseUrl)) { - return Promise.resolve({ - data: { - id: "m1234", - measureScoring: MeasureScoring.COHORT, - }, - }); - } else if (args && args.endsWith("series")) { - return Promise.reject({ - status: 500, - data: null, - }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: null }); - }); + fireEvent.blur(descriptionInput); - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases" - ); + await testTitle("TC1"); - userEvent.click(screen.getByTestId("details-tab")); - const debugOutput = await screen.findByText( - "Unable to retrieve test case series, please try later." + const createBtn = screen.getByRole("button", { name: "Save" }); + await waitFor(() => { + expect(createBtn).toBeDisabled(); + expect( + screen.getByTestId("test-case-description-helper-text") + ).toHaveTextContent( + "Test Case Description cannot be more than 250 characters." ); - expect(debugOutput).toBeInTheDocument(); }); + }); - it("should display an error when measure doesn't exist fetching test case series", async () => { - const axiosError: AxiosError = { - response: { - status: 404, - data: {}, - } as AxiosResponse, - toJSON: jest.fn(), - } as unknown as AxiosError; - + it("should display an error when test case series fail to load", async () => { + mockedAxios.get.mockClear().mockImplementation((args) => { + if (args && args.startsWith(serviceConfig.measureService.baseUrl)) { + return Promise.resolve({ + data: { + id: "m1234", + measureScoring: MeasureScoring.COHORT, + }, +<<<<<<< HEAD + ], + } as unknown as Measure; + const testCaseDescription = "modified description"; mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.startsWith(serviceConfig.measureService.baseUrl)) { - return Promise.resolve({ - data: { - id: "m1234", - measureScoring: MeasureScoring.COHORT, - }, - }); - } else if (args && args.endsWith("series")) { - return Promise.reject(axiosError); + if (args && args.endsWith("series")) { + return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); } else if (args && args.endsWith("resources")) { return Promise.resolve({ data: [...resourceIdentifiers], }); } - return Promise.resolve({ data: null }); - }); - - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases" - ); - - userEvent.click(screen.getByTestId("details-tab")); - const debugOutput = await screen.findByText( - "Measure does not exist, unable to load test case series!" - ); - expect(debugOutput).toBeInTheDocument(); - }); - - it("should allow special characters for test case title", async () => { - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases" - ); - - const testCaseDescription = "Test Description"; - const testCaseTitle = - "{{[[{shift}{ctrl/}a{/shift}~!@#$% ^&*() _-+= }|] \\ :;,. <>?/ '\""; - mockedAxios.post.mockResolvedValue({ - data: { - id: "testID", - createdBy: MEASURE_CREATEDBY, - description: testCaseDescription, - title: testCaseTitle, - hapiOperationOutcome: hapiOperationSuccessOutcome, - }, - }); - - userEvent.click(screen.getByTestId("details-tab")); - await testTitle("TC1"); - - const createBtn = screen.getByRole("button", { name: "Save" }); - userEvent.click(createBtn); - - const debugOutput = await screen.findByText( - "Test case created successfully!" - ); - expect(debugOutput).toBeInTheDocument(); - }); - - it("should allow special characters for test case series", async () => { - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases" - ); - - const testCaseDescription = "Test Description"; - const testCaseSeries = - "{{[[{shift}{ctrl/}a{/shift}~!@#$% ^&*() _-+= }|] \\ :;,. <>?/ '\""; - mockedAxios.post.mockResolvedValue({ - data: { - id: "testID", - createdBy: MEASURE_CREATEDBY, - description: testCaseDescription, - series: testCaseSeries, - hapiOperationOutcome: hapiOperationSuccessOutcome, - }, + return Promise.resolve({ data: testCase }); }); - userEvent.click(screen.getByTestId("details-tab")); - await waitFor( - () => { - const seriesInput = screen.getByTestId("test-case-series"); - userEvent.type(seriesInput, testCaseSeries); - }, - { timeout: 1500 } - ); - await testTitle("TC1"); - - const createBtn = screen.getByRole("button", { name: "Save" }); - userEvent.click(createBtn); - - const debugOutput = await screen.findByText( - "Test case created successfully!" - ); - expect(debugOutput).toBeInTheDocument(); - }, 15000); - - it("should display HAPI validation errors after creating test case", async () => { - jest.useFakeTimers("modern"); - const measure = { - ...defaultMeasure, - model: Model.QICORE_6_0_0, - } as unknown as Measure; renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases", + ["/measures/m1234/edit/test-cases/1234"], + "/measures/:measureId/edit/test-cases/:id", measure ); - const testCaseDescription = "Test Description"; - const testCaseSeries = - "{{[[{shift}{ctrl/}a{/shift}~!@#$% ^&*() _-+= }|] \\ :;,. <>?/ '\""; - mockedAxios.post.mockResolvedValue({ + mockedAxios.put.mockResolvedValue({ data: { - id: "testID", - createdBy: MEASURE_CREATEDBY, + ...testCase, description: testCaseDescription, - series: testCaseSeries, hapiOperationOutcome: { code: 400, outcomeResponse: { @@ -1768,31 +1786,26 @@ describe("EditTestCase component", () => { }, }, }); - userEvent.click(screen.getByTestId("details-tab")); - await waitFor( - () => { - const seriesInput = screen.getByTestId("test-case-series"); - userEvent.type(seriesInput, testCaseSeries); - }, - { timeout: 1500 } - ); - await testTitle("TC1"); - const createBtn = screen.getByRole("button", { name: "Save" }); - userEvent.click(createBtn); + await waitFor(() => { + expect( + screen.getByRole("button", { name: "Save" }) + ).toBeInTheDocument(); + }); + await testTitle("TC1"); + const seriesInput = screen.getByTestId("test-case-description"); + userEvent.type(seriesInput, testCaseDescription); + const updateBtn = screen.getByRole("button", { name: "Save" }); + userEvent.click(updateBtn); const debugOutput = await screen.findByText( testCaseAlertToast - ? "Changes created successfully but the following error(s) were found" + ? "Changes updated successfully but the following error(s) were found" : "Test case updated successfully with errors in JSON" ); expect(debugOutput).toBeInTheDocument(); - expect(screen.queryByTestId("json-error-alert")).not.toBeInTheDocument(); // do not show JSON alert for valid JSON with HAPI FHIR validation issues - expect(screen.queryByText("JSON Failing")).not.toBeInTheDocument(); // do not show JSON alert for valid JSON with HAPI FHIR validation issues - expect(screen.getByTestId("elements-content")).toBeInTheDocument(); - const showValidationErrorsBtn = screen.getByRole("button", { name: "Validation Errors", }); @@ -1812,1172 +1825,1055 @@ describe("EditTestCase component", () => { "Error: Patient.identifier is a required field" ); expect(patientIdentifierError).toBeInTheDocument(); - }, 15000); +======= + }); + } else if (args && args.endsWith("series")) { + return Promise.reject({ + status: 500, + data: null, + }); + } else if (args && args.endsWith("resources")) { + return Promise.resolve({ + data: [...resourceIdentifiers], + }); + } + return Promise.resolve({ data: null }); +>>>>>>> 3972138c (MAT-7667 Updated Routing to make use of useBlocker and blocker internal routing of madie-patient) + }); - it("should display JSON error notification and not display QICore test case builder for invalid JSON", async () => { - jest.useFakeTimers("modern"); - const testCase = { - id: "1234", - createdBy: MEASURE_CREATEDBY, - description: "Test IPP", - series: "SeriesA", - json: `{"test":"test" BAD BAD JSON - DEFINITELY INVALID }`, - } as TestCase; + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); - const measure = { - id: "m1234", - createdBy: MEASURE_CREATEDBY, - model: Model.QICORE_6_0_0, - testCases: [], - groups: [ - { - id: "Group1_ID", - scoring: "Cohort", - population: { - initialPopulation: "Pop1", - }, - }, - ], - } as unknown as Measure; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); + userEvent.click(screen.getByTestId("details-tab")); + const debugOutput = await screen.findByText( + "Unable to retrieve test case series, please try later." + ); + expect(debugOutput).toBeInTheDocument(); + }); - await renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id", - measure - ); + it("should display an error when measure doesn't exist fetching test case series", async () => { + const axiosError: AxiosError = { + response: { + status: 404, + data: {}, + } as AxiosResponse, + toJSON: jest.fn(), + } as unknown as AxiosError; + + mockedAxios.get.mockClear().mockImplementation((args) => { + if (args && args.startsWith(serviceConfig.measureService.baseUrl)) { + return Promise.resolve({ + data: { + id: "m1234", + measureScoring: MeasureScoring.COHORT, + }, + }); + } else if (args && args.endsWith("series")) { + return Promise.reject(axiosError); + } else if (args && args.endsWith("resources")) { + return Promise.resolve({ + data: [...resourceIdentifiers], + }); + } + return Promise.resolve({ data: null }); + }); - expect(await screen.findByTestId("json-error-alert")).toBeInTheDocument(); - expect(screen.queryByTestId("elements-content")).not.toBeInTheDocument(); - expect(screen.getByText("JSON Failing")).toBeInTheDocument(); - expect( - screen.getByText( - "All JSON errors must be cleared before the UI Builder can be used." - ) - ).toBeInTheDocument(); - }, 15000); + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); - it("should display HAPI validation errors after update test case", async () => { - jest.useFakeTimers("modern"); + userEvent.click(screen.getByTestId("details-tab")); + const debugOutput = await screen.findByText( + "Measure does not exist, unable to load test case series!" + ); + expect(debugOutput).toBeInTheDocument(); + }); - const testCase = { - id: "1234", - createdBy: MEASURE_CREATEDBY, - description: "Test IPP", - series: "SeriesA", - json: `{"test":"test"}`, - } as TestCase; + it("should allow special characters for test case title", async () => { + const testCaseTitle = + "{{[[{shift}{ctrl/}a{/shift}~!@#$% ^&*() _-+= }|] \\ :;,. <>?/ '\""; + mockedAxios.put.mockResolvedValue({ + data: { + ...testCase, + title: testCaseTitle, + }, + }); + await waitFor(() => { + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); + }); + userEvent.click(screen.getByTestId("details-tab")); + await testTitle("TC1", true); - const measure = { - id: "m1234", - createdBy: MEASURE_CREATEDBY, - testCases: [], - groups: [ - { - id: "Group1_ID", - scoring: "Cohort", - population: { - initialPopulation: "Pop1", - }, - }, - ], - } as unknown as Measure; - const testCaseDescription = "modified description"; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); + const createBtn = screen.getByRole("button", { name: "Save" }); + userEvent.click(createBtn); - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id", - measure - ); + await waitFor(() => { + const debugOutput = screen.getByText("Test case updated successfully!"); + expect(debugOutput).toBeInTheDocument(); + }); + }); - mockedAxios.put.mockResolvedValue({ - data: { - ...testCase, - description: testCaseDescription, - hapiOperationOutcome: { - code: 400, - outcomeResponse: { - resourceType: "OperationOutcome", - issue: [ - { - severity: "error", - diagnostics: "Patient.name is a required field", - }, - { - severity: "error", - diagnostics: "Patient.identifier is a required field", - }, - ], - }, - }, - }, - }); - userEvent.click(screen.getByTestId("details-tab")); + it("should allow special characters for test case description", async () => { + const testCaseDescription = + "{{[[{shift}{ctrl/}a{/shift}~!@#$% ^&*() _-+= }|] \\ :;,. <>?/ '\""; + // mock update to test case + mockedAxios.put.mockResolvedValue({ + data: { + ...testCase, + description: testCaseDescription, + }, + }); + // The waitFor is to make sure the formik.values are updated with the response from API calls + await waitFor(() => { + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); + }); + userEvent.click(screen.getByTestId("details-tab")); - await waitFor(() => { - expect( - screen.getByRole("button", { name: "Save" }) - ).toBeInTheDocument(); - }); + const descriptionInput = screen.getByTestId("test-case-description"); + expect(descriptionInput).toHaveTextContent("Test IPP description"); + userEvent.clear(descriptionInput); + userEvent.paste(descriptionInput, testCaseDescription); + expect(descriptionInput).toHaveValue(testCaseDescription); - await testTitle("TC1"); - const seriesInput = screen.getByTestId("test-case-description"); - userEvent.type(seriesInput, testCaseDescription); - const updateBtn = screen.getByRole("button", { name: "Save" }); - userEvent.click(updateBtn); - const debugOutput = await screen.findByText( - testCaseAlertToast - ? "Changes updated successfully but the following error(s) were found" - : "Test case updated successfully with errors in JSON" - ); + const saveButton = await screen.findByRole("button", { name: "Save" }); + userEvent.click(saveButton); + await waitFor(() => { + const debugOutput = screen.getByText("Test case updated successfully!"); expect(debugOutput).toBeInTheDocument(); + }); + }); - const showValidationErrorsBtn = screen.getByRole("button", { - name: "Validation Errors", - }); - expect(showValidationErrorsBtn).toBeInTheDocument(); - userEvent.click(showValidationErrorsBtn); - jest.advanceTimersByTime(700); - - const validationErrorsList = await screen.findByTestId( - "json-validation-errors-list" - ); - expect(validationErrorsList).toBeInTheDocument(); - const patientNameError = await within(validationErrorsList).findByText( - "Error: Patient.name is a required field" - ); - expect(patientNameError).toBeInTheDocument(); - const patientIdentifierError = within(validationErrorsList).getByText( - "Error: Patient.identifier is a required field" - ); - expect(patientIdentifierError).toBeInTheDocument(); + it("should allow special characters for test case series", async () => { + const testCaseSeries = + "{{[[{shift}{ctrl/}a{/shift}~!@#$% ^&*() _-+= }|] \\ :;,. <>?/ '\""; + mockedAxios.put.mockResolvedValue({ + data: { + ...testCase, + series: testCaseSeries, + }, }); - it("should alert for HAPI FHIR errors", async () => { - jest.useFakeTimers("modern"); + // The waitFor is to make sure the formik.values are updated with the response from API calls + await waitFor(() => { + renderWithRouter(["/measures/m1234/edit/test-cases/123"]); + }); + userEvent.click(screen.getByTestId("details-tab")); - const testCase = { - id: "1234", - title: "Original Title", - createdBy: MEASURE_CREATEDBY, - description: "Test IPP", - series: "SeriesA", - json: `{"test":"test"}`, - } as TestCase; + const saveButton = await screen.findByRole("button", { name: "Save" }); - const measure = { - id: "m1234", - createdBy: MEASURE_CREATEDBY, - testCases: [], - groups: [ - { - id: "Group1_ID", - scoring: "Cohort", - population: { - initialPopulation: "Pop1", - }, - }, - ], - } as unknown as Measure; + const series = screen.getByTestId("test-case-series"); + const seriesInput = within(series).getByRole("combobox"); + userEvent.clear(seriesInput); + userEvent.type(seriesInput, testCaseSeries); + // The initial keywords from testCaseSeries such as shift and ctrl are being stripped out when using userEvent.type (Restricted to unit tests only) + expect(seriesInput).toHaveValue( + "{[a~!@#$% ^&*() _-+= }|] \\ :;,. <>?/ '\"" + ); - const testCaseDescription = "modified description"; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); + expect(saveButton).toBeEnabled(); + userEvent.click(saveButton); - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id", - measure - ); + await waitFor(() => { + const debugOutput = screen.getByText("Test case updated successfully!"); + expect(debugOutput).toBeInTheDocument(); + }); + }); - const data = { - ...testCase, + it("should display HAPI validation errors after creating test case", async () => { + jest.useFakeTimers("modern"); + const measure = { + ...defaultMeasure, + model: Model.QICORE_6_0_0, + } as unknown as Measure; + renderWithRouter(["/measures/m1234/edit/test-cases/123"], measure); + + const testCaseDescription = "Test Description"; + const testCaseSeries = + "{{[[{shift}{ctrl/}a{/shift}~!@#$% ^&*() _-+= }|] \\ :;,. <>?/ '\""; + mockedAxios.put.mockResolvedValue({ + data: { + id: "testID", + createdBy: MEASURE_CREATEDBY, description: testCaseDescription, + series: testCaseSeries, hapiOperationOutcome: { - code: 500, - message: "An unknown error occurred with HAPI FHIR", + code: 400, outcomeResponse: { resourceType: "OperationOutcome", - text: "Error: Bad things happened", issue: [ { severity: "error", - diagnostics: "Bad things happened", + diagnostics: "Patient.name is a required field", + }, + { + severity: "error", + diagnostics: "Patient.identifier is a required field", }, ], }, }, - }; + }, + }); - mockedAxios.put.mockResolvedValue({ - data, - }); + userEvent.click(screen.getByTestId("details-tab")); + await waitFor( + () => { + const seriesInput = screen.getByTestId("test-case-series"); + userEvent.type(seriesInput, testCaseSeries); + }, + { timeout: 1500 } + ); + await testTitle("TC1", true); - userEvent.click(screen.getByTestId("details-tab")); + const createBtn = screen.getByRole("button", { name: "Save" }); + userEvent.click(createBtn); - await waitFor(() => { - expect( - screen.getByRole("button", { name: "Save" }) - ).toBeInTheDocument(); - }); + const debugOutput = await screen.findByText( + testCaseAlertToast + ? "Changes created successfully but the following error(s) were found" + : "Test case updated successfully with errors in JSON" + ); + expect(debugOutput).toBeInTheDocument(); - const tcTitle = await screen.findByTestId("test-case-title"); - expect(tcTitle).toBeInTheDocument(); - userEvent.type(tcTitle, "TC1"); - await waitFor(() => { - expect(tcTitle).toHaveValue("TC1"); - }); - const seriesInput = screen.getByTestId("test-case-description"); - userEvent.type(seriesInput, testCaseDescription); - const updateBtn = screen.getByRole("button", { name: "Save" }); - userEvent.click(updateBtn); + expect(screen.queryByTestId("json-error-alert")).not.toBeInTheDocument(); // do not show JSON alert for valid JSON with HAPI FHIR validation issues + expect(screen.queryByText("JSON Failing")).not.toBeInTheDocument(); // do not show JSON alert for valid JSON with HAPI FHIR validation issues + expect(screen.getByTestId("elements-content")).toBeInTheDocument(); - const debugOutput = await screen.findByText( - testCaseAlertToast - ? "Changes updated successfully but the following error(s) were found" - : "Test case updated successfully with errors in JSON" - ); - expect(debugOutput).toBeInTheDocument(); + const showValidationErrorsBtn = screen.getByRole("button", { + name: "Validation Errors", + }); + expect(showValidationErrorsBtn).toBeInTheDocument(); + userEvent.click(showValidationErrorsBtn); + jest.advanceTimersByTime(700); - const showValidationErrorsBtn = screen.getByRole("button", { - name: "Validation Errors", - }); - expect(showValidationErrorsBtn).toBeInTheDocument(); - userEvent.click(showValidationErrorsBtn); - jest.advanceTimersByTime(700); + const validationErrorsList = await screen.findByTestId( + "json-validation-errors-list" + ); + expect(validationErrorsList).toBeInTheDocument(); + const patientNameError = await within(validationErrorsList).findByText( + "Error: Patient.name is a required field" + ); + expect(patientNameError).toBeInTheDocument(); + const patientIdentifierError = within(validationErrorsList).getByText( + "Error: Patient.identifier is a required field" + ); + expect(patientIdentifierError).toBeInTheDocument(); + }, 15000); + + it("should display JSON error notification and not display QICore test case builder for invalid JSON", async () => { + jest.useFakeTimers("modern"); + const testCase = { + id: "1234", + createdBy: MEASURE_CREATEDBY, + description: "Test IPP", + series: "SeriesA", + json: `{"test":"test" BAD BAD JSON - DEFINITELY INVALID }`, + } as TestCase; + + const measure = { + id: "m1234", + createdBy: MEASURE_CREATEDBY, + model: Model.QICORE_6_0_0, + testCases: [], + groups: [ + { + id: "Group1_ID", + scoring: "Cohort", + population: { + initialPopulation: "Pop1", + }, + }, + ], + } as unknown as Measure; + mockedAxios.get.mockClear().mockImplementation((args) => { + if (args && args.endsWith("series")) { + return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); + } else if (args && args.endsWith("resources")) { + return Promise.resolve({ + data: [...resourceIdentifiers], + }); + } + return Promise.resolve({ data: testCase }); + }); - const validationErrorsList = await screen.findByTestId( - "json-validation-errors-list" - ); - expect(validationErrorsList).toBeInTheDocument(); - const noErrors = await within(validationErrorsList).findByText( - data.hapiOperationOutcome.outcomeResponse.text - ); - expect(noErrors).toBeInTheDocument(); + renderWithRouter(["/measures/m1234/edit/test-cases/1234"], measure); + + expect(await screen.findByTestId("json-error-alert")).toBeInTheDocument(); + expect(screen.queryByTestId("elements-content")).not.toBeInTheDocument(); + expect(screen.getByText("JSON Failing")).toBeInTheDocument(); + expect( + screen.getByText( + "All JSON errors must be cleared before the UI Builder can be used." + ) + ).toBeInTheDocument(); + }, 15000); + + it("should display HAPI validation errors after update test case", async () => { + jest.useFakeTimers("modern"); + + const measure = { + id: "m1234", + createdBy: MEASURE_CREATEDBY, + testCases: [], + groups: [ + { + id: "Group1_ID", + scoring: "Cohort", + population: { + initialPopulation: "Pop1", + }, + }, + ], + } as unknown as Measure; + const testCaseDescription = "modified description"; - const closeValidationErrorsBtn = await screen.getByRole("button", { - name: "Validation Errors", - }); - expect(closeValidationErrorsBtn).toBeInTheDocument(); - userEvent.click(closeValidationErrorsBtn); - jest.advanceTimersByTime(700); - const sideButton = await screen.findByTestId( - "closed-json-validation-errors-aside" - ); - expect(sideButton).toBeInTheDocument(); - const errorText = screen.queryByText( - "data.hapiOperationOutcome.outcomeResponse.text" - ); - expect(errorText).not.toBeInTheDocument(); - expect(mockEditor.resize).toHaveBeenCalledTimes(2); - }); + renderWithRouter(["/measures/m1234/edit/test-cases/1234"], measure); - it("should handle displaying a test case with null groupPopulation data", async () => { - const testCase = { - id: "1234", - description: "Test IPP", - series: "SeriesA", - createdBy: MEASURE_CREATEDBY, - createdAt: "", - lastModifiedAt: "", - lastModifiedBy: "null", - json: '{ "resourceType": "Bundle", "type": "collection", "entry": [] }', - title: "TestIPP", - name: "TestIPP", - executionStatus: "false", - hapiOperationOutcome: {} as HapiOperationOutcome, - } as TestCase; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.startsWith(serviceConfig.measureService.baseUrl)) { - return Promise.resolve({ - data: { - id: "m1234", - measureScoring: MeasureScoring.CONTINUOUS_VARIABLE, - groups: [ - { - id: "Group1_ID", - scoring: "Cohort", - population: { - initialPopulation: "Pop1", - }, - }, - ], - }, - }); - } else if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); - - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id" - ); - - userEvent.click(screen.getByTestId("expectoractual-tab")); - const ippRow = await screen.findByTestId( - "test-row-population-id-initialPopulation" - ); - expect(ippRow).toBeInTheDocument(); + mockedAxios.put.mockResolvedValue({ + data: { + ...testCase, + description: testCaseDescription, + hapiOperationOutcome: { + code: 400, + outcomeResponse: { + resourceType: "OperationOutcome", + issue: [ + { + severity: "error", + diagnostics: "Patient.name is a required field", + }, + { + severity: "error", + diagnostics: "Patient.identifier is a required field", + }, + ], + }, + }, + }, }); + userEvent.click(screen.getByTestId("details-tab")); + + expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument(); + await testTitle("TC1"); + const seriesInput = screen.getByTestId("test-case-description"); + userEvent.type(seriesInput, testCaseDescription); + const updateBtn = screen.getByRole("button", { name: "Save" }); + userEvent.click(updateBtn); + + const debugOutput = await screen.findByText( + testCaseAlertToast + ? "Changes updated successfully but the following error(s) were found" + : "Test case updated successfully with errors in JSON" + ); + expect(debugOutput).toBeInTheDocument(); - it("should show message and disable run button when no groups are present", async () => { - const testCase = { - id: "1234", - description: "Test IPP", - series: "SeriesA", - createdBy: MEASURE_CREATEDBY, - createdAt: "", - lastModifiedAt: "", - lastModifiedBy: "null", - json: '{ "resourceType": "Bundle", "type": "collection", "entry": [] }', - groupPopulations: [], - title: "TestIPP", - name: "TestIPP", - executionStatus: "false", - hapiOperationOutcome: {} as HapiOperationOutcome, - } as TestCase; + const showValidationErrorsBtn = screen.getByRole("button", { + name: "Validation Errors", + }); + expect(showValidationErrorsBtn).toBeInTheDocument(); + userEvent.click(showValidationErrorsBtn); + jest.advanceTimersByTime(700); - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); - const measure = { ...defaultMeasure, groups: null }; - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id", - measure - ); + const validationErrorsList = await screen.findByTestId( + "json-validation-errors-list" + ); + expect(validationErrorsList).toBeInTheDocument(); + const patientNameError = await within(validationErrorsList).findByText( + "Error: Patient.name is a required field" + ); + expect(patientNameError).toBeInTheDocument(); + const patientIdentifierError = within(validationErrorsList).getByText( + "Error: Patient.identifier is a required field" + ); + expect(patientIdentifierError).toBeInTheDocument(); + }); - userEvent.click(screen.getByTestId("expectoractual-tab")); - const errorMessage = await screen.findByText( - "No data for current scoring. Please make sure at least one measure group has been created." - ); - expect(errorMessage).toBeInTheDocument(); - }); + it("should alert for HAPI FHIR errors", async () => { + jest.useFakeTimers("modern"); - it("showing the error message in the measure cql tab when there are errors in the cql", async () => { - const measure = { ...defaultMeasure, cqlErrors: true }; - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id", - measure - ); + const measure = { + id: "m1234", + createdBy: MEASURE_CREATEDBY, + testCases: [], + groups: [ + { + id: "Group1_ID", + scoring: "Cohort", + population: { + initialPopulation: "Pop1", + }, + }, + ], + } as unknown as Measure; + + const testCaseDescription = "modified description"; + + renderWithRouter(["/measures/m1234/edit/test-cases/1234"], measure); + + const data = { + ...testCase, + description: testCaseDescription, + hapiOperationOutcome: { + code: 500, + message: "An unknown error occurred with HAPI FHIR", + outcomeResponse: { + resourceType: "OperationOutcome", + text: "Error: Bad things happened", + issue: [ + { + severity: "error", + diagnostics: "Bad things happened", + }, + ], + }, + }, + }; - expect(screen.getByTestId("test-case-json-editor")).toBeInTheDocument(); - expect( - await screen.findByText( - "An error exists with the measure CQL, please review the CQL Editor tab" - ) - ).toBeInTheDocument(); + mockedAxios.put.mockResolvedValue({ + data, }); - it("checking if cql is being shown when there are no errors in the cql", async () => { - const measure = { ...defaultMeasure, cql: "MeasureCql" }; - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id", - measure - ); + userEvent.click(screen.getByTestId("details-tab")); - expect(screen.getByTestId("test-case-json-editor")).toBeInTheDocument(); - expect(screen.getByTestId("test-case-cql-editor")).toBeInTheDocument(); - userEvent.click(screen.getByTestId("expectoractual-tab")); - userEvent.click(screen.getByTestId("measurecql-tab")); + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument(); + }); - const editor = screen.getByTestId("test-case-cql-mock-editor"); - expect(editor).toHaveValue("MeasureCql"); + const tcTitle = await screen.findByTestId("test-case-title"); + expect(tcTitle).toBeInTheDocument(); + userEvent.type(tcTitle, "TC1"); + await waitFor(() => { + expect(tcTitle).toHaveValue("TC1"); }); + const seriesInput = screen.getByTestId("test-case-description"); + userEvent.type(seriesInput, testCaseDescription); + const updateBtn = screen.getByRole("button", { name: "Save" }); + userEvent.click(updateBtn); + + const debugOutput = await screen.findByText( + testCaseAlertToast + ? "Changes updated successfully but the following error(s) were found" + : "Test case updated successfully with errors in JSON" + ); + expect(debugOutput).toBeInTheDocument(); - it("should disable run button when json string is empty", async () => { - const testCase = { - id: "1234", - description: "Test IPP", - series: "SeriesA", - createdBy: MEASURE_CREATEDBY, - createdAt: "", - lastModifiedAt: "", - lastModifiedBy: "null", - json: "{}", - groupPopulations: [], - title: "TestIPP", - name: "TestIPP", - executionStatus: "false", - hapiOperationOutcome: {} as HapiOperationOutcome, - } as TestCase; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.startsWith(serviceConfig.measureService.baseUrl)) { - return Promise.resolve({ data: simpleMeasureFixture }); - } else if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA", "SeriesB", "SeriesC"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: testCase }); - }); + const showValidationErrorsBtn = screen.getByRole("button", { + name: "Validation Errors", + }); + expect(showValidationErrorsBtn).toBeInTheDocument(); + userEvent.click(showValidationErrorsBtn); + jest.advanceTimersByTime(700); - renderWithRouter( - ["/measures/m1234/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id" - ); - userEvent.click(screen.getByTestId("details-tab")); + const validationErrorsList = await screen.findByTestId( + "json-validation-errors-list" + ); + expect(validationErrorsList).toBeInTheDocument(); + const noErrors = await within(validationErrorsList).findByText( + data.hapiOperationOutcome.outcomeResponse.text + ); + expect(noErrors).toBeInTheDocument(); - await waitFor(() => { - expect( - screen.getByRole("button", { - name: "Run Test Case", - }) - ).toBeDisabled(); - }); + const closeValidationErrorsBtn = await screen.findByRole("button", { + name: "Validation Errors", }); + expect(closeValidationErrorsBtn).toBeInTheDocument(); + userEvent.click(closeValidationErrorsBtn); + jest.advanceTimersByTime(700); + const sideButton = await screen.findByTestId( + "closed-json-validation-errors-aside" + ); + expect(sideButton).toBeInTheDocument(); + const errorText = screen.queryByText( + "data.hapiOperationOutcome.outcomeResponse.text" + ); + expect(errorText).not.toBeInTheDocument(); + expect(mockEditor.resize).toHaveBeenCalledTimes(2); + }); - it("should render 404 page", async () => { - mockedAxios.get.mockClear().mockImplementation(() => { - return Promise.reject( - new Error("Error: Request failed with status code 404") - ); - }); + it("should handle displaying a test case with null groupPopulation data", async () => { + renderWithRouter(["/measures/m1234/edit/test-cases/1234"]); - await act(async () => { - render( - - - - - - ); - }); + userEvent.click(screen.getByTestId("expectoractual-tab")); + const ippRow = await screen.findByTestId( + "test-row-population-id-initialPopulation" + ); + expect(ippRow).toBeInTheDocument(); + }); - expect(screen.getByTestId("404-page")).toBeInTheDocument(); - expect(screen.getByText("404 - Not Found!")).toBeInTheDocument(); - expect(screen.getByTestId("404-page-link")).toBeInTheDocument(); - }); + it("should show message and disable run button when no groups are present", async () => { + const measure = { ...defaultMeasure, groups: null }; + renderWithRouter(["/measures/m1234/edit/test-cases/1234"], measure); - it("should disable text input and no create or update button if measure is not shared with user", async () => { - (checkUserCanEdit as jest.Mock).mockImplementation(() => { - return false; - }); - mockedAxios.get.mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA"] }); - } - return Promise.resolve({ data: null }); - }); + userEvent.click(screen.getByTestId("expectoractual-tab")); + const errorMessage = await screen.findByText( + "No data for current scoring. Please make sure at least one measure group has been created." + ); + expect(errorMessage).toBeInTheDocument(); + }); - const measure = { ...defaultMeasure, createdBy: "AnotherUser" }; + it("showing the error message in the measure cql tab when there are errors in the cql", async () => { + const measure = { ...defaultMeasure, cqlErrors: true }; + renderWithRouter(["/measures/m1234/edit/test-cases/1234"], measure); - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases", - measure - ); + expect(screen.getByTestId("test-case-json-editor")).toBeInTheDocument(); + expect( + await screen.findByText( + "An error exists with the measure CQL, please review the CQL Editor tab" + ) + ).toBeInTheDocument(); + }); - const editor = screen.getByTestId("test-case-json-editor"); - userEvent.click(screen.getByTestId("details-tab")); - await waitFor( - () => { - expect( - screen.queryByRole("button", { name: "Save" }) - ).not.toBeInTheDocument(); - expect(screen.getByTestId("test-case-title")).toBeDisabled(); - expect(screen.getByTestId("test-case-description")).toBeDisabled(); - expect(screen.getByLabelText("Group")).toBeDisabled(); - }, - { timeout: 1500 } - ); + it("checking if cql is being shown when there are no errors in the cql", async () => { + const measure = { ...defaultMeasure, cql: "MeasureCql" }; + renderWithRouter(["/measures/m1234/edit/test-cases/1234"], measure); - expect(editor).toBeInTheDocument(); - }); + expect(screen.getByTestId("test-case-json-editor")).toBeInTheDocument(); + expect(screen.getByTestId("test-case-cql-editor")).toBeInTheDocument(); + userEvent.click(screen.getByTestId("expectoractual-tab")); + userEvent.click(screen.getByTestId("measurecql-tab")); - it("should render text input and update button if measure is shared with the user", async () => { - mockedAxios.get.mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["SeriesA"] }); - } - return Promise.resolve({ data: null }); - }); + const editor = screen.getByTestId("test-case-cql-mock-editor"); + expect(editor).toHaveValue("MeasureCql"); + }); - renderWithRouter( - ["/measures/m1234/edit/test-cases"], - "/measures/:measureId/edit/test-cases", - defaultMeasure - ); - const editor = await screen.getByTestId("test-case-json-editor"); - await userEvent.click(screen.getByTestId("details-tab")); - await waitFor( - () => { - expect(screen.queryByTestId("test-case-title")).toBeInTheDocument(); - expect( - screen.queryByTestId("test-case-description") - ).toBeInTheDocument(); - expect(screen.queryByTestId("test-case-series")).toBeInTheDocument(); - expect( - screen.queryByRole("button", { name: "Save" }) - ).toBeInTheDocument(); - }, - { timeout: 1500 } - ); - expect( - screen.queryByRole("button", { name: "Discard Changes" }) - ).toBeInTheDocument(); + it("should disable run button when json string is empty", async () => { + renderWithRouter(["/measures/m1234/edit/test-cases/1234"]); + userEvent.click(screen.getByTestId("details-tab")); - expect(editor).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByRole("button", { + name: "Run Test Case", + }) + ).toBeDisabled(); }); - it("handles checking expected values", async () => { - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["DENOM_Pass", "NUMER_Pass"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: { ...testCaseFixture } }); - }); - const measure = { ...simpleMeasureFixture, createdBy: MEASURE_CREATEDBY }; - renderWithRouter( - [ - "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", - ], - "/measures/:measureId/edit/test-cases/:id", - measure - ); - userEvent.click(screen.getByTestId("expectoractual-tab")); - //fail - const ipCheckbox = await screen.findByTestId( - "test-population-initialPopulation-expected" - ); - expect(ipCheckbox).toBeInTheDocument(); - userEvent.click(ipCheckbox); - await waitFor(() => expect(ipCheckbox).toBeChecked()); - const stratCheckbox = await screen.findByTestId( - "Strata 1-initialPopulation-expected" - ); - userEvent.click(stratCheckbox); - expect(stratCheckbox).toBeInTheDocument(); - await waitFor(() => { - expect(stratCheckbox).toBeChecked(); - }); - userEvent.click(stratCheckbox); - userEvent.click(screen.getByTestId("details-tab")); - - const tcTitle = await screen.findByTestId("test-case-title"); - userEvent.clear(tcTitle); - userEvent.type(tcTitle, "testTitle"); - await waitFor(() => expect(tcTitle).toHaveValue("testTitle")); - - const saveButton = await screen.findByRole("button", { - name: "Save", - }); - await waitFor(() => expect(saveButton).not.toBeDisabled()); - userEvent.click(saveButton); + }); - const alert = await screen.findByTestId("error-toast"); - expect(alert).toBeInTheDocument(); + it("should disable text input and no create or update button if measure is not shared with user", async () => { + (checkUserCanEdit as jest.Mock).mockImplementation(() => { + return false; }); - it("handles checking expected non-boolean values", async () => { - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["DENOM_Pass", "NUMER_Pass"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ data: { ...nonBoolTestCaseFixture } }); - }); - const measure = { - ...multiGroupMeasureFixture, - createdBy: MEASURE_CREATEDBY, - }; - renderWithRouter( - [ - "/measures/623cacebe74613783378c17b/edit/test-cases/631f98927e7cb7651b971d1d", - ], - "/measures/:measureId/edit/test-cases/:id", - measure - ); - userEvent.click(screen.getByTestId("expectoractual-tab")); + const measure = { ...defaultMeasure, createdBy: "AnotherUser" }; - const ipInput = await screen.findByTestId( - "test-population-initialPopulation-expected" - ); - expect(ipInput).toBeInTheDocument(); - userEvent.clear(ipInput); - userEvent.type(ipInput, "BAD"); - await waitFor(() => expect(ipInput).toHaveValue("BAD")); - await waitFor(() => - expect( - screen.getByText( - "Only positive numeric values can be entered in the expected values" - ) - ).toBeInTheDocument() - ); + renderWithRouter(["/measures/m1234/edit/test-cases/123"], measure); - userEvent.click(screen.getByTestId("details-tab")); + const editor = screen.getByTestId("test-case-json-editor"); + userEvent.click(screen.getByTestId("details-tab")); + await waitFor( + () => { + expect( + screen.queryByRole("button", { name: "Save" }) + ).not.toBeInTheDocument(); + expect(screen.getByTestId("test-case-title")).toBeDisabled(); + expect(screen.getByTestId("test-case-description")).toBeDisabled(); + expect(screen.getByLabelText("Group")).toBeDisabled(); + }, + { timeout: 1500 } + ); - const tcTitle = await screen.findByTestId("test-case-title"); - userEvent.clear(tcTitle); - userEvent.type(tcTitle, "testTitle"); - await waitFor(() => expect(tcTitle).toHaveValue("testTitle")); + expect(editor).toBeInTheDocument(); + }); - const saveButton = await screen.findByRole("button", { - name: "Save", - }); - await waitFor(() => expect(saveButton).toBeDisabled()); - }); + it("should render text input and update button if measure is shared with the user", async () => { + renderWithRouter(["/measures/m1234/edit/test-cases/123"], defaultMeasure); + const editor = screen.getByTestId("test-case-json-editor"); + userEvent.click(screen.getByTestId("details-tab")); + await waitFor( + () => { + expect(screen.queryByTestId("test-case-title")).toBeInTheDocument(); + expect( + screen.queryByTestId("test-case-description") + ).toBeInTheDocument(); + expect(screen.queryByTestId("test-case-series")).toBeInTheDocument(); + expect( + screen.queryByRole("button", { name: "Save" }) + ).toBeInTheDocument(); + }, + { timeout: 1500 } + ); + expect( + screen.queryByRole("button", { name: "Discard Changes" }) + ).toBeInTheDocument(); - it("executes a test case and shows the errors for invalid test case json", async () => { - mockedAxios.post.mockResolvedValue({ - data: { - code: 200, - message: null, - successful: true, - outcomeResponse: { - resourceType: "OperationOutcome", - issue: [ - { - severity: "informational", - code: "processing", - diagnostics: "No issues!", - }, - ], - }, - }, - }); - const testCase = { - id: "1234", - description: "Test IPP", - series: "SeriesA", - createdBy: MEASURE_CREATEDBY, - createdAt: "", - lastModifiedAt: "", - lastModifiedBy: "null", - title: "TestIPP", - name: "TestIPP", - executionStatus: "false", - json: '{ "resourceType": "Bundle", "type": "collection", "entry": [] }', - } as TestCase; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("/bundle")) { - return Promise.resolve({ - data: buildMeasureBundle(simpleMeasureFixture), - }); - } else if ( - args && - args.startsWith(serviceConfig.measureService.baseUrl) - ) { - return Promise.resolve({ data: simpleMeasureFixture }); - } else if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["DENOM_Pass", "NUMER_Pass"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ - data: testCase, - }); - }); - renderWithRouter( - [ - "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", - ], - "/measures/:measureId/edit/test-cases/:id" - ); - userEvent.click(screen.getByTestId("details-tab")); - // this is to make form dirty so that run test button is enabled - const tcTitle = await screen.findByTestId("test-case-title"); - userEvent.type(tcTitle, "testTitle"); - const runTestButton = screen.getByRole("button", { - name: "Run Test Case", - }); - expect(runTestButton).not.toBeDisabled(); - userEvent.click(runTestButton); + expect(editor).toBeInTheDocument(); + }); - userEvent.click(screen.getByTestId("highlighting-tab")); - const debugOutput = await screen.findByText( - "No entries found in passed patient bundles" - ); - expect(debugOutput).toBeInTheDocument(); + it("handles checking expected values", async () => { + const measure = { ...simpleMeasureFixture, createdBy: MEASURE_CREATEDBY }; + renderWithRouter( + [ + "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", + ], + measure + ); + userEvent.click(screen.getByTestId("expectoractual-tab")); + //fail + const ipCheckbox = await screen.findByTestId( + "test-population-initialPopulation-expected" + ); + expect(ipCheckbox).toBeInTheDocument(); + userEvent.click(ipCheckbox); + await waitFor(() => expect(ipCheckbox).toBeChecked()); + const stratCheckbox = await screen.findByTestId( + "Strata 1-initialPopulation-expected" + ); + userEvent.click(stratCheckbox); + expect(stratCheckbox).toBeInTheDocument(); + await waitFor(() => { + expect(stratCheckbox).toBeChecked(); }); + userEvent.click(stratCheckbox); + userEvent.click(screen.getByTestId("details-tab")); - it("executes a test case successfully when test case resources are valid", async () => { - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["DENOM_Pass", "NUMER_Pass"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ - data: { ...testCaseFixture, createdBy: MEASURE_CREATEDBY }, - }); - }); - mockedAxios.post.mockResolvedValue({ - data: { - code: 200, - message: null, - successful: true, - outcomeResponse: { - resourceType: "OperationOutcome", - issue: [ - { - severity: "informational", - code: "processing", - diagnostics: "No issues!", - }, - ], - }, - }, - }); - const measure = { ...simpleMeasureFixture, createdBy: MEASURE_CREATEDBY }; - renderWithRouter( - [ - "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", - ], - "/measures/:measureId/edit/test-cases/:id", - measure - ); - userEvent.click(screen.getByTestId("details-tab")); - - // this is to make form dirty so that run test button is enabled - const tcTitle = await screen.findByTestId("test-case-title"); - userEvent.type(tcTitle, "testTitle"); - - userEvent.click(screen.getByTestId("expectoractual-tab")); - - await waitFor(async () => { - userEvent.click( - await screen.findByRole("button", { name: "Run Test Case" }) - ); - }); - userEvent.click(screen.getByTestId("highlighting-tab")); - expect( - await screen.findByText("Population Criteria") - ).toBeInTheDocument(); + const tcTitle = await screen.findByTestId("test-case-title"); + userEvent.clear(tcTitle); + userEvent.type(tcTitle, "testTitle"); + await waitFor(() => expect(tcTitle).toHaveValue("testTitle")); - userEvent.click(screen.getByTestId("expectoractual-tab")); - expect( - await screen.findByTestId("test-population-initialPopulation-actual") - ).toBeInTheDocument(); - expect( - screen.getByTestId("test-population-numerator-actual") - ).not.toBeChecked(); + const saveButton = await screen.findByRole("button", { + name: "Save", }); + await waitFor(() => expect(saveButton).not.toBeDisabled()); + userEvent.click(saveButton); - it("disables run button when CQL return type mismatch error exists on measure", async () => { - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["DENOM_Pass", "NUMER_Pass"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ - data: { ...testCaseFixture, createdBy: MEASURE_CREATEDBY }, - }); - }); - mockedAxios.post.mockResolvedValue({ - data: { - code: 200, - message: null, - successful: true, - outcomeResponse: { - resourceType: "OperationOutcome", - issue: [ - { - severity: "informational", - code: "processing", - diagnostics: "No issues!", - }, - ], - }, - }, - }); - const measure = { - ...simpleMeasureFixture, - createdBy: MEASURE_CREATEDBY, - errors: [MeasureErrorType.MISMATCH_CQL_POPULATION_RETURN_TYPES], - }; - renderWithRouter( - [ - "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", - ], - "/measures/:measureId/edit/test-cases/:id", - measure - ); - userEvent.click(screen.getByTestId("details-tab")); + const alert = await screen.findByTestId("create-test-case-alert"); + expect(alert).toBeInTheDocument(); + }); - // this is to make form dirty so that run test button is enabled - const tcTitle = await screen.findByTestId("test-case-title"); - userEvent.type(tcTitle, "testTitle"); + it("handles checking expected non-boolean values", async () => { + const measure = { + ...multiGroupMeasureFixture, + createdBy: MEASURE_CREATEDBY, + }; + renderWithRouter( + [ + "/measures/623cacebe74613783378c17b/edit/test-cases/631f98927e7cb7651b971d1d", + ], + measure + ); + userEvent.click(screen.getByTestId("expectoractual-tab")); - userEvent.click(screen.getByTestId("expectoractual-tab")); + const ipInput = await screen.findByTestId( + "test-population-initialPopulation-expected" + ); + expect(ipInput).toBeInTheDocument(); + userEvent.clear(ipInput); + userEvent.type(ipInput, "BAD"); + await waitFor(() => expect(ipInput).toHaveValue("BAD")); + await waitFor(() => + expect( + screen.getByText( + "Only positive numeric values can be entered in the expected values" + ) + ).toBeInTheDocument() + ); - await waitFor(async () => { - expect( - await screen.findByRole("button", { name: "Run Test Case" }) - ).toBeDisabled(); - }); + userEvent.click(screen.getByTestId("details-tab")); + + const tcTitle = await screen.findByTestId("test-case-title"); + userEvent.clear(tcTitle); + userEvent.type(tcTitle, "testTitle"); + await waitFor(() => expect(tcTitle).toHaveValue("testTitle")); + + const saveButton = await screen.findByRole("button", { + name: "Save", }); + await waitFor(() => expect(saveButton).toBeDisabled()); + }); - it("displays non-boolean results", async () => { - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["DENOM_Pass", "NUMER_Pass"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } + it("executes a test case and shows the errors for invalid test case json", async () => { + mockedAxios.post.mockResolvedValue({ + data: { + code: 200, + message: null, + successful: true, + outcomeResponse: { + resourceType: "OperationOutcome", + issue: [ + { + severity: "informational", + code: "processing", + diagnostics: "No issues!", + }, + ], + }, + }, + }); + const testCase = { + id: "1234", + description: "Test IPP", + series: "SeriesA", + createdBy: MEASURE_CREATEDBY, + createdAt: "", + lastModifiedAt: "", + lastModifiedBy: "null", + title: "TestIPP", + name: "TestIPP", + executionStatus: "false", + json: '{ "resourceType": "Bundle", "type": "collection", "entry": [] }', + } as TestCase; + mockedAxios.get.mockClear().mockImplementation((args) => { + if (args && args.endsWith("/bundle")) { return Promise.resolve({ - data: { ...nonBoolTestCaseFixture, createdBy: MEASURE_CREATEDBY }, + data: buildMeasureBundle(simpleMeasureFixture), }); + } else if ( + args && + args.startsWith(serviceConfig.measureService.baseUrl) + ) { + return Promise.resolve({ data: simpleMeasureFixture }); + } else if (args && args.endsWith("series")) { + return Promise.resolve({ data: ["DENOM_Pass", "NUMER_Pass"] }); + } else if (args && args.endsWith("resources")) { + return Promise.resolve({ + data: [...resourceIdentifiers], + }); + } + return Promise.resolve({ + data: testCase, }); - mockedAxios.post.mockResolvedValue({ - data: { - code: 200, - message: null, - successful: true, - outcomeResponse: { - resourceType: "OperationOutcome", - issue: [ - { - severity: "informational", - code: "processing", - diagnostics: "No issues!", - }, - ], - }, + }); + renderWithRouter([ + "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", + ]); + userEvent.click(screen.getByTestId("details-tab")); + // this is to make form dirty so that run test button is enabled + const tcTitle = await screen.findByTestId("test-case-title"); + userEvent.type(tcTitle, "testTitle"); + const runTestButton = screen.getByRole("button", { + name: "Run Test Case", + }); + expect(runTestButton).not.toBeDisabled(); + userEvent.click(runTestButton); + + userEvent.click(screen.getByTestId("highlighting-tab")); + const debugOutput = await screen.findByText( + "No entries found in passed patient bundles" + ); + expect(debugOutput).toBeInTheDocument(); + }); + + it("executes a test case successfully when test case resources are valid", async () => { + mockedAxios.post.mockResolvedValue({ + data: { + code: 200, + message: null, + successful: true, + outcomeResponse: { + resourceType: "OperationOutcome", + issue: [ + { + severity: "informational", + code: "processing", + diagnostics: "No issues!", + }, + ], }, - }); - const measure = { - ...multiGroupMeasureFixture, - createdBy: MEASURE_CREATEDBY, - }; - renderWithRouter( - [ - "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", - ], - "/measures/:measureId/edit/test-cases/:id", - measure + }, + }); + const measure = { ...simpleMeasureFixture, createdBy: MEASURE_CREATEDBY }; + renderWithRouter( + [ + "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", + ], + measure + ); + userEvent.click(screen.getByTestId("details-tab")); + + // this is to make form dirty so that run test button is enabled + const tcTitle = await screen.findByTestId("test-case-title"); + userEvent.type(tcTitle, "testTitle"); + + userEvent.click(screen.getByTestId("expectoractual-tab")); + + await waitFor(async () => { + userEvent.click( + await screen.findByRole("button", { name: "Run Test Case" }) ); - userEvent.click(screen.getByTestId("details-tab")); + }); + userEvent.click(screen.getByTestId("highlighting-tab")); + expect(await screen.findByText("Population Criteria")).toBeInTheDocument(); + + userEvent.click(screen.getByTestId("expectoractual-tab")); + expect( + await screen.findByTestId("test-population-initialPopulation-actual") + ).toBeInTheDocument(); + expect( + screen.getByTestId("test-population-numerator-actual") + ).not.toBeChecked(); + }); - // this is to make form dirty so that run test button is enabled - const tcTitle = await screen.findByTestId("test-case-title"); - userEvent.type(tcTitle, "testTitle"); + it("disables run button when CQL return type mismatch error exists on measure", async () => { + mockedAxios.post.mockResolvedValue({ + data: { + code: 200, + message: null, + successful: true, + outcomeResponse: { + resourceType: "OperationOutcome", + issue: [ + { + severity: "informational", + code: "processing", + diagnostics: "No issues!", + }, + ], + }, + }, + }); + const measure = { + ...simpleMeasureFixture, + createdBy: MEASURE_CREATEDBY, + errors: [MeasureErrorType.MISMATCH_CQL_POPULATION_RETURN_TYPES], + }; + renderWithRouter( + [ + "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", + ], + measure + ); + userEvent.click(screen.getByTestId("details-tab")); - userEvent.click(screen.getByTestId("expectoractual-tab")); + // this is to make form dirty so that run test button is enabled + const tcTitle = await screen.findByTestId("test-case-title"); + userEvent.type(tcTitle, "testTitle"); - await waitFor(async () => { - userEvent.click( - await screen.findByRole("button", { name: "Run Test Case" }) - ); - }); - userEvent.click(screen.getByTestId("highlighting-tab")); - expect( - await screen.findByText("Population Criteria") - ).toBeInTheDocument(); + userEvent.click(screen.getByTestId("expectoractual-tab")); - userEvent.click(screen.getByTestId("expectoractual-tab")); - expect( - await screen.findByTestId("test-population-initialPopulation-actual") - ).toBeInTheDocument(); + await waitFor(async () => { expect( - screen.getByTestId("test-population-initialPopulation-expected") - ).toHaveValue("2"); + await screen.findByRole("button", { name: "Run Test Case" }) + ).toBeDisabled(); }); + }); - it("displays warning when test case execution is aborted for service error on test case JSON validation", async () => { - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["DENOM_Pass", "NUMER_Pass"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } + it("displays non-boolean results", async () => { + mockedAxios.get.mockClear().mockImplementation((args) => { + if (args && args.endsWith("series")) { + return Promise.resolve({ data: ["DENOM_Pass", "NUMER_Pass"] }); + } else if (args && args.endsWith("resources")) { return Promise.resolve({ - data: { ...testCaseFixture, createdBy: MEASURE_CREATEDBY }, + data: [...resourceIdentifiers], }); + } + return Promise.resolve({ + data: { ...nonBoolTestCaseFixture, createdBy: MEASURE_CREATEDBY }, }); - const axiosError: AxiosError = { - response: { - status: 500, - data: {}, - } as AxiosResponse, - toJSON: jest.fn(), - } as unknown as AxiosError; +<<<<<<< HEAD + await waitFor(() => expect(saveButton).not.toBeDisabled()); + userEvent.click(saveButton); - mockedAxios.post.mockClear().mockRejectedValue(axiosError); - const measure = { ...simpleMeasureFixture, createdBy: MEASURE_CREATEDBY }; - renderWithRouter( - [ - "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", - ], - "/measures/:measureId/edit/test-cases/:id", - measure - ); + const alert = await screen.findByTestId("error-toast"); + expect(alert).toBeInTheDocument(); +======= +>>>>>>> 3972138c (MAT-7667 Updated Routing to make use of useBlocker and blocker internal routing of madie-patient) + }); + mockedAxios.post.mockResolvedValue({ + data: { + code: 200, + message: null, + successful: true, + outcomeResponse: { + resourceType: "OperationOutcome", + issue: [ + { + severity: "informational", + code: "processing", + diagnostics: "No issues!", + }, + ], + }, + }, + }); + const measure = { + ...multiGroupMeasureFixture, + createdBy: MEASURE_CREATEDBY, + }; + renderWithRouter( + [ + "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", + ], + measure + ); + userEvent.click(screen.getByTestId("details-tab")); - const editor = (await screen.getByTestId( - "test-case-json-editor" - )) as HTMLInputElement; - await waitFor(() => expect(editor.value).not.toBe("Loading...")); // wait for load to complete as editor is read-only - userEvent.clear(editor); - await waitFor(() => expect(editor.value).toBe("")); - userEvent.paste( - editor, - `{ "resourceType": "BAD", "type": "collection" }` - ); - await waitFor(() => { - expect(editor.value).toBeTruthy(); - expect(editor.value.trim().length > 0).toBeTruthy(); - }); + // this is to make form dirty so that run test button is enabled + const tcTitle = await screen.findByTestId("test-case-title"); + userEvent.type(tcTitle, "testTitle"); - const runButton = await screen.findByRole("button", { - name: "Run Test Case", - }); - await waitFor(() => expect(runButton).not.toBeDisabled()); - userEvent.click(runButton); - await waitFor(async () => - userEvent.click(screen.getByTestId("highlighting-tab")) - ); + userEvent.click(screen.getByTestId("expectoractual-tab")); - const alert = await screen.findByTestId("calculation-error-alert"); - expect(alert).toBeInTheDocument(); - expect(alert).toHaveTextContent( - "Test case execution was aborted because JSON could not be validated. If this error persists, please contact the help desk." + await waitFor(async () => { + userEvent.click( + await screen.findByRole("button", { name: "Run Test Case" }) ); }); + userEvent.click(screen.getByTestId("highlighting-tab")); + expect(await screen.findByText("Population Criteria")).toBeInTheDocument(); + + userEvent.click(screen.getByTestId("expectoractual-tab")); + expect( + await screen.findByTestId("test-population-initialPopulation-actual") + ).toBeInTheDocument(); + expect( + screen.getByTestId("test-population-initialPopulation-expected") + ).toHaveValue("2"); + }); - it("displays error when test case execution is aborted due to errors validating test case JSON on new test case", async () => { - const testCase = { - id: "1234", - description: "Test IPP", - series: "SeriesA", - createdBy: MEASURE_CREATEDBY, - createdAt: "", - lastModifiedAt: "", - lastModifiedBy: "null", - title: "TestIPP", - name: "TestIPP", - executionStatus: "false", - json: null, - } as TestCase; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: [] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ - data: testCase, - }); - }); - mockedAxios.post.mockResolvedValue({ - data: { - code: 200, - message: null, - successful: false, - outcomeResponse: { - resourceType: "OperationOutcome", - issue: [ - { - severity: "error", - code: "processing", - diagnostics: "Major issue on line 1!", - }, - ], - }, - }, - }); - const measure = { ...simpleMeasureFixture, createdBy: MEASURE_CREATEDBY }; - renderWithRouter( - ["/measures/623cacebe74613783378c17b/edit/test-cases/1234"], - "/measures/:measureId/edit/test-cases/:id", - measure - ); + it("displays warning when test case execution is aborted for service error on test case JSON validation", async () => { + const axiosError: AxiosError = { + response: { + status: 500, + data: {}, + } as AxiosResponse, + toJSON: jest.fn(), + } as unknown as AxiosError; + + mockedAxios.post.mockClear().mockRejectedValue(axiosError); + const measure = { ...simpleMeasureFixture, createdBy: MEASURE_CREATEDBY }; + renderWithRouter( + [ + "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", + ], + measure + ); - const editor = (await screen.getByTestId( - "test-case-json-editor" - )) as HTMLInputElement; - await waitFor(() => expect(editor.value).toEqual("")); - userEvent.paste(editor, testCaseFixture.json); - await waitFor(() => expect(editor.value).toBeTruthy()); + const editor = (await screen.findByTestId( + "test-case-json-editor" + )) as HTMLInputElement; + await waitFor(() => expect(editor.value).not.toBe("Loading...")); // wait for load to complete as editor is read-only + userEvent.clear(editor); + await waitFor(() => expect(editor.value).toBe("")); + userEvent.paste(editor, `{ "resourceType": "BAD", "type": "collection" }`); + await waitFor(() => { + expect(editor.value).toBeTruthy(); + expect(editor.value.trim().length > 0).toBeTruthy(); + }); - const runButton = await screen.findByRole("button", { - name: "Run Test Case", - }); - userEvent.click(runButton); - await waitFor(async () => - userEvent.click(screen.getByTestId("highlighting-tab")) - ); - const alert = await screen.findByTestId("calculation-error-alert"); - expect(alert).toBeInTheDocument(); - expect(alert).toHaveTextContent( - "Test case execution was aborted due to errors with the test case JSON." - ); + const runButton = await screen.findByRole("button", { + name: "Run Test Case", }); + await waitFor(() => expect(runButton).not.toBeDisabled()); + userEvent.click(runButton); + await waitFor(async () => + userEvent.click(screen.getByTestId("highlighting-tab")) + ); - it("displays error when test case execution is aborted due to errors validating test case JSON on existing test case", async () => { - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["DENOM_Pass", "NUMER_Pass"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ - data: { ...testCaseFixture, createdBy: MEASURE_CREATEDBY }, - }); - }); - mockedAxios.post.mockResolvedValue({ - data: { - code: 200, - message: null, - successful: false, - outcomeResponse: { - resourceType: "OperationOutcome", - issue: [ - { - severity: "error", - code: "processing", - diagnostics: "Major issue on line 1!", - }, - ], - }, + const alert = await screen.findByTestId("calculation-error-alert"); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveTextContent( + "Test case execution was aborted because JSON could not be validated. If this error persists, please contact the help desk." + ); + }); + + it("displays error when test case execution is aborted due to errors validating test case JSON on new test case", async () => { + mockedAxios.post.mockResolvedValue({ + data: { + code: 200, + message: null, + successful: false, + outcomeResponse: { + resourceType: "OperationOutcome", + issue: [ + { + severity: "error", + code: "processing", + diagnostics: "Major issue on line 1!", + }, + ], }, - }); - const measure = { ...simpleMeasureFixture, createdBy: MEASURE_CREATEDBY }; - renderWithRouter( - [ - "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", - ], - "/measures/:measureId/edit/test-cases/:id", - measure - ); + }, + }); + const measure = { ...simpleMeasureFixture, createdBy: MEASURE_CREATEDBY }; + renderWithRouter( + ["/measures/623cacebe74613783378c17b/edit/test-cases/1234"], + measure + ); - const editor = (await screen.getByTestId( - "test-case-json-editor" - )) as HTMLInputElement; - await waitFor(() => expect(editor.value).not.toBe("Loading...")); // wait for load to complete as editor is read-only - userEvent.clear(editor); - await waitFor(() => expect(editor.value).toBe("")); - userEvent.paste( - editor, - `{ "resourceType": "BAD", "type": "collection" }` - ); - await waitFor(() => { - expect(editor.value).toBeTruthy(); - expect(editor.value.trim().length > 0).toBeTruthy(); - }); + const editor = (await screen.findByTestId( + "test-case-json-editor" + )) as HTMLInputElement; + await waitFor(() => expect(editor.value).toEqual("")); + userEvent.paste(editor, testCaseFixture.json); + await waitFor(() => expect(editor.value).toBeTruthy()); - const runButton = await screen.findByRole("button", { - name: "Run Test Case", - }); - await waitFor(() => expect(runButton).not.toBeDisabled()); - await waitFor(async () => userEvent.click(runButton)); + const runButton = await screen.findByRole("button", { + name: "Run Test Case", + }); + userEvent.click(runButton); + await waitFor(async () => + userEvent.click(screen.getByTestId("highlighting-tab")) + ); + const alert = await screen.findByTestId("calculation-error-alert"); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveTextContent( + "Test case execution was aborted due to errors with the test case JSON." + ); + }); - userEvent.click(screen.getByTestId("highlighting-tab")); - const alert = await screen.findByTestId("calculation-error-alert"); - expect(alert).toBeInTheDocument(); - expect(alert).toHaveTextContent( - "Test case execution was aborted due to errors with the test case JSON." - ); + it("displays error when test case execution is aborted due to errors validating test case JSON on existing test case", async () => { + mockedAxios.post.mockResolvedValue({ + data: { + code: 200, + message: null, + successful: false, + outcomeResponse: { + resourceType: "OperationOutcome", + issue: [ + { + severity: "error", + code: "processing", + diagnostics: "Major issue on line 1!", + }, + ], + }, + }, }); + const measure = { ...simpleMeasureFixture, createdBy: MEASURE_CREATEDBY }; + renderWithRouter( + [ + "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", + ], + measure + ); - it("disables button to run the test case when Measure CQL errors exist", async () => { - // measure with cqlErrors flag - const testCase = { - id: "623cacffe74613783378c17c", - description: "Test IPP", - series: "SeriesA", - createdBy: MEASURE_CREATEDBY, - createdAt: "", - lastModifiedAt: "", - lastModifiedBy: "null", - json: '{ "resourceType": "Bundle", "type": "collection", "entry": [] }', - groupPopulations: [], - title: "TestIPP", - name: "TestIPP", - executionStatus: "false", - hapiOperationOutcome: {} as HapiOperationOutcome, - } as TestCase; - mockedAxios.get.mockClear().mockImplementation((args) => { - if (args && args.endsWith("series")) { - return Promise.resolve({ data: ["DENOM_Pass", "NUMER_Pass"] }); - } else if (args && args.endsWith("resources")) { - return Promise.resolve({ - data: [...resourceIdentifiers], - }); - } - return Promise.resolve({ - data: testCase, - }); - }); - const measure = { ...simpleMeasureFixture, cqlErrors: true }; - renderWithRouter( - [ - "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", - ], - "/measures/:measureId/edit/test-cases/:id", - measure - ); - const runButton = await screen.findByRole("button", { - name: "Run Test Case", - }); - expect(runButton).toBeDisabled(); + const editor = (await screen.findByTestId( + "test-case-json-editor" + )) as HTMLInputElement; + await waitFor(() => expect(editor.value).not.toBe("Loading...")); // wait for load to complete as editor is read-only + userEvent.clear(editor); + await waitFor(() => expect(editor.value).toBe("")); + userEvent.paste(editor, `{ "resourceType": "BAD", "type": "collection" }`); + await waitFor(() => { + expect(editor.value).toBeTruthy(); + expect(editor.value.trim().length > 0).toBeTruthy(); + }); + + const runButton = await screen.findByRole("button", { + name: "Run Test Case", + }); + await waitFor(() => expect(runButton).not.toBeDisabled()); + await waitFor(async () => userEvent.click(runButton)); + + userEvent.click(screen.getByTestId("highlighting-tab")); + const alert = await screen.findByTestId("calculation-error-alert"); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveTextContent( + "Test case execution was aborted due to errors with the test case JSON." + ); + }); + + it("disables button to run the test case when Measure CQL errors exist", async () => { + // measure with cqlErrors flag + const measure = { ...simpleMeasureFixture, cqlErrors: true }; + renderWithRouter( + [ + "/measures/623cacebe74613783378c17b/edit/test-cases/623cacffe74613783378c17c", + ], + measure + ); + const runButton = await screen.findByRole("button", { + name: "Run Test Case", }); + expect(runButton).toBeDisabled(); }); }); @@ -3050,7 +2946,7 @@ describe("validator", () => { } expect(expectedError).toBeTruthy(); - expect(expectedError.message).toEqual( + expect(expectedError?.message).toEqual( "Expected value type must match population basis type" ); }); @@ -3093,7 +2989,7 @@ describe("validator", () => { } expect(expectedError).toBeTruthy(); - expect(expectedError.message).toEqual( + expect(expectedError?.message).toEqual( "Decimals values cannot be entered in the population expected values" ); }); @@ -3136,7 +3032,7 @@ describe("validator", () => { } expect(expectedError).toBeTruthy(); - expect(expectedError.message).toEqual( + expect(expectedError?.message).toEqual( "Only positive numeric values can be entered in the expected values" ); }); @@ -3181,11 +3077,6 @@ describe("findEpisodeActualValue", () => { value: 2, }, ]; - const measureGroupPop: Population = { - id: "abc", - name: PopulationType.INITIAL_POPULATION, - definition: "ipp", - }; const popValue: PopulationExpectedValue = { id: "abc", name: PopulationType.INITIAL_POPULATION, diff --git a/src/components/editTestCase/qiCore/EditTestCase.tsx b/src/components/editTestCase/qiCore/EditTestCase.tsx index 852270c43..5577e557b 100644 --- a/src/components/editTestCase/qiCore/EditTestCase.tsx +++ b/src/components/editTestCase/qiCore/EditTestCase.tsx @@ -834,6 +834,7 @@ const EditTestCase = (props: EditTestCaseProps) => { } return map; }, [measure?.groups]); + return ( ({ @@ -16,7 +37,7 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({ disconnect: jest.fn(), })); const mockedAxios = axios as jest.Mocked; -const serviceConfig: ServiceConfig = { +export const serviceConfig: ServiceConfig = { measureService: { baseUrl: "measure.url", }, @@ -32,6 +53,9 @@ const serviceConfig: ServiceConfig = { fhirElmTranslationService: { baseUrl: "fhir-elmTranslationService.com", }, + fhirService: { + baseUrl: "fhirService.com", + }, excelExportService: { baseUrl: "excelexport.com", }, @@ -42,11 +66,10 @@ const mockMeasure = { id: "m1234", model: Model.QDM_5_6, cqlLibraryName: "CM527Library", - measurementPeriodStart: "01/05/2022", - measurementPeriodEnd: "03/07/2022", + measurementPeriodStart: new Date("01/05/2022"), + measurementPeriodEnd: new Date("03/07/2022"), active: true, cqlErrors: false, - errors: ["error"], elmJson: "Fak3", groups: [ { @@ -66,7 +89,7 @@ const mockMeasure = { }, ], createdBy: MEASURE_CREATEDBY, -}; +} as Measure; jest.mock("@madie/madie-util", () => ({ useDocumentTitle: jest.fn(), @@ -95,9 +118,9 @@ jest.mock("@madie/madie-util", () => ({ subscribe: () => { return { unsubscribe: () => null }; }, - updateRouteHandlerState: () => null, - state: { canTravel: false, pendingPath: "" }, - initialState: { canTravel: false, pendingPath: "" }, + updateRouteHandlerState: () => jest.fn(), + state: { canTravel: true, pendingPath: "" }, + initialState: { canTravel: true, pendingPath: "" }, }, })); @@ -111,35 +134,59 @@ const CQMConversionMock = const useCqmConversionServiceMockResolved = { convertToCqmMeasure: jest.fn().mockResolvedValue(mockMeasure), } as unknown as CqmConversionService; - CQMConversionMock.mockImplementation(() => { return useCqmConversionServiceMockResolved; }); -describe("TestCaseRoutes", () => { +const renderComponentViaRouter = ( + initialEntry = "/measures/m1234/edit/test-cases/list-page" +) => { + return render( + + + + + + ); +}; + +describe("QDM TestCaseRoutes", () => { it("should render the test case list component", async () => { - mockedAxios.get.mockImplementation(() => { - return Promise.resolve({ - data: [ - { - id: "id1", - title: "TC12", - description: "Desc1", - series: "IPP_Pass", - status: null, - lastModifiedAt: "2024-09-10T08:40:14.382Z", - }, - ], - }); + mockedAxios.get.mockImplementation((args) => { + if ( + args && + args.startsWith( + serviceConfig.testCaseService.baseUrl + "/measures/m1234/test-cases" + ) + ) { + return Promise.resolve({ + data: [ + { + id: "id1", + title: "TC1", + description: "Desc1", + series: "IPP_Pass", + lastModifiedAt: "2024-09-10T09:19:14.382Z", + status: null, + }, + ], + }); + } else { + return null; + } }); - render( - - - - - - ); + renderComponentViaRouter("/measures/m1234/edit/test-cases/list-page"); const testCaseListTable = (await screen.findByTestId( "test-case-tbl" )) as HTMLTableElement; @@ -147,12 +194,12 @@ describe("TestCaseRoutes", () => { expect(tBody.rows.length).toBe(1); expect(tBody.rows.item(0).cells[0]).toHaveTextContent("Invalid"); expect(tBody.rows.item(0).cells[1]).toHaveTextContent("IPP_Pass"); - expect(tBody.rows.item(0).cells[2]).toHaveTextContent("TC12"); + expect(tBody.rows.item(0).cells[2]).toHaveTextContent("TC1"); expect(tBody.rows.item(0).cells[3]).toHaveTextContent("Desc1"); expect(tBody.rows.item(0).cells[4]).toHaveTextContent("09/10/2024"); expect( within(tBody.rows.item(0).cells[5]).getByRole("button", { - name: "select-action-TC12", + name: "select-action-TC1", }) ).toBeInTheDocument(); }); @@ -171,15 +218,7 @@ describe("TestCaseRoutes", () => { ], }); }); - render( - - - - - - ); + renderComponentViaRouter("/measures/m1234/edit/test-cases/list-page/sde"); expect( screen.getByTestId("sde-option-radio-buttons-group") ).toBeInTheDocument(); @@ -199,14 +238,8 @@ describe("TestCaseRoutes", () => { ], }); }); - render( - - - - - + renderComponentViaRouter( + "/measures/m1234/edit/test-cases/list-page/expansion" ); expect(screen.getByTestId("manifest-expansion-form")).toBeInTheDocument(); }); @@ -225,16 +258,8 @@ describe("TestCaseRoutes", () => { ], }); }); - render( - - - - - + renderComponentViaRouter( + "/measures/m1234/edit/test-cases/list-page/test-case-data" ); expect( await screen.findByTestId("test-case-data-form") @@ -255,13 +280,7 @@ describe("TestCaseRoutes", () => { ], }); }); - render( - - - - - - ); + renderComponentViaRouter("/measures/m1234/edit/test-cases/m1234"); expect(screen.getByTestId("edit-test-case-form")).toBeInTheDocument(); }); @@ -278,13 +297,7 @@ describe("TestCaseRoutes", () => { CQMConversionMock.mockImplementation(() => { return useCqmConversionServiceMockRejected; }); - render( - - - - - - ); + renderComponentViaRouter("/measures/m1234/edit/test-cases/m1234"); const runTestCaseButton = screen.getByRole("button", { name: "Run Test", diff --git a/src/components/routes/qdm/TestCaseRoutes.tsx b/src/components/routes/qdm/TestCaseRoutes.tsx index 80ec4d7b1..dac1f5a14 100644 --- a/src/components/routes/qdm/TestCaseRoutes.tsx +++ b/src/components/routes/qdm/TestCaseRoutes.tsx @@ -1,11 +1,11 @@ import React, { useEffect, useCallback, useRef, useState } from "react"; -import { Route, Routes } from "react-router-dom"; +import { createBrowserRouter, RouterProvider } from "react-router-dom"; import TestCaseLandingQdm from "../../testCaseLanding/qdm/TestCaseLanding"; import EditTestCase from "../../editTestCase/qdm/EditTestCase"; import NotFound from "../../notfound/NotFound"; import StatusHandler from "../../statusHandler/StatusHandler"; import { Measure, TestCaseImportOutcome } from "@madie/madie-models"; -import { measureStore, useFeatureFlags } from "@madie/madie-util"; +import { measureStore } from "@madie/madie-util"; import { CqmMeasure, ValueSet } from "cqm-models"; import useCqmConversionService from "../../../api/CqmModelConversionService"; import useTerminologyServiceApi from "../../../api/useTerminologyServiceApi"; @@ -17,13 +17,17 @@ import SDEPage from "../../testCaseConfiguration/sde/SDEPage"; import Expansion from "../../testCaseConfiguration/expansion/Expansion"; import TestCaseData from "../../testCaseConfiguration/testCaseData/TestCaseData"; -const TestCaseRoutes = () => { +const TestCaseRoutes = (props) => { + // only for Unit Tests + const { initialEntry } = props; + if (initialEntry) { + window.history.pushState({}, "", initialEntry); + } const [cqmMeasureErrors, setCqmMeasureErrors] = useState>([]); const [importWarnings, setImportWarnings] = useState( [] ); const [importErrors, setImportErrors] = useState>([]); - const featureFlags = useFeatureFlags(); const [executionContextReady, setExecutionContextReady] = useState(false); const [executing, setExecuting] = useState(); @@ -48,7 +52,7 @@ const TestCaseRoutes = () => { const getValueSetAbortController = useRef( new AbortController() ); - // arbitraty number that's just supposed to increment on abort calls + // arbitrary number that's just supposed to increment on abort calls // sole purpose is only to spin off the useEffect that typically listens for measure changes const [aborted, setAborted] = useState(0); // instantiating both at once is not the play here. @@ -169,6 +173,68 @@ const TestCaseRoutes = () => { } }; + const routesConfig = [ + { + children: [ + { + path: "/measures/:measureId/edit/test-cases", + element: , + }, + { + path: "/measures/:measureId/edit/test-cases/:id", + element: , + }, + { + path: "/measures/:measureId/edit/test-cases/list-page", + element: ( + + } + /> + ), + }, + { + path: "/measures/:measureId/edit/test-cases/list-page/:criteriaId", + element: ( + + } + /> + ), + }, + { + path: "/measures/:measureId/edit/test-cases/list-page/sde", + element: } />, + }, + { + path: "/measures/:measureId/edit/test-cases/list-page/expansion", + element: } />, + }, + { + path: "/measures/:measureId/edit/test-cases/list-page/test-case-data", + element: } />, + }, + { path: "/404", element: }, + { path: "*", element: }, + ], + }, + ]; + + const router = createBrowserRouter(routesConfig); return ( { testDataId="import-warning-messages" /> )} - + {/* { /> } /> - - - } - /> - - - } /> - } /> - + */} - } /> - + ); }; diff --git a/src/components/routes/qiCore/TestCaseRoutes.test.tsx b/src/components/routes/qiCore/TestCaseRoutes.test.tsx index 5dfbde2f6..107898d7a 100644 --- a/src/components/routes/qiCore/TestCaseRoutes.test.tsx +++ b/src/components/routes/qiCore/TestCaseRoutes.test.tsx @@ -1,13 +1,18 @@ import * as React from "react"; -import { render, screen, waitFor } from "@testing-library/react"; -import { MemoryRouter } from "react-router-dom"; +import { prettyDOM, render, screen, waitFor } from "@testing-library/react"; +import { + createMemoryRouter, + MemoryRouter, + RouterProvider, +} from "react-router-dom"; import TestCaseRoutes, { CQL_RETURN_TYPES_MISMATCH_ERROR, } from "./TestCaseRoutes"; import userEvent from "@testing-library/user-event"; import axios from "../../../api/axios-instance"; -import { ApiContextProvider, ServiceConfig } from "../../../api/ServiceContext"; +import { ApiContextProvider } from "../../../api/ServiceContext"; import { + Measure, MeasureErrorType, MeasureScoring, PopulationType, @@ -15,30 +20,20 @@ import { import { getExampleValueSet } from "../../../util/CalculationTestHelpers"; import { Bundle } from "fhir/r4"; import { act } from "react-dom/test-utils"; - +import { serviceConfig } from "../qdm/TestCaseRoutes.test"; +import { ExecutionContextProvider } from "./ExecutionContext"; +import RedirectToList from "../RedirectToList"; +import EditTestCase from "../../editTestCase/qiCore/EditTestCase"; +import TestCaseLandingWrapper from "../../testCaseLanding/common/TestCaseLandingWrapper"; +import TestCaseLanding from "../../testCaseLanding/qiCore/TestCaseLanding"; +import TestCaseData from "../../testCaseConfiguration/testCaseData/TestCaseData"; +import NotFound from "../../notfound/NotFound"; // mock the editor cause we don't care for this test and it gets rid of errors jest.mock("../../editor/Editor", () => () =>
editor contents
); jest.mock("../../../api/axios-instance"); const mockedAxios = axios as jest.Mocked; -const serviceConfig: ServiceConfig = { - qdmElmTranslationService: { baseUrl: "qdm/translator" }, - fhirElmTranslationService: { baseUrl: "fhir/translator" }, - excelExportService: { - baseUrl: "excelexport.com", - }, - measureService: { - baseUrl: "measure.url", - }, - testCaseService: { - baseUrl: "base.url", - }, - terminologyService: { - baseUrl: "something.com", - }, -}; - const MEASURE_CREATEDBY = "testuser"; const measureBundle = {} as Bundle; const valueSets = [getExampleValueSet()]; @@ -46,15 +41,15 @@ const mockMeasure = { id: "m1234", model: "QI-Core v4.1.1", cqlLibraryName: "CM527Library", - measurementPeriodStart: "01/05/2022", - measurementPeriodEnd: "03/07/2022", + measurementPeriodStart: new Date("01/05/2022"), + measurementPeriodEnd: new Date("03/07/2022"), active: true, cqlErrors: false, errors: [], elmJson: "Fak3", groups: [ { - id: null, + id: "234234234", scoring: "Cohort", populations: [ { @@ -70,7 +65,7 @@ const mockMeasure = { }, ], createdBy: MEASURE_CREATEDBY, -}; +} as Measure; jest.mock("@madie/madie-util", () => ({ useDocumentTitle: jest.fn(), @@ -97,16 +92,36 @@ jest.mock("@madie/madie-util", () => ({ return true; }), routeHandlerStore: { - subscribe: (set) => { + subscribe: () => { return { unsubscribe: () => null }; }, - updateRouteHandlerState: () => null, - state: { canTravel: false, pendingPath: "" }, - initialState: { canTravel: false, pendingPath: "" }, + updateRouteHandlerState: () => jest.fn(), + state: { canTravel: true, pendingPath: "" }, + initialState: { canTravel: true, pendingPath: "" }, }, })); - -describe("TestCaseRoutes", () => { +const renderComponentViaRouter = ( + initialEntry = "/measures/m1234/edit/test-cases/list-page" +) => { + return render( + + + + + + ); +}; +describe("Qi-Core TestCaseRoutes", () => { afterEach(() => { jest.clearAllMocks(); mockMeasure.errors = []; @@ -115,27 +130,29 @@ describe("TestCaseRoutes", () => { it("should render the landing component first", async () => { mockedAxios.get.mockImplementation((args) => { - return Promise.resolve({ - data: [ - { - id: "id1", - title: "TC1", - description: "Desc1", - series: "IPP_Pass", - lastModifiedAt: "2024-09-10T09:19:14.382Z", - status: null, - }, - ], - }); + if ( + args && + args.startsWith( + serviceConfig.testCaseService.baseUrl + "/measures/m1234/test-cases" + ) + ) { + return Promise.resolve({ + data: [ + { + id: "id1", + title: "TC1", + description: "Desc1", + series: "IPP_Pass", + lastModifiedAt: "2024-09-10T09:19:14.382Z", + status: null, + }, + ], + }); + } else { + return null; + } }); - render( - - - - - - ); - + await waitFor(() => renderComponentViaRouter()); const testCaseTitle = await screen.findByText("TC1"); expect(testCaseTitle).toBeInTheDocument(); const testCaseSeries = await screen.findByText("IPP_Pass"); @@ -162,13 +179,7 @@ describe("TestCaseRoutes", () => { ], }); }); - render( - - - - - - ); + renderComponentViaRouter(); const testCaseTitle = await screen.findByText("TC1"); expect(testCaseTitle).toBeInTheDocument(); @@ -211,13 +222,7 @@ describe("TestCaseRoutes", () => { return Promise.resolve({ data: null }); }); - render( - - - - - - ); + renderComponentViaRouter(); expect(await screen.findByTestId("code-coverage-tabs")).toBeInTheDocument(); @@ -287,13 +292,7 @@ describe("TestCaseRoutes", () => { return Promise.resolve({ data: null }); }); - render( - - - - - - ); + renderComponentViaRouter(); const testCaseTitle = await screen.findByText("TC1"); expect(testCaseTitle).toBeInTheDocument(); @@ -357,13 +356,7 @@ describe("TestCaseRoutes", () => { } }); - render( - - - - - - ); + renderComponentViaRouter(); mockedAxios.post.mockResolvedValue({ data: { @@ -440,14 +433,7 @@ describe("TestCaseRoutes", () => { return Promise.resolve({ data: [valueSets] }); } }); - - render( - - - - - - ); + renderComponentViaRouter(); mockedAxios.post.mockRejectedValue({ data: { @@ -536,13 +522,7 @@ describe("TestCaseRoutes", () => { } }); - render( - - - - - - ); + renderComponentViaRouter(); const testCaseTitle = await screen.findByText("TC1"); expect(testCaseTitle).toBeInTheDocument(); @@ -624,13 +604,7 @@ describe("TestCaseRoutes", () => { }); mockedAxios.put.mockRejectedValue(new Error("VALUE SET ERRORS")); - render( - - - - - - ); + renderComponentViaRouter(); expect( await screen.findByText( "An error occurred, please try again. If the error persists, please contact the help desk. (003)" @@ -658,13 +632,7 @@ describe("TestCaseRoutes", () => { ], }); }); - render( - - - - - - ); + renderComponentViaRouter(); const runAllTestsButton = await screen.findByRole("button", { name: "Run Test(s)", @@ -691,14 +659,7 @@ describe("TestCaseRoutes", () => { data: [], }); }); - render( - - - - - - ); - + renderComponentViaRouter(); await waitFor(() => { expect(screen.getByRole("alert")).toHaveTextContent( "An error occurred, please try again. If the error persists, please contact the help desk." @@ -707,16 +668,9 @@ describe("TestCaseRoutes", () => { }); it("should render 404 page", async () => { - const { getByTestId } = render( - - - - - - ); - - expect(getByTestId("404-page")).toBeInTheDocument(); - expect(getByTestId("404-page-link")).toBeInTheDocument(); + renderComponentViaRouter("/measures/m1234/edit/2"); + expect(screen.getByTestId("404-page")).toBeInTheDocument(); + expect(screen.getByTestId("404-page-link")).toBeInTheDocument(); }); it("should display error message when fetch test cases fails", async () => { @@ -745,17 +699,10 @@ describe("TestCaseRoutes", () => { } return Promise.resolve({ data: null }); }); - - const { getByTestId } = render( - - - - - - ); + renderComponentViaRouter(); await waitFor(() => { - const error = getByTestId("execution_context_loading_errors"); + const error = screen.getByTestId("execution_context_loading_errors"); expect(error).toBeInTheDocument(); }); }); diff --git a/src/components/routes/qiCore/TestCaseRoutes.tsx b/src/components/routes/qiCore/TestCaseRoutes.tsx index dcedfdf71..0f8b634d1 100644 --- a/src/components/routes/qiCore/TestCaseRoutes.tsx +++ b/src/components/routes/qiCore/TestCaseRoutes.tsx @@ -1,9 +1,13 @@ import React, { useEffect, useRef, useState } from "react"; -import { Route, Routes } from "react-router-dom"; +import { + createBrowserRouter, + RouterProvider, + useLocation, +} from "react-router-dom"; import TestCaseLanding from "../../testCaseLanding/qiCore/TestCaseLanding"; import EditTestCase from "../../editTestCase/qiCore/EditTestCase"; import NotFound from "../../notfound/NotFound"; -import { measureStore, useFeatureFlags } from "@madie/madie-util"; +import { measureStore } from "@madie/madie-util"; import { Bundle, ValueSet } from "fhir/r4"; import useTerminologyServiceApi from "../../../api/useTerminologyServiceApi"; import { ExecutionContextProvider } from "./ExecutionContext"; @@ -22,8 +26,12 @@ import TestCaseData from "../../testCaseConfiguration/testCaseData/TestCaseData" export const CQL_RETURN_TYPES_MISMATCH_ERROR = "One or more Population Criteria has a mismatch with CQL return types. Test Cases cannot be executed until this is resolved."; -const TestCaseRoutes = () => { - const featureFlags = useFeatureFlags(); +const TestCaseRoutes = (props) => { + // only for Unit Tests + const { initialEntry } = props; + if (initialEntry) { + window.history.pushState({}, "", initialEntry); + } const [measureBundle, setMeasureBundle] = useState(); const [valueSets, setValueSets] = useState(); const [errors, setErrors] = useState>([]); @@ -111,6 +119,61 @@ const TestCaseRoutes = () => { setExecutionContextReady(!!measureBundle && !!valueSets && !!measure); }, [measureBundle, measure, valueSets]); + const routesConfig = [ + { + children: [ + { + path: "/measures/:measureId/edit/test-cases", + element: , + }, + { + path: "/measures/:measureId/edit/test-cases/:id", + element: , + }, + { + path: "/measures/:measureId/edit/test-cases/list-page", + element: ( + + } + /> + ), + }, + { + path: "/measures/:measureId/edit/test-cases/list-page/:criteriaId", + element: ( + + } + /> + ), + }, + { + path: "/measures/:measureId/edit/test-cases/list-page/test-case-data", + element: ( + } /> + ), + }, + { path: "/404", element: }, + { path: "*", element: }, + ], + }, + ]; + + const router = createBrowserRouter(routesConfig); + return ( { {importWarnings && importWarnings.length > 0 && ( )} - + {/* { } /> - + */} + ); }; diff --git a/src/components/testCaseLanding/common/TestCaseLandingWrapper.tsx b/src/components/testCaseLanding/common/TestCaseLandingWrapper.tsx index 33e080001..df8139eb4 100644 --- a/src/components/testCaseLanding/common/TestCaseLandingWrapper.tsx +++ b/src/components/testCaseLanding/common/TestCaseLandingWrapper.tsx @@ -1,10 +1,15 @@ import React, { useState, useEffect } from "react"; import "twin.macro"; import "styled-components/macro"; -import { Group } from "@madie/madie-models"; -import * as _ from "lodash"; +import { MadieDiscardDialog } from "@madie/madie-design-system/dist/react"; import TestCaseListSideBarNav from "./TestCaseListSideBarNav"; -import { measureStore, useFeatureFlags } from "@madie/madie-util"; +import { measureStore, routeHandlerStore } from "@madie/madie-util"; +import { useBlocker } from "react-router"; + +export interface RouteHandlerState { + canTravel: boolean; + pendingRoute: string; +} const TestCaseLandingWrapper = (props) => { const [measure, setMeasure] = useState(measureStore.state); @@ -14,20 +19,59 @@ const TestCaseLandingWrapper = (props) => { subscription.unsubscribe(); }; }, []); + + // Required by every single spa application that has internal routing + // This will block user from navigating inside madie-measure when the current form is dirty + const { updateRouteHandlerState } = routeHandlerStore; + const [routeHandlerState, setRouteHandlerState] = useState( + routeHandlerStore.state + ); + const [dialogOpen, setDialogOpen] = useState(false); + useEffect(() => { + const subscription = routeHandlerStore.subscribe(setRouteHandlerState); + return () => { + subscription.unsubscribe(); + }; + }, []); + const blocker = useBlocker(({ currentLocation, nextLocation }) => { + if ( + !routeHandlerState?.canTravel && + currentLocation.pathname !== nextLocation.pathname + ) { + setDialogOpen(true); + return true; + } + setDialogOpen(false); + return false; + }); + const onContinue = () => { + setDialogOpen(false); + updateRouteHandlerState({ + canTravel: true, + pendingRoute: "", + }); + blocker.proceed(); + }; + const onClose = () => { + setDialogOpen(false); + blocker.reset(); + }; + return (
- <> - -
- {props.children && props.children} -
- + +
{props.children && props.children}
+
); }; diff --git a/src/components/testCaseLanding/common/TestCaseTable/TestCaseTablePopover.tsx b/src/components/testCaseLanding/common/TestCaseTable/TestCaseTablePopover.tsx index 30d408084..c280f3865 100644 --- a/src/components/testCaseLanding/common/TestCaseTable/TestCaseTablePopover.tsx +++ b/src/components/testCaseLanding/common/TestCaseTable/TestCaseTablePopover.tsx @@ -119,7 +119,7 @@ const TestCaseTablePopover = (props: TestCaseTablePopoverProps) => { > {viewOrEdit} - {model.startsWith("QI-Core") ? ( + {model?.startsWith("QI-Core") ? ( <> - {model.startsWith("QI-Core") && + {model?.startsWith("QI-Core") && selectedTestCase && selectedTestCase.executionStatus != "Invalid" && (