Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed Multi-selection functionality. #9345

Closed
wants to merge 44 commits into from
Closed
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
72c25c7
Fix multi-selection, closes #9144
Jeffrin2005 Dec 10, 2024
5e98869
Merge branch 'develop' into multiselection
Jeffrin2005 Dec 10, 2024
e8499fd
updated , closes 9144
Jeffrin2005 Dec 10, 2024
db2fb4c
updated index.tsx
Jeffrin2005 Dec 11, 2024
2098454
updated index.tsx
Jeffrin2005 Dec 13, 2024
7d99ffe
Merge branch 'develop' into multiselection
Jeffrin2005 Dec 17, 2024
b9c2913
final Board.tsx
Jeffrin2005 Dec 18, 2024
e721590
Switch out all the ButtonV2s and used button
Jeffrin2005 Dec 19, 2024
c2fe3a9
Merge branch 'multiselection' of https://github.com/Jeffrin2005/care_…
Jeffrin2005 Dec 19, 2024
7cd8d7f
button instead of buttonV2
Jeffrin2005 Dec 21, 2024
d869d1b
Merge branch 'develop' into multiselection
Jeffrin2005 Dec 21, 2024
aa51880
Final-updated shadcn button
Jeffrin2005 Dec 23, 2024
102bc74
Merge branch 'multiselection' of https://github.com/Jeffrin2005/care_…
Jeffrin2005 Dec 23, 2024
9a73618
orginal-used shadcn button
Jeffrin2005 Dec 23, 2024
1f667e2
Final-Latest-used shadcn Button
Jeffrin2005 Dec 23, 2024
fc1ff52
used_variant=primary,secondary
Jeffrin2005 Dec 23, 2024
de35c4d
Merge branch 'develop' into multiselection
nihal467 Dec 24, 2024
1ad5032
Merge branch 'develop' into multiselection
nihal467 Dec 24, 2024
b900f6f
latest-investigation
Jeffrin2005 Dec 26, 2024
db0e22e
Merge branch 'develop' into multiselection
Jeffrin2005 Dec 28, 2024
bad7863
Correct Failing Test Case
Jeffrin2005 Dec 28, 2024
0faeb64
Merge branch 'multiselection' of https://github.com/Jeffrin2005/care_…
Jeffrin2005 Dec 28, 2024
f52117b
Correct Failing Test case
Jeffrin2005 Dec 28, 2024
564a461
changed patientInvestigation.ts
Jeffrin2005 Dec 28, 2024
18fcf32
final test fail
Jeffrin2005 Dec 28, 2024
ce00cdf
latest check
Jeffrin2005 Dec 28, 2024
a2d1d38
final
Jeffrin2005 Dec 28, 2024
e6702d0
latest-final
Jeffrin2005 Dec 28, 2024
6c91c63
final1
Jeffrin2005 Dec 28, 2024
0c9da17
cypress test fail
Jeffrin2005 Dec 28, 2024
623dfa0
cypress3 test
Jeffrin2005 Dec 28, 2024
762c1ea
final2
Jeffrin2005 Dec 28, 2024
6a67690
orginal
Jeffrin2005 Dec 28, 2024
34d40da
latest1
Jeffrin2005 Dec 28, 2024
e9129e5
finall
Jeffrin2005 Dec 28, 2024
ec73916
rever
Jeffrin2005 Dec 28, 2024
33653fb
orginal
Jeffrin2005 Dec 28, 2024
a971c59
test final
Jeffrin2005 Dec 28, 2024
21601fe
temp1
Jeffrin2005 Dec 28, 2024
6d63a69
Test final
Jeffrin2005 Dec 28, 2024
a920771
Final commit of tests
Jeffrin2005 Dec 28, 2024
d6fe9b4
test1
Jeffrin2005 Dec 28, 2024
197c9a9
UP1
Jeffrin2005 Dec 28, 2024
4c56047
FINAL_COMMIT
Jeffrin2005 Dec 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions cypress/e2e/patient_spec/PatientInvestigation.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,33 @@ describe("Patient Investigation Creation from Patient consultation page", () =>

it("Create a investigation for a patient and verify its reflection", () => {
patientPage.visitPatient(patientName);
cy.url().should("include", "/patient");

cy.log("Clicking investigation tab");
patientInvestigation.clickInvestigationTab();
cy.get("#consultation_tab_nav").should("be.visible").should("exist");

cy.log("Attempting to click log lab results");
cy.wait(2000);
patientInvestigation.clickLogLabResults();

cy.get("#log-lab-results", { timeout: 15000 })
.should("be.visible")
.should("exist");

cy.log("Selecting investigation options");
patientInvestigation.selectInvestigationOption([
"Haematology",
"Urine Test",
]);
cy.clickSubmitButton("Save Investigation");

cy.get("button")
.contains("Save Investigation")
.should("be.visible")
.click();

cy.verifyNotification("Please Enter at least one value");
cy.closeNotification();
// Temporary workflow for investigation since we dont have dummy data and moving away from existing module
});

afterEach(() => {
Expand Down
111 changes: 102 additions & 9 deletions cypress/pageobject/Patient/PatientInvestigation.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,126 @@
class PatientInvestigation {
private elements = {
addInvestigation: () => cy.get("#investigation"),
investigationTab: () => cy.get("#consultation_tab_nav"),
searchPatientInvestigation: () => cy.get("#search-patient-investigation"),
investigationGroup: () => cy.get("#investigation-group"),
investigationCheckbox: () => cy.get("#investigation-checkbox"),
investigationSelect: () => cy.get("#investigation-select"),
logLabResults: () => cy.get("#log-lab-results"),
investigationFrequency: () => cy.get("#investigation-frequency"),
};

clickAddInvestigation() {
cy.verifyAndClickElement("#investigation", "Add Investigation");
cy.log("Clicking Add Investigation");
this.elements
.addInvestigation()
.should("be.visible")
.contains("Add Investigation")
.click();
cy.wait(1000);
}

clickInvestigationTab() {
cy.verifyAndClickElement("#consultation_tab_nav", "Investigations");
cy.log("Clicking Investigation Tab");
this.elements
.investigationTab()
.should("be.visible")
.contains("Investigations")
.click();
cy.wait(2000);
}

selectInvestigation(investigation: string) {
cy.get("#search-patient-investigation").type(investigation);
cy.verifyAndClickElement("#investigation-group", investigation);
cy.verifyAndClickElement("#investigation", "Investigation No. 1");
cy.log(`Selecting Investigation: ${investigation}`);

this.elements
.searchPatientInvestigation()
.should("be.visible")
.clear()
.type(investigation, { delay: 100 });

this.elements
.investigationGroup()
.contains(investigation)
.should("be.visible")
.click();

this.elements
.addInvestigation()
.contains("Investigation No. 1")
.should("be.visible")
.click();
}

clickInvestigationCheckbox() {
cy.get("#investigation-checkbox").click();
cy.log("Clicking Investigation Checkbox");
this.elements
.investigationCheckbox()
.should("be.visible")
.should("be.enabled")
.click();
}

selectInvestigationOption(options: string[]) {
cy.clickAndMultiSelectOption("#investigations", options);
cy.log("Selecting Investigation Options:", options);

this.elements
.investigationSelect()
.should("exist")
.should("be.visible")
.click();

options.forEach((option) => {
cy.get("[role='option']")
.contains(option)
.should("be.visible")
.then(($el) => {
if ($el.length > 0) {
cy.wrap($el).click();
} else {
throw new Error(`Option "${option}" not found in dropdown`);
}
});
});

cy.get("body").click(0, 0);
cy.wait(500);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve robustness of multi-selection handling.

The current implementation has several areas for improvement:

  1. Clicking at (0,0) to close the dropdown is fragile and may interfere with other elements
  2. No verification that selections were actually successful
  3. Using fixed waits instead of waiting for state changes

Consider this improved implementation:

 selectInvestigationOption(options: string[]) {
   cy.log("Selecting Investigation Options:", options);

   this.elements
     .investigationSelect()
     .should("exist")
     .should("be.visible")
     .click();

   options.forEach((option) => {
     cy.get("[role='option']")
       .contains(option)
-      .should("be.visible")
+      .should("be.visible", { timeout: 5000 })
       .then(($el) => {
         if ($el.length > 0) {
           cy.wrap($el).click();
+          // Verify selection was successful
+          cy.get(".selected-option").should("contain", option);
         } else {
-          throw new Error(`Option "${option}" not found in dropdown`);
+          throw new Error(
+            `Option "${option}" not found in dropdown. Available options: ${
+              $el.parent().text()
+            }`
+          );
         }
       });
   });

-  cy.get("body").click(0, 0);
-  cy.wait(500);
+  // Close dropdown using the dropdown trigger itself
+  this.elements.investigationSelect().click();
+  // Verify dropdown is closed
+  cy.get("[role='option']").should("not.exist");
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
selectInvestigationOption(options: string[]) {
cy.clickAndMultiSelectOption("#investigations", options);
cy.log("Selecting Investigation Options:", options);
this.elements
.investigationSelect()
.should("exist")
.should("be.visible")
.click();
options.forEach((option) => {
cy.get("[role='option']")
.contains(option)
.should("be.visible")
.then(($el) => {
if ($el.length > 0) {
cy.wrap($el).click();
} else {
throw new Error(`Option "${option}" not found in dropdown`);
}
});
});
cy.get("body").click(0, 0);
cy.wait(500);
selectInvestigationOption(options: string[]) {
cy.log("Selecting Investigation Options:", options);
this.elements
.investigationSelect()
.should("exist")
.should("be.visible")
.click();
options.forEach((option) => {
cy.get("[role='option']")
.contains(option)
.should("be.visible", { timeout: 5000 })
.then(($el) => {
if ($el.length > 0) {
cy.wrap($el).click();
// Verify selection was successful
cy.get(".selected-option").should("contain", option);
} else {
throw new Error(
`Option "${option}" not found in dropdown. Available options: ${
$el.parent().text()
}`
);
}
});
});
// Close dropdown using the dropdown trigger itself
this.elements.investigationSelect().click();
// Verify dropdown is closed
cy.get("[role='option']").should("not.exist");
}

}

clickLogLabResults() {
cy.verifyAndClickElement("#log-lab-results", "Log Lab Results");
cy.log("Clicking Log Lab Results");

this.elements.logLabResults().scrollIntoView().should("be.visible");

cy.wait(1000);

this.elements
.logLabResults()
.contains("Log Lab Results")
.should("be.visible")
.should("be.enabled")
.click();

cy.wait(2000);
}

selectInvestigationFrequency(frequency: string) {
cy.get("#investigation-frequency").click();
cy.log(`Selecting Investigation Frequency: ${frequency}`);

this.elements.investigationFrequency().should("be.visible").click();

cy.contains("button", frequency).should("be.visible").click();

cy.wait(500);
}

verifyElementPresent(selector: string, timeout = 10000) {
return cy.get(selector, { timeout }).should("exist").should("be.visible");
}

waitForLoading() {
cy.get("#loading-indicator", { timeout: 10000 }).should("not.exist");
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Test coverage is incomplete for multi-selection scenarios

The current test implementation only partially covers the required scenarios. Here are the gaps identified:

  • The test only verifies multi-selection in the log lab results section with two options ("Haematology", "Urine Test")
  • Missing test coverage for:
    • Multi-selection in the create investigation page
    • Clear button functionality
    • Edge cases like selecting/deselecting all options
    • Persistence of selected options

The page object has the necessary methods (selectInvestigationOption, clear), but they aren't fully utilized in the test scenarios. The underlying components (AutoCompleteAsync.tsx) support multiple selection, but the test coverage doesn't exercise all these capabilities.

🔗 Analysis chain

Verify coverage of all multi-selection scenarios.

While the test file has been updated to handle multi-selection, please ensure it covers all scenarios mentioned in issue #9144, including:

  1. Multi-selection in the Patient log lab results section
  2. Multi-selection in the create investigation page
  3. The new Clear button functionality

Let's verify the test coverage:


Let me gather more information about the test coverage:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for related test cases in the codebase
echo "Searching for related test files..."
fd -e cy.ts -e spec.ts "Investigation"

echo "Checking for multi-select testing patterns..."
rg -l "selectInvestigationOption.*\[.*\]" -t ts

echo "Checking for Clear button test coverage..."
rg -l "clear.*button|clear.*selection" -t ts

Length of output: 746


Script:

#!/bin/bash
# Check the actual test implementation
echo "Checking PatientInvestigation test implementation..."
cat cypress/e2e/patient_spec/PatientInvestigation.cy.ts

# Look for clear functionality in the page object
echo -e "\nChecking clear functionality in page object..."
rg "clear" cypress/pageobject/Patient/PatientInvestigation.ts

# Check for any multi-select related code in the component
echo -e "\nChecking multi-select implementation in components..."
rg -A 5 "multiSelect|multiple" src/components/Form/AutoCompleteAsync.tsx src/components/Form/FormFields/Autocomplete.tsx

Length of output: 5394

export default PatientInvestigation;
77 changes: 41 additions & 36 deletions src/components/Facility/Investigations/Reports/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useCallback, useReducer, useState } from "react";
import { useTranslation } from "react-i18next";

import ButtonV2 from "@/components/Common/ButtonV2";
import { Button } from "@/components/ui/button";

import CircularProgress from "@/components/Common/CircularProgress";
import Loading from "@/components/Common/Loading";
import Page from "@/components/Common/Page";
Expand Down Expand Up @@ -53,42 +54,36 @@ const initialState: InitialState = {

const investigationReportsReducer = (state = initialState, action: any) => {
switch (action.type) {
case "set_investigation_groups": {
case "set_investigation_groups":
return {
...state,
investigationGroups: action.payload,
};
}
case "set_selected_group": {
case "set_selected_group":
return {
...state,
selectedGroup: action.payload,
};
}
case "set_investigations": {
case "set_investigations":
return {
...state,
investigations: action.payload,
};
}
case "set_selected_investigations": {
case "set_selected_investigations":
return {
...state,
selectedInvestigations: action.payload,
};
}
case "set_investigation_table_data": {
case "set_investigation_table_data":
return {
...state,
investigationTableData: action.payload,
};
}
case "set_loading": {
case "set_loading":
return {
...state,
isLoading: action.payload,
};
}
default:
return state;
}
Expand All @@ -113,6 +108,10 @@ const InvestigationReports = ({ id }: any) => {
selectedInvestigations,
} = state as InitialState;

const clearSelectedInvestigations = () => {
dispatch({ type: "set_selected_investigations", payload: [] });
};

const fetchInvestigationsData = useCallback(
async (
onSuccess: (
Expand Down Expand Up @@ -314,14 +313,13 @@ const InvestigationReports = ({ id }: any) => {
/>
</div>
{!isLoading.investigationLoading && (
<ButtonV2
<Button
onClick={() => fetchInvestigation()}
disabled={getTestDisabled}
variant="primary"
className="my-2.5"
>
{t("get_tests")}
</ButtonV2>
</Button>
)}
{!!isLoading.investigationLoading && (
<CircularProgress className="text-primary-500" />
Expand All @@ -342,23 +340,31 @@ const InvestigationReports = ({ id }: any) => {
})
}
optionLabel={(option) => option.name}
optionValue={(option) => option}
optionValue={(option) => option.external_id}
isLoading={isLoading.investigationLoading}
placeholder={t("select_investigations")}
selectAll={true}
/>
</div>

<ButtonV2
onClick={() => {
setSessionPage(1);
handleGenerateReports(1);
}}
disabled={generateReportDisabled}
variant="primary"
className="my-2.5"
>
{t("generate_report")}
</ButtonV2>
<div className="flex space-x-2">
<Button
onClick={() => {
setSessionPage(1);
handleGenerateReports(1);
}}
disabled={generateReportDisabled}
variant="primary"
>
{t("generate_report")}
</Button>
<Button
onClick={clearSelectedInvestigations}
disabled={!selectedInvestigations.length}
variant="secondary"
>
{t("clear")}
</Button>
</div>
</>
)}
{isLoading.tableData && (
Expand All @@ -370,19 +376,19 @@ const InvestigationReports = ({ id }: any) => {
{!!investigationTableData.length && (
<>
<div className="my-2.5">
<ButtonV2
<Button
onClick={() => handleSessionPage("NEXT")}
disabled={prevSessionDisabled}
>
{isLoading.tableData ? "Loading..." : t("next_sessions")}
</ButtonV2>
<ButtonV2
</Button>
<Button
onClick={() => handleSessionPage("PREV")}
disabled={nextSessionDisabled}
className="ml-3"
>
{isLoading.tableData ? "Loading..." : t("prev_sessions")}
</ButtonV2>
</Button>
</div>

<ReportTable
Expand All @@ -396,14 +402,13 @@ const InvestigationReports = ({ id }: any) => {
/>

{!loadMoreDisabled && (
<ButtonV2
<Button
disabled={loadMoreDisabled}
onClick={handleLoadMore}
className="my-2.5 w-full"
variant="primary"
>
{t("load_more")}
</ButtonV2>
</Button>
)}
</>
)}
Expand Down
Loading
Loading