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

Add Cypress Test Suite for Facility Notice Board Functionality Verification #9045

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
52 changes: 44 additions & 8 deletions cypress/e2e/facility_spec/FacilityHomepage.cy.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
// FacilityCreation

import LoginPage from "../../pageobject/Login/LoginPage";
import { AssetPagination } from "../../pageobject/Asset/AssetPagination";
import FacilityPage from "../../pageobject/Facility/FacilityCreation";
import FacilityHome from "../../pageobject/Facility/FacilityHome";
import { FacilityNotify } from "../../pageobject/Facility/FacilityNotify";
import LoginPage from "../../pageobject/Login/LoginPage";
import ManageUserPage from "../../pageobject/Users/ManageUserPage";
import FacilityPage from "../../pageobject/Facility/FacilityCreation";
import { UserPage } from "../../pageobject/Users/UserSearch";
import { AssetPagination } from "../../pageobject/Asset/AssetPagination";

describe("Facility Homepage Function", () => {
const loginPage = new LoginPage();
const facilityHome = new FacilityHome();
const facilityNotify = new FacilityNotify();
const facilityPage = new FacilityPage();
const manageUserPage = new ManageUserPage();
const userPage = new UserPage();
Expand All @@ -23,7 +24,9 @@ describe("Facility Homepage Function", () => {
const district = "Ernakulam";
const localBody = "Aikaranad";
const facilityType = "Private Hospital";

const notificationErrorMsg = "Message cannot be empty";
const noitificationMessage =
"Reminder: The monthly report submission deadline is on 15th Nov. Ensure all entries are updated.";
Comment on lines +27 to +29
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Move notification messages to i18n configuration.

As per PR objectives, UI text should support internationalization. Consider moving these message constants to the i18n configuration:

  • notificationErrorMsg
  • noitificationMessage

Example structure:

// i18n/en/notification.json
{
  "errors": {
    "emptyMessage": "Message cannot be empty"
  },
  "templates": {
    "monthlyReport": "Reminder: The monthly report submission deadline is on 15th Nov. Ensure all entries are updated."
  }
}

before(() => {
loginPage.loginAsDistrictAdmin();
cy.saveLocalStorage();
Expand All @@ -42,9 +45,6 @@ describe("Facility Homepage Function", () => {
facilityHome.clickViewCnsButton();
facilityHome.verifyCnsUrl();
facilityHome.navigateBack();
// view notify button
facilityHome.clickFacilityNotifyButton();
facilityHome.verifyAndCloseNotifyModal();
// view facility button
facilityHome.clickViewFacilityDetails();
facilityPage.getFacilityName().should("be.visible");
Expand Down Expand Up @@ -135,6 +135,42 @@ describe("Facility Homepage Function", () => {
facilityHome.verifyLiveMonitorUrl();
});

it("Verify Notice Board Functionality", () => {
// search facility and verify it's loaded or not
facilityNotify.interceptFacilitySearchReq();
manageUserPage.typeFacilitySearch(facilityName);
facilityNotify.verifyFacilitySearchReq();
// verify facility name and notify button and click it
manageUserPage.assertFacilityInCard(facilityName);
facilityHome.clickFacilityNotifyButton();
// check visiblity of pop-up and frontend error on empty message
cy.verifyContentPresence("#notify-facility-name", [facilityName]);
cy.submitButton("Notify");
cy.verifyContentPresence(".error-text", [notificationErrorMsg]);
// close pop-up and verify
facilityHome.verifyAndCloseNotifyModal();
// send notification
facilityHome.clickFacilityNotifyButton();
facilityNotify.fillNotifyText(noitificationMessage);
facilityNotify.interceptPostNotificationReq();
cy.submitButton("Notify");
cy.verifyNotification("Facility Notified");
facilityNotify.verifyPostNotificationReq();
// signout as district admin and login as a Nurse
loginPage.ensureLoggedIn();
loginPage.clickSignOutBtn();
loginPage.loginManuallyAsNurse();
// Visit Notification Sidebar
facilityNotify.interceptGetNotificationReq();
facilityNotify.visitNoticeBoard();
facilityNotify.verifyGetNotificationReq();
cy.verifyContentPresence("#notification-message", [noitificationMessage]);
facilityNotify.interceptGetNotificationReq();
cy.verifyAndClickElement("#notification-slide-btn", "Notifications");
facilityNotify.verifyGetNotificationReq();
cy.verifyContentPresence("#notification-slide-msg", [noitificationMessage]);
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding negative test cases and cleanup.

The test covers the happy path well but could be enhanced with:

  1. Negative test cases:
    • Network failure scenarios
    • Server error responses
    • Invalid message formats
  2. Test data cleanup after test execution
  3. Explicit assertions for state changes

Would you like me to provide example code for these scenarios?


afterEach(() => {
cy.saveLocalStorage();
});
Expand Down
36 changes: 36 additions & 0 deletions cypress/pageobject/Facility/FacilityNotify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export class FacilityNotify {
fillNotifyText(message: string): void {
cy.get("#NotifyModalMessageInput").should("be.visible").type(message);
}

visitNoticeBoard(): void {
cy.get("a[href='/notice_board']").should("be.visible").click();
}
Comment on lines +10 to +12
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add timeout and error handling for link interaction.

The notice board link interaction could be more robust with proper timeout and error handling.

-    cy.get("a[href='/notice_board']").should("be.visible").click();
+    cy.get('[data-testid="notice-board-link"]', { timeout: 10000 })
+      .should("be.visible")
+      .should("not.be.disabled")
+      .click();

Committable suggestion skipped: line range outside the PR's diff.


visitNotificationSideBar(): void {
cy.get("#notification-slide-btn").should("be.visible").click();
}

interceptFacilitySearchReq(): void {
cy.intercept("GET", "**/api/v1/facility/**").as("searchFacility");
}
verifyFacilitySearchReq(): void {
cy.wait("@searchFacility").its("response.statusCode").should("eq", 200);
}
Comment on lines +18 to +23
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve API interception configuration and type safety.

The facility search API interception could be more specific and type-safe.

+  private readonly API_PATHS = {
+    FACILITY_SEARCH: '**/api/v1/facility',
+    NOTIFICATION: '**/api/v1/notification'
+  } as const;
+
+  interface FacilitySearchResponse {
+    // Add expected response type definition
+    data: Array<{ id: string; name: string }>;
+  }
+
   interceptFacilitySearchReq(): void {
-    cy.intercept("GET", "**/api/v1/facility/**").as("searchFacility");
+    cy.intercept("GET", this.API_PATHS.FACILITY_SEARCH + '/**').as("searchFacility");
   }
   
   verifyFacilitySearchReq(): void {
-    cy.wait("@searchFacility").its("response.statusCode").should("eq", 200);
+    cy.wait("@searchFacility").then((interception) => {
+      expect(interception.response?.statusCode).to.equal(200);
+      expect(interception.response?.body).to.have.property('data');
+    });
   }

Committable suggestion skipped: line range outside the PR's diff.


interceptPostNotificationReq(): void {
cy.intercept("POST", "**/api/v1/notification/notify").as("notifyFacility");
}

verifyPostNotificationReq(): void {
cy.wait("@notifyFacility").its("response.statusCode").should("eq", 204);
}
Comment on lines +25 to +31
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for notification POST requests.

The notification POST verification should handle error cases and validate request payload.

   interceptPostNotificationReq(): void {
-    cy.intercept("POST", "**/api/v1/notification/notify").as("notifyFacility");
+    cy.intercept("POST", this.API_PATHS.NOTIFICATION + '/notify').as("notifyFacility");
   }

   verifyPostNotificationReq(): void {
-    cy.wait("@notifyFacility").its("response.statusCode").should("eq", 204);
+    cy.wait("@notifyFacility").then((interception) => {
+      // Verify request payload
+      expect(interception.request.body).to.have.property('message');
+      // Verify response
+      expect(interception.response?.statusCode).to.equal(204);
+      // Handle potential error responses
+      if (interception.response?.statusCode !== 204) {
+        throw new Error(`Notification failed: ${interception.response?.body}`);
+      }
+    });
   }

Committable suggestion skipped: line range outside the PR's diff.


interceptGetNotificationReq(): void {
cy.intercept("GET", "**/api/v1/notification/**").as("getNotifications");
}

verifyGetNotificationReq(): void {
cy.wait("@getNotifications").its("response.statusCode").should("eq", 200);
}
}
4 changes: 4 additions & 0 deletions cypress/pageobject/Login/LoginPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class LoginPage {
cy.get("#sign-out-button").scrollIntoView();
cy.get("#sign-out-button").contains("Sign Out").should("exist");
}

clickSignOutBtn(): void {
cy.verifyAndClickElement("#sign-out-button", "Sign Out");
}
}

export default LoginPage;
2 changes: 2 additions & 0 deletions src/components/Common/Sidebar/SidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type SidebarItemProps = {

type SidebarItemBaseProps = SidebarItemProps & {
shrinked?: boolean;
id?: string;
};

const SidebarItemBase = forwardRef<HTMLAnchorElement, SidebarItemBaseProps>(
Expand All @@ -31,6 +32,7 @@ const SidebarItemBase = forwardRef<HTMLAnchorElement, SidebarItemBaseProps>(
return (
<Link
ref={ref}
id={props?.id}
className={`tooltip relative ml-1 mr-2 h-12 flex-1 cursor-pointer rounded-md py-1 font-medium text-gray-600 transition md:flex-none ${
props.selected
? "bg-white text-green-800 shadow"
Expand Down
5 changes: 4 additions & 1 deletion src/components/Facility/FacilityCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,10 @@ export const FacilityCard = (props: {
<DialogModal
show={notifyModalFor === facility.id}
title={
<span className="flex justify-center text-2xl">
<span
className="flex justify-center text-2xl"
id="notify-facility-name"
>
Notify: {facility.name}
</span>
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/Notifications/NoticeBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export const NoticeBoard = () => {
className="overflow-hidden rounded shadow-md"
>
<div className="px-6 py-4">
<div className="text-justify text-lg">{item.message}</div>
<div className="text-justify text-lg" id="notification-message">
{item.message}
</div>
<div className="text-md my-2 text-secondary-700">
{formatName(item.caused_by)} -{" "}
<span className="font-bold text-primary-700">
Expand Down
5 changes: 4 additions & 1 deletion src/components/Notifications/NotificationsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@
/>
</div>
</div>
<div className="py-1 text-sm">{result.message}</div>
<div className="py-1 text-sm" id="notification-slide-msg">
{result.message}
</div>
<div className="flex flex-col justify-end gap-2">
<div className="py-1 text-right text-xs text-secondary-700">
{formatDateTime(result.created_date)}
Expand Down Expand Up @@ -474,6 +476,7 @@
<>
<Item
text={t("Notifications")}
id="notification-slide-btn"

Check failure on line 479 in src/components/Notifications/NotificationsList.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (1)

Type '{ text: string; id: string; do: () => void; icon: Element; badgeCount: number; handleOverflow: any; }' is not assignable to type 'IntrinsicAttributes & ((Omit<{ ref?: Ref<HTMLAnchorElement> | undefined; text: string; icon: ReactNode; onItemClick?: (() => void) | undefined; external?: true | undefined; badgeCount?: number | undefined; selected?: boolean | undefined; handleOverflow?: any; } & { ...; }, "ref"> | Omit<...>) & RefAttributes<...>)'.

Check failure on line 479 in src/components/Notifications/NotificationsList.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (2)

Type '{ text: string; id: string; do: () => void; icon: Element; badgeCount: number; handleOverflow: any; }' is not assignable to type 'IntrinsicAttributes & ((Omit<{ ref?: Ref<HTMLAnchorElement> | undefined; text: string; icon: ReactNode; onItemClick?: (() => void) | undefined; external?: true | undefined; badgeCount?: number | undefined; selected?: boolean | undefined; handleOverflow?: any; } & { ...; }, "ref"> | Omit<...>) & RefAttributes<...>)'.

Check failure on line 479 in src/components/Notifications/NotificationsList.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (3)

Type '{ text: string; id: string; do: () => void; icon: Element; badgeCount: number; handleOverflow: any; }' is not assignable to type 'IntrinsicAttributes & ((Omit<{ ref?: Ref<HTMLAnchorElement> | undefined; text: string; icon: ReactNode; onItemClick?: (() => void) | undefined; external?: true | undefined; badgeCount?: number | undefined; selected?: boolean | undefined; handleOverflow?: any; } & { ...; }, "ref"> | Omit<...>) & RefAttributes<...>)'.

Check failure on line 479 in src/components/Notifications/NotificationsList.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (4)

Type '{ text: string; id: string; do: () => void; icon: Element; badgeCount: number; handleOverflow: any; }' is not assignable to type 'IntrinsicAttributes & ((Omit<{ ref?: Ref<HTMLAnchorElement> | undefined; text: string; icon: ReactNode; onItemClick?: (() => void) | undefined; external?: true | undefined; badgeCount?: number | undefined; selected?: boolean | undefined; handleOverflow?: any; } & { ...; }, "ref"> | Omit<...>) & RefAttributes<...>)'.
do={() => setOpen(!open)}
icon={<CareIcon icon="l-bell" className="h-5" />}
badgeCount={unreadCount}
Expand Down
Loading