diff --git a/.cursorrules b/.cursorrules index 3acf89006b4..53cbee3934d 100644 --- a/.cursorrules +++ b/.cursorrules @@ -38,4 +38,4 @@ General Guidelines # Testing Guidelines -For Cypress testing guidelines, refer to cypress/docs/cypress.md +For Cypress testing guidelines, refer to cypress/docs/*.md diff --git a/cypress/docs/best-practices.md b/cypress/docs/best-practices.md new file mode 100644 index 00000000000..f567b8a9ea8 --- /dev/null +++ b/cypress/docs/best-practices.md @@ -0,0 +1,32 @@ +# Best Practices + +## Test Independence +- Each test should be independent and isolated +- Clean up test data after tests +- Don't rely on the state from other tests + +## API Testing +- Use cy.intercept() for API verification +- Use waitUntil() for API completion +- Avoid cy.wait() except for API responses + +## Element Interaction +- Always verify element state before interaction +- Use data-cy attributes for selectors +- Verify button text before clicking + +## Code Organization +- Keep tests focused and concise +- Follow AAA pattern (Arrange, Act, Assert) +- Use meaningful test descriptions + +## Common Pitfalls to Avoid +- Redundant visibility checks with verifyAndClickElement +- Hardcoded values in page objects +- Unnecessary waits +- Test interdependencies + +## Performance Considerations +- Minimize unnecessary API calls +- Use efficient selectors +- Batch similar operations \ No newline at end of file diff --git a/cypress/docs/cypress.md b/cypress/docs/cypress.md index 92204f27443..5e28cef49f0 100644 --- a/cypress/docs/cypress.md +++ b/cypress/docs/cypress.md @@ -1,114 +1,25 @@ -# Cypress Guidelines +# Cypress Testing Documentation -## File Structure +## Overview +This documentation covers the testing standards and patterns for our Cypress test suite. -``` -cypress/ -├── docs/ -│ └── cypress.md -├── e2e/ # Test files grouped by modules -│ ├── patient/ # Patient module tests -│ │ ├── search.cy.ts -│ │ ├── create.cy.ts -│ │ └── edit.cy.ts -│ ├── facility/ # Facility module tests -│ │ ├── list.cy.ts -│ │ └── details.cy.ts -│ └── user/ # User module tests -│ ├── login.cy.ts -│ └── profile.cy.ts -├── fixtures/ # Test data files by module -│ ├── patient/ -│ │ └── patient-data.json -│ └── facility/ -│ └── facility-data.json -├── pageObject/ # Page Objects by module -│ ├── patient/ -│ │ ├── SearchPage.ts -│ │ └── CreatePage.ts -│ ├── facility/ -│ │ ├── ListPage.ts -│ │ └── DetailsPage.ts -│ └── utils/ # Common helpers and global functions -│ ├── CommonActions.ts # Shared actions across pages -│ ├── CommonAssertions.ts # Shared assertions -│ └── GlobalHelpers.ts # Platform-wide utility functions -├── support/ # Core support files -│ ├── commands.ts # Custom Cypress commands -│ ├── e2e.ts # e2e test configuration -│ └── index.ts # Main support file -└── tsconfig.json -``` - -## Support Files - -- `commands.ts`: Custom Cypress commands and their TypeScript definitions -- `e2e.ts`: e2e specific configurations and imports -- `index.ts`: Main support file that loads commands and configurations - -## Page Objects Utils - -The `pageObjects/utils` folder contains: - -- Common helper functions used across different page objects -- Global utility functions for platform-wide operations -- Shared assertions and verifications -- Reusable action patterns - -## Module-based Organization - -Each module (patient, facility, user, etc.) should have its own: - -- Test files in `e2e//` -- Page Objects in `pageObjects//` -- Fixtures in `fixtures//` - -This organization helps: - -- Keep related tests and page objects together -- Maintain clear separation between module-specific and common utilities -- Enable better code reuse through common utilities -- Keep core support files focused and minimal +## Quick Links +- [File Structure and Organization](./file-structure.md) +- [Testing Patterns](./patterns.md) +- [Best Practices](./best-practices.md) ## Core Principles - -- Create, use and modify Reusable Commands and Functions for Cypress as needed -- Provide Id for the elements using data-cy attributes -- When interacting with a button, verify the button is enabled and visible before interacting with it -- when interacting with a button, verify the text of the button is correct -- Use Page Object Model for Cypress -- Use built-in assertions for Cypress -- Use beforeEach, afterEach and all relevant hooks for Cypress on every test file - -## File Naming Conventions - -- Test files: `feature-name.cy.ts` -- Page Objects: `FeatureNamePage.ts` -- Custom Commands: `feature-name.ts` -- Fixtures: `feature-name-data.json` - -## Storage Management - -- Use cy.saveLocalStorage() and cy.restoreLocalStorage() for Cypress -- If we are using same element id to verify presence, interact and assert, make a reusable structure for it - -## API Testing - -- Use cy.intercept() for Cypress to verify API calls -- Use waitUntil() for Cypress to wait for API calls to complete -- Never use cy.wait() for Cypress except for API responses - -## Best Practices - -- Keep tests independent and isolated -- Use meaningful test descriptions -- Follow AAA pattern (Arrange, Act, Assert) -- Use fixtures for test data -- Implement custom commands for repetitive actions - -### Code Editing Guidelines - -- When suggesting code edits, provide only the relevant file and changes -- Don't create separate folders for each edit -- Keep the existing file structure intact -- Provide clear comments for what's being changed +- Create and use reusable commands and functions +- Use data-cy attributes for element identification +- Follow Page Object Model pattern +- Write independent and isolated tests +- Use TypeScript for better type safety + +## Getting Started +1. Familiarize yourself with the file structure +2. Review the testing patterns +3. Follow the best practices +4. Use the provided examples as templates + +## Support +For questions or clarifications, refer to the specific documentation sections or reach out to the team. diff --git a/cypress/docs/file-structure.md b/cypress/docs/file-structure.md new file mode 100644 index 00000000000..8edee0d93f6 --- /dev/null +++ b/cypress/docs/file-structure.md @@ -0,0 +1,39 @@ +# File Structure and Organization + +## Directory Structure +``` +cypress/ +├── docs/ +│ ├── README.md +│ ├── file-structure.md +│ ├── patterns.md +│ └── best-practices.md +├── e2e/ # Test files grouped by modules +│ ├── patient/ +│ ├── facility/ +│ └── user/ +├── fixtures/ +├── pageObject/ +└── support/ +``` + +## Module Organization +Each module (patient, facility, user, etc.) should have: +- Test files in `e2e//` +- Page Object in `pageObject//` +- Fixtures in `fixtures//` + +## File Naming Conventions +- Test files: `feature-name.cy.ts` +- Page Object: `FeatureNamePage.ts` +- Custom Commands: `feature-name.ts` +- Fixtures: `feature-name-data.json` + +## Support Files +- `commands.ts`: Custom Cypress commands +- `e2e.ts`: e2e configurations +- `index.ts`: Main support file + +## Storage Management +- Use cy.saveLocalStorage() and cy.restoreLocalStorage() +- Manage test data cleanup \ No newline at end of file diff --git a/cypress/docs/patterns.md b/cypress/docs/patterns.md new file mode 100644 index 00000000000..430e31413da --- /dev/null +++ b/cypress/docs/patterns.md @@ -0,0 +1,50 @@ +# Testing Patterns + +## Element Interaction +### Preferred Command Usage +```typescript +// Correct +cy.verifyAndClickElement('[data-cy="element"]', "Button Text"); + +// Avoid +cy.get('[data-cy="element"]').should('be.visible').click(); +``` + +## Navigation Patterns +```typescript +// Good +navigateToOrganization(orgName: string) { + cy.verifyAndClickElement('[data-cy="organization-list"]', orgName); +} + +navigateToFacilitiesList() { + cy.verifyAndClickElement('[data-testid="org-nav-facilities"]', "Facilities"); +} +``` + +## Test Data Management +```typescript +// Constants for fixed values +const facilityType = "Primary Health Centre"; + +// Generator functions for dynamic data +const phoneNumber = generatePhoneNumber(); +``` + +## Test Structure +```typescript +describe("Feature Name", () => { + const page = new PageObject(); + const facilityType = "Primary Health Centre"; + const testData = generateTestData(); + + beforeEach(() => { + // Setup + }); + + it("should perform action", () => { + page.navigateToOrganization("Kerala"); + page.navigateToFacilitiesList(); + }); +}); +``` \ No newline at end of file diff --git a/cypress/e2e/facility_spec/facility_creation.cy.ts b/cypress/e2e/facility_spec/facility_creation.cy.ts new file mode 100644 index 00000000000..c784d4c3991 --- /dev/null +++ b/cypress/e2e/facility_spec/facility_creation.cy.ts @@ -0,0 +1,58 @@ +import { FacilityCreation } from "../../pageObject/facility/FacilityCreation"; +import { generatePhoneNumber } from "../../utils/commonUtils"; +import { generateFacilityData } from "../../utils/facilityData"; + +describe("Facility Management", () => { + const facilityPage = new FacilityCreation(); + const facilityType = "Primary Health Centre"; + const testFacility = generateFacilityData(); + const phoneNumber = generatePhoneNumber(); + + beforeEach(() => { + cy.visit("/login"); + cy.loginByApi("nurse"); + }); + + it("Create a new facility using the admin role", () => { + facilityPage.navigateToOrganization("Kerala"); + facilityPage.navigateToFacilitiesList(); + facilityPage.clickAddFacility(); + + // Fill form + facilityPage.fillBasicDetails( + testFacility.name, + facilityType, + testFacility.description, + ); + + facilityPage.selectFeatures(testFacility.features); + + facilityPage.fillContactDetails( + phoneNumber, + testFacility.pincode, + testFacility.address, + ); + + facilityPage.fillLocationDetails( + testFacility.coordinates.latitude, + testFacility.coordinates.longitude, + ); + + // Submit and verify + facilityPage.makePublicFacility(); + facilityPage.submitFacilityCreationForm(); + facilityPage.verifySuccessMessage(); + + // Search for the facility and verify in card + facilityPage.searchFacility(testFacility.name); + facilityPage.verifyFacilityNameInCard(testFacility.name); + }); + + it("Should show validation errors for required fields", () => { + facilityPage.navigateToOrganization("Kerala"); + facilityPage.navigateToFacilitiesList(); + facilityPage.clickAddFacility(); + facilityPage.submitFacilityCreationForm(); + facilityPage.verifyValidationErrors(); + }); +}); diff --git a/cypress/e2e/login_spec/loginpage.cy.ts b/cypress/e2e/login_spec/loginpage.cy.ts index e70ba7f951e..4b1965f75f7 100644 --- a/cypress/e2e/login_spec/loginpage.cy.ts +++ b/cypress/e2e/login_spec/loginpage.cy.ts @@ -4,19 +4,11 @@ describe("Login Page", () => { const loginPage = new LoginPage(); beforeEach(() => { - cy.clearLocalStorage(); - cy.saveLocalStorage(); cy.visit("/login"); }); - afterEach(() => { - cy.saveLocalStorage(); - }); - it("should successfully login with admin credentials", () => { - loginPage.interceptLogin(); - loginPage.loginByRole("admin"); - loginPage.verifyLoginResponse(); + cy.loginByApi("staff"); cy.url().should("include", "/"); }); diff --git a/cypress/e2e/patient_spec/patient_search.cy.ts b/cypress/e2e/patient_spec/patient_search.cy.ts index b63e456be9b..66a09eed153 100644 --- a/cypress/e2e/patient_spec/patient_search.cy.ts +++ b/cypress/e2e/patient_spec/patient_search.cy.ts @@ -1,9 +1,6 @@ -import { LoginPage } from "pageObject/auth/LoginPage"; - import { patientSearch } from "../../pageObject/Patients/PatientSearch"; describe("Patient Search", () => { - const loginPage = new LoginPage(); const TEST_PHONE = "9495031234"; const PATIENT_DETAILS = { name: "Nihal", @@ -12,7 +9,8 @@ describe("Patient Search", () => { }; beforeEach(() => { - loginPage.loginByRole("nurse"); + cy.visit("/login"); + cy.loginByApi("nurse"); }); it("search patient with phone number and verifies details", () => { diff --git a/cypress/fixtures/users.json b/cypress/fixtures/users.json index 5de910d93ff..7fe744d7edc 100644 --- a/cypress/fixtures/users.json +++ b/cypress/fixtures/users.json @@ -6,5 +6,9 @@ "nurse": { "username": "nihal-nurse", "password": "Test@123" + }, + "staff": { + "username": "nihal-staff", + "password": "Test@123" } -} +} \ No newline at end of file diff --git a/cypress/pageObject/auth/LoginPage.ts b/cypress/pageObject/auth/LoginPage.ts index ada53b61d3a..8509b2b448a 100644 --- a/cypress/pageObject/auth/LoginPage.ts +++ b/cypress/pageObject/auth/LoginPage.ts @@ -7,13 +7,6 @@ export class LoginPage { return cy.intercept("POST", this.routes.login).as("loginRequest"); } - verifyLoginResponse() { - return cy - .wait("@loginRequest") - .its("response.statusCode") - .should("eq", 200); - } - // Add selectors for existing elements private readonly usernameInput = "[data-cy=username]"; private readonly passwordInput = "[data-cy=password]"; diff --git a/cypress/pageObject/facility/FacilityCreation.ts b/cypress/pageObject/facility/FacilityCreation.ts new file mode 100644 index 00000000000..d4cbf68bc4a --- /dev/null +++ b/cypress/pageObject/facility/FacilityCreation.ts @@ -0,0 +1,102 @@ +export class FacilityCreation { + // Navigation + navigateToOrganization(orgName: string) { + cy.verifyAndClickElement('[data-cy="organization-list"]', orgName); + } + + navigateToFacilitiesList() { + cy.verifyAndClickElement( + '[data-testid="org-nav-facilities"]', + "Facilities", + ); + } + + clickAddFacility() { + cy.get('[data-cy="add-facility-button"]').should("be.visible").click(); + } + + // Individual field methods + enterFacilityName(name: string) { + cy.get('[data-cy="facility-name"]').type(name); + } + + selectFacilityType(facilityType: string) { + cy.clickAndSelectOption('[data-cy="facility-type"]', facilityType); + } + + enterDescription(description: string) { + cy.get('[data-cy="facility-description"]').type(description); + } + + enterPhoneNumber(phone: string) { + cy.get('[data-cy="facility-phone"]').type(phone); + } + + enterPincode(pincode: string) { + cy.get('[data-cy="facility-pincode"]').type(pincode); + } + + enterAddress(address: string) { + cy.get('[data-cy="facility-address"]').type(address); + } + + enterLatitude(latitude: string) { + cy.get('[data-cy="facility-latitude"]').type(latitude); + } + + enterLongitude(longitude: string) { + cy.get('[data-cy="facility-longitude"]').type(longitude); + } + + // Combined methods using individual functions + fillBasicDetails(name: string, facilityType: string, description: string) { + this.enterFacilityName(name); + this.selectFacilityType(facilityType); + this.enterDescription(description); + } + + selectFeatures(features: string[]) { + cy.clickAndMultiSelectOption("#facility-features", features); + } + + fillContactDetails(phone: string, pincode: string, address: string) { + this.enterPhoneNumber(phone); + this.enterPincode(pincode); + this.enterAddress(address); + } + + fillLocationDetails(latitude: string, longitude: string) { + this.enterLatitude(latitude); + this.enterLongitude(longitude); + } + + makePublicFacility() { + cy.get('[data-cy="make-facility-public"]').click(); + } + + submitFacilityCreationForm() { + cy.clickSubmitButton("Create Facility"); + } + + // Verification Methods + verifySuccessMessage() { + cy.verifyNotification("Facility created successfully"); + } + + verifyValidationErrors() { + cy.verifyErrorMessages([ + "Name is required", + "Facility type is required", + "Address is required", + "Phone number must start with +91 followed by 10 digits", + ]); + } + + searchFacility(facilityName: string) { + cy.get('[data-cy="search-facility"]').type(facilityName); + } + + verifyFacilityNameInCard(facilityName: string) { + cy.get('[data-cy="facility-cards"]').should("contain", facilityName); + } +} diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 54bc3be2666..c8c0dbbd3d8 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -1,69 +1,37 @@ import "cypress-localstorage-commands"; -const apiUrl = Cypress.env("API_URL"); +const LOCAL_STORAGE_MEMORY = {}; -Cypress.Commands.add("login", (username: string, password: string) => { - cy.log(`Logging in the user: ${username}:${password}`); - cy.visit("/"); - cy.get("input[id='username']").type(username); - cy.get("input[id='password']").type(password); - cy.get("button").contains("Login").click(); - return cy.url().should("include", "/facility"); -}); +Cypress.Commands.add("loginByApi", (role: string) => { + const token = LOCAL_STORAGE_MEMORY["care_token"]; -Cypress.Commands.add("refreshApiLogin", (username, password) => { - cy.request({ - method: "POST", - url: `${apiUrl}/api/v1/auth/login/`, - body: { - username, - password, - }, - failOnStatusCode: false, - }).then((response) => { - if (response.status === 200) { - cy.writeFile("cypress/fixtures/token.json", { - username: username, - access: response.body.access, - refresh: response.body.refresh, - }); - cy.setLocalStorage("care_access_token", response.body.access); - cy.setLocalStorage("care_refresh_token", response.body.refresh); - } else { - cy.log("An error occurred while logging in"); - } - }); -}); + if (!token) { + cy.fixture("users").then((users) => { + const user = users[role]; -Cypress.Commands.add("loginByApi", (username, password) => { - cy.log(`Logging in the user: ${username}:${password}`); - cy.task("readFileMaybe", "cypress/fixtures/token.json").then( - (tkn: unknown) => { - const token = JSON.parse(tkn as string); // Cast tkn to string - if (tkn && token.access && token.username === username) { - cy.request({ - method: "POST", - url: `${apiUrl}/api/v1/auth/token/verify/`, - body: { - token: token.access, - }, - headers: { - "Content-Type": "application/json", - }, - failOnStatusCode: false, - }).then((response) => { - if (response.status === 200) { - cy.setLocalStorage("care_access_token", token.access); - cy.setLocalStorage("care_refresh_token", token.refresh); - } else { - cy.refreshApiLogin(username, password); - } - }); - } else { - cy.refreshApiLogin(username, password); + if (!user) { + throw new Error(`User role "${role}" not found in users fixture`); } - }, - ); + + // First do UI login to get tokens + cy.get('[data-cy="username"]').type(user.username); + cy.get('[data-cy="password"]').type(user.password); + cy.get('[data-cy="submit"]').click(); + + // Verify successful login by checking we're not on login page + cy.url().should("not.include", "/login"); + + // Save session after successful login + Object.keys(localStorage).forEach((key) => { + LOCAL_STORAGE_MEMORY[key] = localStorage[key]; + }); + }); + } else { + // If token exists, just restore the session + Object.keys(LOCAL_STORAGE_MEMORY).forEach((key) => { + localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]); + }); + } }); Cypress.on("uncaught:exception", () => { @@ -113,8 +81,11 @@ Cypress.Commands.add( }, ); -Cypress.Commands.add("verifyNotification", (text) => { - return cy.get(".pnotify-container").should("exist").contains(text); +Cypress.Commands.add("verifyNotification", (text: string) => { + return cy + .get("li[data-sonner-toast] div[data-title]") + .should("exist") + .contains(text); }); Cypress.Commands.add("clearAllFilters", () => { @@ -237,13 +208,9 @@ Cypress.Commands.add("verifyContentPresence", (selector, texts) => { }); Cypress.Commands.add("verifyErrorMessages", (errorMessages: string[]) => { - const selector = ".error-text"; // Static selector - cy.get(selector).then(($errors) => { - const displayedErrorMessages = $errors - .map((_, el) => Cypress.$(el).text()) - .get(); - errorMessages.forEach((errorMessage) => { - expect(displayedErrorMessages).to.include(errorMessage); + cy.get("body").within(() => { + errorMessages.forEach((message) => { + cy.contains(message).scrollIntoView().should("be.visible"); }); }); }); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 59620bf8a6d..cfa6266439c 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -4,9 +4,7 @@ declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { - login(username: string, password: string): Chainable; - refreshApiLogin(username: string, password: string): Chainable; - loginByApi(username: string, password: string): Chainable; + loginByApi(role: string): Chainable; verifyNotification(msg: string): Chainable; awaitUrl( url: string, diff --git a/cypress/utils/commonUtils.ts b/cypress/utils/commonUtils.ts new file mode 100644 index 00000000000..c6cddc66840 --- /dev/null +++ b/cypress/utils/commonUtils.ts @@ -0,0 +1,21 @@ +export function generatePhoneNumber(): string { + // Create a new Uint8Array to store our random bytes + const randomBytes = new Uint8Array(1); + // Get a cryptographically secure random value + crypto.getRandomValues(randomBytes); + + // First digit should be 6, 7, 8, or 9 for Indian mobile numbers + const validFirstDigits = [6, 7, 8, 9]; + const firstDigit = validFirstDigits[randomBytes[0] % validFirstDigits.length]; + + // Generate remaining 9 digits using crypto + const remainingDigits = new Uint8Array(9); + crypto.getRandomValues(remainingDigits); + + // Convert to string and ensure each digit is 0-9 + const remainingDigitsStr = Array.from(remainingDigits) + .map((byte) => byte % 10) + .join(""); + + return `${firstDigit}${remainingDigitsStr}`; +} diff --git a/cypress/utils/facilityData.ts b/cypress/utils/facilityData.ts new file mode 100644 index 00000000000..7aa6e76497d --- /dev/null +++ b/cypress/utils/facilityData.ts @@ -0,0 +1,195 @@ +interface FacilityTestData { + name: string; + type: string; + description: string; + address: string; + pincode: string; + coordinates: { + latitude: string; + longitude: string; + }; + features: string[]; +} + +const FACILITY_FEATURES = [ + "CT Scan", + "Maternity Care", + "X-Ray", + "Neonatal Care", + "Operation Theater", + "Blood Bank", + "Emergency Services", +]; + +// Facility type prefixes +const facilityPrefixes = [ + "GHC", // Government Hospital + "PHC", // Primary Health Center + "CHC", // Community Health Center + "THC", // Taluk Hospital + "DH", // District Hospital +]; + +// Special institution prefixes +const specialPrefixes = [ + "Saint Maria", + "Holy Cross", + "Little Flower", + "Mar Sleeva", + "Saint Thomas", +]; + +// Ernakulam district locations with real coordinates +const locations = [ + { + name: "Aluva", + pincode: "683101", + coordinates: { latitude: "10.1004", longitude: "76.3570" }, + }, + { + name: "Angamaly", + pincode: "683572", + coordinates: { latitude: "10.1960", longitude: "76.3860" }, + }, + { + name: "Kalady", + pincode: "683574", + coordinates: { latitude: "10.1682", longitude: "76.4410" }, + }, + { + name: "Perumbavoor", + pincode: "683542", + coordinates: { latitude: "10.1071", longitude: "76.4750" }, + }, + { + name: "Kothamangalam", + pincode: "686691", + coordinates: { latitude: "10.0558", longitude: "76.6280" }, + }, + { + name: "Muvattupuzha", + pincode: "686661", + coordinates: { latitude: "9.9894", longitude: "76.5790" }, + }, + { + name: "Piravom", + pincode: "686664", + coordinates: { latitude: "9.8730", longitude: "76.4920" }, + }, + { + name: "Kolenchery", + pincode: "682311", + coordinates: { latitude: "9.9811", longitude: "76.4007" }, + }, + { + name: "Tripunithura", + pincode: "682301", + coordinates: { latitude: "9.9486", longitude: "76.3289" }, + }, + { + name: "Kakkanad", + pincode: "682030", + coordinates: { latitude: "10.0158", longitude: "76.3419" }, + }, + { + name: "Edappally", + pincode: "682024", + coordinates: { latitude: "10.0236", longitude: "76.3097" }, + }, + { + name: "Kaloor", + pincode: "682017", + coordinates: { latitude: "9.9894", longitude: "76.2998" }, + }, + { + name: "Fort Kochi", + pincode: "682001", + coordinates: { latitude: "9.9639", longitude: "76.2432" }, + }, + { + name: "Mattancherry", + pincode: "682002", + coordinates: { latitude: "9.9585", longitude: "76.2574" }, + }, + { + name: "Vytilla", + pincode: "682019", + coordinates: { latitude: "9.9712", longitude: "76.3186" }, + }, + { + name: "Palarivattom", + pincode: "682025", + coordinates: { latitude: "10.0070", longitude: "76.3050" }, + }, + { + name: "Thevara", + pincode: "682013", + coordinates: { latitude: "9.9312", longitude: "76.2891" }, + }, + { + name: "Thrikkakara", + pincode: "682021", + coordinates: { latitude: "10.0316", longitude: "76.3421" }, + }, + { + name: "Kalamassery", + pincode: "683104", + coordinates: { latitude: "10.0558", longitude: "76.3213" }, + }, + { + name: "Eloor", + pincode: "683501", + coordinates: { latitude: "10.0583", longitude: "76.2833" }, + }, +]; + +function generateFacilityName(): string { + // Use crypto.getRandomValues for secure random selection + const randomBytes = new Uint8Array(2); + crypto.getRandomValues(randomBytes); + + const useSpecialPrefix = randomBytes[0] % 5 === 0; // 20% chance + const locationIndex = randomBytes[1] % locations.length; + const location = locations[locationIndex]; + + if (useSpecialPrefix) { + const specialPrefixIndex = randomBytes[0] % specialPrefixes.length; + const specialPrefix = specialPrefixes[specialPrefixIndex]; + return `${specialPrefix} GHC ${location.name}`; + } else { + const prefixIndex = randomBytes[0] % facilityPrefixes.length; + const prefix = facilityPrefixes[prefixIndex]; + return `${prefix} ${location.name}`; + } +} + +export function generateFacilityData(): FacilityTestData { + const randomBytes = new Uint8Array(2); + crypto.getRandomValues(randomBytes); + + const locationIndex = randomBytes[0] % locations.length; + const location = locations[locationIndex]; + const name = generateFacilityName(); + + // Generate number of features (2-4) + const numberOfFeatures = (randomBytes[1] % 3) + 2; + + // Securely shuffle features array + const shuffledFeatures = [...FACILITY_FEATURES] + .map((value) => ({ + value, + sort: crypto.getRandomValues(new Uint8Array(1))[0], + })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value); + + return { + name, + type: "General Hospital", + description: `Healthcare facility serving the ${location.name} region with modern medical facilities and experienced staff`, + address: `${name} Building, Main Road, ${location.name}`, + pincode: location.pincode, + coordinates: location.coordinates, + features: shuffledFeatures.slice(0, numberOfFeatures), + }; +} diff --git a/public/locale/en.json b/public/locale/en.json index 3fbcfc3de72..5d33733b15c 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -923,6 +923,7 @@ "external_identifier": "External Identifier", "facilities": "Facilities", "facility": "Facility", + "facility_added_successfully": "Facility created successfully", "facility_consent_requests_page_title": "Patient Consent List", "facility_district_name": "Facility/District Name", "facility_district_pincode": "Facility/District/Pincode", diff --git a/src/components/Facility/CreateFacilityForm.tsx b/src/components/Facility/CreateFacilityForm.tsx index 8ad9887d2ef..ed5c87caabc 100644 --- a/src/components/Facility/CreateFacilityForm.tsx +++ b/src/components/Facility/CreateFacilityForm.tsx @@ -177,13 +177,17 @@ export default function CreateFacilityForm({ Facility Type + @@ -218,6 +226,7 @@ export default function CreateFacilityForm({