diff --git a/.eslintrc b/.eslintrc
index 398d43e03..a5d273232 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -2,9 +2,14 @@
"env": {
"node": true
},
- "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
+ "extends": [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:jest-dom/recommended",
+ "plugin:testing-library/react"
+ ],
"parser": "@typescript-eslint/parser",
- "plugins": ["@typescript-eslint", "react-hooks"],
+ "plugins": ["@typescript-eslint", "jest-dom", "react-hooks", "testing-library"],
"rules": {
"react-hooks/rules-of-hooks": "error",
// Disabling these rules for now just to keep the diff small. I'll enable them one by one as we go.
@@ -14,6 +19,11 @@
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/triple-slash-reference": "off",
+ // The following rules need `noImplicitAny` to be set to `true` in our tsconfig. They are too restrictive for now, but should be reconsidered in future
+ "@typescript-eslint/no-unsafe-assignment": "off",
+ "@typescript-eslint/no-unsafe-call": "off",
+ "@typescript-eslint/no-unsafe-member-access": "off",
+ "@typescript-eslint/unbound-method": "off",
// Use `import type` instead of `import` for type imports https://typescript-eslint.io/blog/consistent-type-imports-and-exports-why-and-how
"@typescript-eslint/consistent-type-imports": [
"error",
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 7291ff859..d317b47f8 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -8,6 +8,11 @@ on:
branches:
- main
+env:
+ TURBO_API: 'http://127.0.0.1:9080'
+ TURBO_TOKEN: ${{ secrets.TURBO_SERVER_TOKEN }}
+ TURBO_TEAM: ${{ github.repository_owner }}
+
jobs:
main:
runs-on: ubuntu-latest
@@ -35,11 +40,18 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --immutable
+ - name: Setup local cache server for Turborepo
+ uses: felixmosh/turborepo-gh-artifacts@v3
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ server-token: ${{ env.TURBO_TOKEN }}
+
+
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
- name: Build apps
- run: yarn turbo run build --concurrency=5
+ run: yarn turbo run build --color --concurrency=5
- name: Run dev server
run: bash e2e/support/github/run-e2e-docker-env.sh
@@ -51,7 +63,7 @@ jobs:
run: yarn playwright test
- name: Stop dev server
- if: "!cancelled()"
+ if: '!cancelled()'
run: docker stop $(docker ps -a -q)
- name: Upload Report
diff --git a/__mocks__/index.ts b/__mocks__/index.ts
index f39744475..765a26c74 100644
--- a/__mocks__/index.ts
+++ b/__mocks__/index.ts
@@ -14,4 +14,5 @@ export * from './queue-rooms.mock';
export * from './search.mock';
export * from './session.mock';
export * from './wards.mock';
+export * from './ward-patient';
export * from './visits.mock';
diff --git a/package.json b/package.json
index 70fea0b9e..54bd254ac 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,9 @@
"dayjs": "^1.8.36",
"dotenv": "^16.0.3",
"eslint": "^8.55.0",
+ "eslint-plugin-jest-dom": "^5.4.0",
"eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-testing-library": "^6.2.2",
"husky": "^8.0.3",
"i18next": "^21.10.0",
"i18next-parser": "^6.6.0",
diff --git a/packages/esm-active-visits-app/src/active-visits-widget/active-visits.test.tsx b/packages/esm-active-visits-app/src/active-visits-widget/active-visits.test.tsx
index 4eb9fb350..fa1d4c3f3 100644
--- a/packages/esm-active-visits-app/src/active-visits-widget/active-visits.test.tsx
+++ b/packages/esm-active-visits-app/src/active-visits-widget/active-visits.test.tsx
@@ -57,7 +57,7 @@ describe('ActiveVisitsTable', () => {
await user.type(searchInput, 'John');
expect(screen.getByText('John Doe')).toBeInTheDocument();
- expect(screen.queryByText('Some One')).toBeNull();
+ expect(screen.queryByText('Some One')).not.toBeInTheDocument();
});
it('displays empty state when there are no active visits', () => {
diff --git a/packages/esm-active-visits-app/src/visits-summary/visit-detail.test.tsx b/packages/esm-active-visits-app/src/visits-summary/visit-detail.test.tsx
index 56a3d590f..ea2e7839c 100644
--- a/packages/esm-active-visits-app/src/visits-summary/visit-detail.test.tsx
+++ b/packages/esm-active-visits-app/src/visits-summary/visit-detail.test.tsx
@@ -1,8 +1,8 @@
import React from 'react';
import { render, screen, act } from '@testing-library/react';
-import VisitDetailComponent from './visit-detail.component';
-import { useVisit } from './visit.resource';
import { formatDate } from '@openmrs/esm-framework';
+import { useVisit } from './visit.resource';
+import VisitDetailComponent from './visit-detail.component';
jest.mock('./visit.resource');
@@ -117,6 +117,6 @@ describe('VisitDetailComponent', () => {
render();
- expect(screen.queryByRole('button', { name: 'All Encounters' })).toBeNull();
+ expect(screen.queryByRole('button', { name: 'All Encounters' })).not.toBeInTheDocument();
});
});
diff --git a/packages/esm-active-visits-app/translations/am.json b/packages/esm-active-visits-app/translations/am.json
index c9bb3a0e6..2d0e9b820 100644
--- a/packages/esm-active-visits-app/translations/am.json
+++ b/packages/esm-active-visits-app/translations/am.json
@@ -17,7 +17,7 @@
"noDiagnosesFound": "No diagnoses found",
"noEncountersFound": "No encounters found",
"noMedicationsFound": "No medications found",
- "noNotesToShowForPatient": "There are no notes to display for this patient",
+ "noNotesToShowForPatient": "No hay notas para mostrar de este paciente",
"noObservationsFound": "No observations found",
"notes": "Notes",
"noVisitsToDisplay": "No visits to display",
diff --git a/packages/esm-active-visits-app/translations/es.json b/packages/esm-active-visits-app/translations/es.json
index dbd0ae068..e7864e65b 100644
--- a/packages/esm-active-visits-app/translations/es.json
+++ b/packages/esm-active-visits-app/translations/es.json
@@ -21,7 +21,7 @@
"noObservationsFound": "No se encontraron observaciones",
"notes": "Notas",
"noVisitsToDisplay": "No hay visitas para mostrar",
- "orderDurationAndUnit": "para {duración} {duraciónUnidad}",
+ "orderDurationAndUnit": "para {{duration}} {{durationUnit}}",
"orderIndefiniteDuration": "Duración indefinida",
"provider": "Proveedor",
"quantity": "Cantidad",
diff --git a/packages/esm-appointments-app/src/appointments.component.tsx b/packages/esm-appointments-app/src/appointments.component.tsx
index 964efd942..e475d3193 100644
--- a/packages/esm-appointments-app/src/appointments.component.tsx
+++ b/packages/esm-appointments-app/src/appointments.component.tsx
@@ -4,7 +4,6 @@ import dayjs from 'dayjs';
import AppointmentTabs from './appointments/appointment-tabs.component';
import AppointmentsHeader from './header/appointments-header.component';
import AppointmentMetrics from './metrics/appointments-metrics.component';
-import { WorkspaceOverlay } from '@openmrs/esm-framework';
import { useParams } from 'react-router-dom';
import SelectedDateContext from './hooks/selectedDateContext';
import { omrsDateFormat } from './constants';
diff --git a/packages/esm-appointments-app/src/appointments/common-components/appointments-actions.test.tsx b/packages/esm-appointments-app/src/appointments/common-components/appointments-actions.test.tsx
index c84456a65..4ebf8b060 100644
--- a/packages/esm-appointments-app/src/appointments/common-components/appointments-actions.test.tsx
+++ b/packages/esm-appointments-app/src/appointments/common-components/appointments-actions.test.tsx
@@ -1,9 +1,9 @@
-import { render } from '@testing-library/react';
import React from 'react';
-import AppointmentActions from './appointments-actions.component';
+import { render, screen } from '@testing-library/react';
+import { useConfig } from '@openmrs/esm-framework';
import { useTodaysVisits } from '../../hooks/useTodaysVisits';
import { type Appointment, AppointmentKind, AppointmentStatus } from '../../types';
-import { useConfig } from '@openmrs/esm-framework';
+import AppointmentActions from './appointments-actions.component';
const appointment: Appointment = {
uuid: '7cd38a6d-377e-491b-8284-b04cf8b8c6d8',
@@ -46,8 +46,13 @@ const appointment: Appointment = {
voided: false,
teleconsultationLink: null,
extensions: [],
+ endDateTime: null,
+ dateAppointmentScheduled: null,
};
+const mockUseConfig = useConfig as jest.Mock;
+const mockUseTodaysVisits = useTodaysVisits as jest.Mock;
+
jest.mock('../../hooks/useTodaysVisits', () => {
const originalModule = jest.requireActual('../../hooks/useTodaysVisits');
@@ -79,41 +84,41 @@ describe('AppointmentActions', () => {
it('renders the check in button when appointment is today and the patient has not checked in and check in button enabled', () => {
appointment.status = AppointmentStatus.SCHEDULED;
- useConfig.mockImplementation(() => ({
+ mockUseConfig.mockImplementation(() => ({
checkInButton: { enabled: true },
checkOutButton: { enabled: true },
}));
- useTodaysVisits.mockImplementation(() => ({
+ mockUseTodaysVisits.mockImplementation(() => ({
visits: [],
}));
const props = { ...defaultProps };
- const { getByText } = render();
- const button = getByText(/check in/i);
- expect(button).toBeInTheDocument();
+ render();
+
+ expect(screen.getByText(/check in/i)).toBeInTheDocument();
});
it('does not renders the check in button when appointment is today and the patient has not checked in but the check-in button is disabled', () => {
appointment.status = AppointmentStatus.SCHEDULED;
- useConfig.mockImplementation(() => ({
+ mockUseConfig.mockImplementation(() => ({
checkInButton: { enabled: false },
checkOutButton: { enabled: true },
}));
- useTodaysVisits.mockImplementation(() => ({
+ mockUseTodaysVisits.mockImplementation(() => ({
visits: [],
}));
const props = { ...defaultProps };
- const { queryByText } = render();
- const button = queryByText('Check In');
- expect(button).not.toBeInTheDocument();
+ render();
+
+ expect(screen.queryByText(/check in/i)).not.toBeInTheDocument();
});
it('renders the checked out button when the patient has checked out', () => {
appointment.status = AppointmentStatus.COMPLETED;
- useConfig.mockImplementation(() => ({
+ mockUseConfig.mockImplementation(() => ({
checkInButton: { enabled: true },
checkOutButton: { enabled: true },
}));
- useTodaysVisits.mockImplementation(() => ({
+ mockUseTodaysVisits.mockImplementation(() => ({
visits: [
{
patient: { uuid: '8673ee4f-e2ab-4077-ba55-4980f408773e' },
@@ -123,18 +128,18 @@ describe('AppointmentActions', () => {
],
}));
const props = { ...defaultProps };
- const { getByText } = render();
- const button = getByText('Checked out');
- expect(button).toBeInTheDocument();
+ render();
+
+ expect(screen.getByText('Checked out')).toBeInTheDocument();
});
it('renders the check out button when the patient has an active visit and today is the appointment date and the check out button enabled', () => {
appointment.status = AppointmentStatus.CHECKEDIN;
- useConfig.mockImplementation(() => ({
+ mockUseConfig.mockImplementation(() => ({
checkInButton: { enabled: true },
checkOutButton: { enabled: true },
}));
- useTodaysVisits.mockImplementation(() => ({
+ mockUseTodaysVisits.mockImplementation(() => ({
visits: [
{
patient: { uuid: '8673ee4f-e2ab-4077-ba55-4980f408773e' },
@@ -144,18 +149,18 @@ describe('AppointmentActions', () => {
],
}));
const props = { ...defaultProps, scheduleType: 'Scheduled' };
- const { getByText } = render();
- const button = getByText('Check out');
- expect(button).toBeInTheDocument();
+ render();
+
+ expect(screen.getByText(/check out/i)).toBeInTheDocument();
});
it('does not render check out button when the patient has an active visit and today is the appointment date but the check out button is disabled', () => {
appointment.status = AppointmentStatus.CHECKEDIN;
- useConfig.mockImplementation(() => ({
+ mockUseConfig.mockImplementation(() => ({
checkInButton: { enabled: true },
checkOutButton: { enabled: false },
}));
- useTodaysVisits.mockImplementation(() => ({
+ mockUseTodaysVisits.mockImplementation(() => ({
visits: [
{
patient: { uuid: '8673ee4f-e2ab-4077-ba55-4980f408773e' },
@@ -165,18 +170,18 @@ describe('AppointmentActions', () => {
],
}));
const props = { ...defaultProps, scheduleType: 'Scheduled' };
- const { queryByText } = render();
- const button = queryByText('Check out');
- expect(button).not.toBeInTheDocument();
+ render();
+
+ expect(screen.queryByText(/check out/i)).not.toBeInTheDocument();
});
// commenting these tests out as this functionality is not implemented yet so not sure how they would have ever passed?
/*it('renders the correct button when today is the appointment date and the schedule type is pending', () => {
- useConfig.mockImplementation(() => ({
+ mockUseConfig.mockImplementation(() => ({
checkInButton: { enabled: true },
checkOutButton: { enabled: true },
}));
- useTodaysVisits.mockImplementation(() => ({
+ mockUseTodaysVisits.mockImplementation(() => ({
visits: [],
}));
const props = { ...defaultProps, scheduleType: 'Pending' };
@@ -186,11 +191,11 @@ describe('AppointmentActions', () => {
});
it('renders the correct button when today is the appointment date and the schedule type is not pending', () => {
- useConfig.mockImplementation(() => ({
+ mockUseConfig.mockImplementation(() => ({
checkInButton: { enabled: true },
checkOutButton: { enabled: true },
}));
- useTodaysVisits.mockImplementation(() => ({
+ mockUseTodaysVisits.mockImplementation(() => ({
visits: [],
}));
const props = { ...defaultProps, scheduleType: 'Confirmed' };
diff --git a/packages/esm-appointments-app/src/appointments/common-components/end-appointment.test.tsx b/packages/esm-appointments-app/src/appointments/common-components/end-appointment.test.tsx
index 3883740fa..cf70a7cbb 100644
--- a/packages/esm-appointments-app/src/appointments/common-components/end-appointment.test.tsx
+++ b/packages/esm-appointments-app/src/appointments/common-components/end-appointment.test.tsx
@@ -38,7 +38,7 @@ describe('EndAppointmentModal', () => {
render();
const submitButton = screen.getByRole('button', { name: /check out/i });
- expect(submitButton).not.toBeDisabled();
+ expect(submitButton).toBeEnabled();
await user.click(submitButton);
expect(changeAppointmentStatus).toHaveBeenCalledWith('Completed', 'abc');
@@ -63,7 +63,7 @@ describe('EndAppointmentModal', () => {
render();
const submitButton = screen.getByRole('button', { name: /check out/i });
- expect(submitButton).not.toBeDisabled();
+ expect(submitButton).toBeEnabled();
await user.click(submitButton);
expect(changeAppointmentStatus).toHaveBeenCalledWith('Completed', 'abc');
diff --git a/packages/esm-appointments-app/src/appointments/details/appointment-details.test.tsx b/packages/esm-appointments-app/src/appointments/details/appointment-details.test.tsx
index dfdae14e0..4efdc0885 100644
--- a/packages/esm-appointments-app/src/appointments/details/appointment-details.test.tsx
+++ b/packages/esm-appointments-app/src/appointments/details/appointment-details.test.tsx
@@ -1,7 +1,7 @@
import React from 'react';
-import { render } from '@testing-library/react';
-import AppointmentDetails from './appointment-details.component';
+import { render, screen } from '@testing-library/react';
import { type Appointment } from '../../types';
+import AppointmentDetails from './appointment-details.component';
const appointment: Appointment = {
uuid: '7cd38a6d-377e-491b-8284-b04cf8b8c6d8',
@@ -77,27 +77,27 @@ jest.mock('@openmrs/esm-framework', () => {
});
test('renders appointment details correctly', async () => {
- const { getByText } = render();
- expect(getByText(/Patient name/i)).toBeInTheDocument();
- expect(getByText(/John Wilson/i)).toBeInTheDocument();
- expect(getByText(/Age/i)).toBeInTheDocument();
- expect(getByText(/34/i)).toBeInTheDocument();
- expect(getByText(/Gender/i)).toBeInTheDocument();
- expect(getByText(/Male/i)).toBeInTheDocument();
- expect(getByText(/Date of birth/i)).toBeInTheDocument();
- expect(getByText(/Date of birth/i)).toBeInTheDocument();
- expect(getByText(/22-Mar-2020/i)).toBeInTheDocument();
- expect(getByText(/Contact 1/i)).toBeInTheDocument();
- expect(getByText(/0899129989932/i)).toBeInTheDocument();
- expect(getByText(/Appointment Notes/i)).toBeInTheDocument();
- expect(getByText(/Some comments/i)).toBeInTheDocument();
- expect(getByText(/Appointment History/i)).toBeInTheDocument();
- expect(getByText(/Completed/i)).toBeInTheDocument();
- expect(getByText('1', { exact: true })).toBeInTheDocument();
- expect(getByText(/Missed/i)).toBeInTheDocument();
- expect(getByText('2', { exact: true })).toBeInTheDocument();
- expect(getByText(/Cancelled/i)).toBeInTheDocument();
- expect(getByText('3', { exact: true })).toBeInTheDocument();
- expect(getByText(/Upcoming/i)).toBeInTheDocument();
- expect(getByText('4', { exact: true })).toBeInTheDocument();
+ render();
+ expect(screen.getByText(/Patient name/i)).toBeInTheDocument();
+ expect(screen.getByText(/John Wilson/i)).toBeInTheDocument();
+ expect(screen.getByText(/Age/i)).toBeInTheDocument();
+ expect(screen.getByText(/34/i)).toBeInTheDocument();
+ expect(screen.getByText(/Gender/i)).toBeInTheDocument();
+ expect(screen.getByText(/Male/i)).toBeInTheDocument();
+ expect(screen.getByText(/Date of birth/i)).toBeInTheDocument();
+ expect(screen.getByText(/Date of birth/i)).toBeInTheDocument();
+ expect(screen.getByText(/22-Mar-2020/i)).toBeInTheDocument();
+ expect(screen.getByText(/Contact 1/i)).toBeInTheDocument();
+ expect(screen.getByText(/0899129989932/i)).toBeInTheDocument();
+ expect(screen.getByText(/Appointment Notes/i)).toBeInTheDocument();
+ expect(screen.getByText(/Some comments/i)).toBeInTheDocument();
+ expect(screen.getByText(/Appointment History/i)).toBeInTheDocument();
+ expect(screen.getByText(/Completed/i)).toBeInTheDocument();
+ expect(screen.getByText('1', { exact: true })).toBeInTheDocument();
+ expect(screen.getByText(/Missed/i)).toBeInTheDocument();
+ expect(screen.getByText('2', { exact: true })).toBeInTheDocument();
+ expect(screen.getByText(/Cancelled/i)).toBeInTheDocument();
+ expect(screen.getByText('3', { exact: true })).toBeInTheDocument();
+ expect(screen.getByText(/Upcoming/i)).toBeInTheDocument();
+ expect(screen.getByText('4', { exact: true })).toBeInTheDocument();
});
diff --git a/packages/esm-appointments-app/src/form/appointments-form.test.tsx b/packages/esm-appointments-app/src/form/appointments-form.test.tsx
index f2f40f21f..5cd366ce8 100644
--- a/packages/esm-appointments-app/src/form/appointments-form.test.tsx
+++ b/packages/esm-appointments-app/src/form/appointments-form.test.tsx
@@ -158,7 +158,7 @@ describe('AppointmentForm', () => {
const serviceSelect = screen.getByRole('combobox', { name: /Select a service/i });
const appointmentTypeSelect = screen.getByRole('combobox', { name: /Select the type of appointment/i });
- expect(saveButton).not.toBeDisabled();
+ expect(saveButton).toBeEnabled();
await user.clear(dateInput);
await user.type(dateInput, '4/4/2021');
diff --git a/packages/esm-appointments-app/src/patient-appointments/patient-appointments-overview.component.tsx b/packages/esm-appointments-app/src/patient-appointments/patient-appointments-overview.component.tsx
index 50755a402..5f8490aba 100644
--- a/packages/esm-appointments-app/src/patient-appointments/patient-appointments-overview.component.tsx
+++ b/packages/esm-appointments-app/src/patient-appointments/patient-appointments-overview.component.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { usePatient, useLayoutType, isDesktop, WorkspaceOverlay } from '@openmrs/esm-framework';
+import { usePatient, useLayoutType, isDesktop, WorkspaceContainer } from '@openmrs/esm-framework';
import PatientAppointmentsBase from './patient-appointments-base.component';
import { useParams } from 'react-router-dom';
import PatientAppointmentContext, { PatientAppointmentContextTypes } from '../hooks/patientAppointmentContext';
@@ -26,7 +26,7 @@ const PatientAppointmentsOverview: React.FC = () => {
);
diff --git a/packages/esm-patient-list-management-app/src/list-details/list-details.test.tsx b/packages/esm-patient-list-management-app/src/list-details/list-details.test.tsx
index 8c74af695..9645f8a72 100644
--- a/packages/esm-patient-list-management-app/src/list-details/list-details.test.tsx
+++ b/packages/esm-patient-list-management-app/src/list-details/list-details.test.tsx
@@ -87,12 +87,12 @@ describe('ListDetails', () => {
expect(screen.getByText(/there are no patients in this list/i)).toBeInTheDocument();
});
- it('opens overlay with a form when the "Edit name or description" button is clicked', () => {
+ it('opens overlay with a form when the "Edit name or description" button is clicked', async () => {
render();
- userEvent.click(screen.getByText('Actions'));
+ await userEvent.click(screen.getByText('Actions'));
const editBtn = screen.getByText('Edit name or description');
- userEvent.click(editBtn);
+ await userEvent.click(editBtn);
});
it('deletes patient list and navigates back to the list page', async () => {
@@ -105,7 +105,8 @@ describe('ListDetails', () => {
expect(screen.getByText('Delete')).toBeInTheDocument();
expect(screen.getByText('Cancel')).toBeInTheDocument();
- expect(screen.getByText('Delete').closest('button')).not.toHaveAttribute('disabled');
+ // eslint-disable-next-line testing-library/no-node-access
+ expect(screen.getByText('Delete').closest('button')).toBeEnabled();
await userEvent.click(screen.getByText('Cancel'));
});
diff --git a/packages/esm-patient-registration-app/src/add-patient-link.test.tsx b/packages/esm-patient-registration-app/src/add-patient-link.test.tsx
index 0d94c3131..cd1842104 100644
--- a/packages/esm-patient-registration-app/src/add-patient-link.test.tsx
+++ b/packages/esm-patient-registration-app/src/add-patient-link.test.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
-import { render } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
import { navigate } from '@openmrs/esm-framework';
import Root from './add-patient-link';
@@ -10,10 +10,9 @@ describe('Add patient link component', () => {
it('renders an "Add Patient" button and triggers navigation on click', async () => {
const user = userEvent.setup();
- const { getByRole } = render();
- const addButton = getByRole('button', { name: /add patient/i });
+ render();
- await user.click(addButton);
+ await user.click(screen.getByRole('button', { name: /add patient/i }));
expect(mockedNavigate).toHaveBeenCalledWith({ to: '${openmrsSpaBase}/patient-registration' });
});
diff --git a/packages/esm-patient-registration-app/src/nav-link.test.tsx b/packages/esm-patient-registration-app/src/nav-link.test.tsx
index 36428973e..3e2aa99bf 100644
--- a/packages/esm-patient-registration-app/src/nav-link.test.tsx
+++ b/packages/esm-patient-registration-app/src/nav-link.test.tsx
@@ -1,11 +1,11 @@
import React from 'react';
-import { render } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
import Root from './nav-link';
describe('Nav link component', () => {
it('renders a link to the patient registration page', () => {
- const { getByText } = render();
- const linkElement = getByText('Patient Registration');
+ render();
+ const linkElement = screen.getByText('Patient Registration');
expect(linkElement).toBeInTheDocument();
expect(linkElement).toHaveAttribute('href', '/openmrs/spa/patient-registration');
diff --git a/packages/esm-patient-registration-app/src/offline.resources.ts b/packages/esm-patient-registration-app/src/offline.resources.ts
index f0d91f3f7..1e36e6ba3 100644
--- a/packages/esm-patient-registration-app/src/offline.resources.ts
+++ b/packages/esm-patient-registration-app/src/offline.resources.ts
@@ -83,11 +83,13 @@ export async function fetchPatientIdentifierTypesWithSources(): Promise {
- const option = find(autoGenOptions.data.results, { source: { uuid: source.uuid } });
- source.autoGenerationOption = option;
- return source;
- });
+ identifierTypes[i].identifierSources = allIdentifierSources
+ .filter((source) => source.identifierType.uuid === identifierTypes[i].uuid)
+ .map((source) => {
+ const option = find(autoGenOptions.data.results, { source: { uuid: source.uuid } });
+ source.autoGenerationOption = option;
+ return source;
+ });
}
return identifierTypes;
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/address/tests/address-hierarchy.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/address/tests/address-hierarchy.test.tsx
index 860c1cc36..c649827fd 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/field/address/tests/address-hierarchy.test.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/field/address/tests/address-hierarchy.test.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { cleanup, render, screen } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
import { AddressComponent } from '../address-field.component';
import { Formik, Form } from 'formik';
import { type Resources, ResourcesContext } from '../../../../offline.resources';
@@ -44,8 +44,6 @@ async function renderAddressHierarchy(addressTemplate = mockedAddressTemplate) {
}
describe('Testing address hierarchy', () => {
- beforeEach(cleanup);
-
it('should render skeleton when address template is loading', () => {
(useConfig as jest.Mock).mockImplementation(() => ({
fieldConfigurations: {
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx
index 465839cb1..9052b0574 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx
@@ -1,14 +1,14 @@
import React, { useContext } from 'react';
import { RadioButton, RadioButtonGroup } from '@carbon/react';
-import styles from '../field.scss';
import { useTranslation } from 'react-i18next';
import { PatientRegistrationContext } from '../../patient-registration-context';
import { useField } from 'formik';
import { type RegistrationConfig } from '../../../config-schema';
import { useConfig } from '@openmrs/esm-framework';
+import styles from '../field.scss';
export const GenderField: React.FC = () => {
- const { fieldConfigurations } = useConfig() as RegistrationConfig;
+ const { fieldConfigurations } = useConfig();
const { t } = useTranslation();
const [field, meta] = useField('gender');
const { setFieldValue } = useContext(PatientRegistrationContext);
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.test.tsx
index 6b239330a..89c6d4ce9 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.test.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.test.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
import { Formik, Form } from 'formik';
-import { render } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
import { GenderField } from './gender-field.component';
jest.mock('@openmrs/esm-framework', () => ({
@@ -13,6 +13,10 @@ jest.mock('@openmrs/esm-framework', () => ({
value: 'male',
label: 'Male',
},
+ {
+ value: 'female',
+ label: 'Female',
+ },
],
name: {
displayMiddleName: false,
@@ -37,23 +41,30 @@ jest.mock('formik', () => ({
}));
describe('GenderField', () => {
- const renderComponent = () => {
- return render(
-
-
- ,
- );
- };
-
it('has a label', () => {
- expect(renderComponent().getAllByText('Sex')).toBeTruthy();
+ renderGenderField();
+
+ expect(screen.getByRole('heading', { name: /sex/i })).toBeInTheDocument();
+ expect(screen.getByLabelText(/^male/i)).toBeInTheDocument();
+ expect(screen.getByLabelText(/female/i)).toBeInTheDocument();
});
it('checks an option', async () => {
const user = userEvent.setup();
- const component = renderComponent();
- expect(component.getByLabelText('Male').getAttribute('value')).toBe('male');
+ renderGenderField();
+
+ await user.click(screen.getByText(/female/i));
+ expect(screen.getByLabelText(/female/i)).toBeChecked();
+ expect(screen.getByLabelText(/^male/i)).not.toBeChecked();
});
});
+
+function renderGenderField() {
+ render(
+
+
+ ,
+ );
+}
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.test.tsx
index 56440f138..0dbbe3890 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.test.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.test.tsx
@@ -193,7 +193,7 @@ describe('ObsField', () => {
expect(console.error).toHaveBeenCalledWith(
expect.stringMatching(/no registration encounter type has been configured/i),
);
- expect(screen.queryByRole('textbox')).toBeNull();
+ expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
});
it('renders a text box for text concept', () => {
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx
index 14cd16fa9..251154042 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx
@@ -1,17 +1,13 @@
import React from 'react';
+import { Form, Formik } from 'formik';
import { render, screen } from '@testing-library/react';
import { usePersonAttributeType } from './person-attributes.resource';
-import { PersonAttributeField } from './person-attribute-field.component';
import { useConceptAnswers } from '../field.resource';
-import { Form, Formik } from 'formik';
import { type FieldDefinition } from '../../../config-schema';
+import { PersonAttributeField } from './person-attribute-field.component';
-jest.mock('./person-attributes.resource'); // Mock the usePersonAttributeType hook
-jest.mock('../field.resource'); // Mock the useConceptAnswers hook
-
-jest.mock('formik', () => ({
- ...jest.requireActual('formik'),
-}));
+jest.mock('./person-attributes.resource');
+jest.mock('../field.resource');
const mockedUsePersonAttributeType = usePersonAttributeType as jest.Mock;
const mockedUseConceptAnswers = useConceptAnswers as jest.Mock;
@@ -28,7 +24,7 @@ describe('PersonAttributeField', () => {
beforeEach(() => {
fieldDefinition = {
id: 'referredby',
- name: 'Referred by',
+ label: 'Referred by',
type: 'person attribute',
uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
answerConceptSetUuid: '6682d17f-0777-45e4-a39b-93f77eb3531c',
@@ -135,6 +131,7 @@ describe('PersonAttributeField', () => {
expect(screen.getByText('Error')).toBeInTheDocument();
expect(screen.getByText(/Patient attribute type has unknown format/i)).toBeInTheDocument();
});
+
it('renders an error notification if unable to fetch attribute type', () => {
mockedUsePersonAttributeType.mockReturnValue({
data: null,
@@ -143,9 +140,11 @@ describe('PersonAttributeField', () => {
});
fieldDefinition = {
+ id: 'referredBy',
uuid: 'attribute-uuid',
label: 'Attribute',
showHeading: false,
+ type: 'person attribute',
};
render(
@@ -160,7 +159,7 @@ describe('PersonAttributeField', () => {
expect(screen.getByText(/Unable to fetch person attribute type/i)).toBeInTheDocument();
});
- it('renders a skeleton if attribute type is loading', () => {
+ it('renders a skeleton if attribute type is loading', async () => {
mockedUsePersonAttributeType.mockReturnValue({
data: null,
isLoading: true,
@@ -168,9 +167,11 @@ describe('PersonAttributeField', () => {
});
fieldDefinition = {
+ id: 'referredBy',
uuid: 'attribute-uuid',
label: 'Attribute',
showHeading: true,
+ type: 'person attribute',
};
render(
@@ -180,8 +181,8 @@ describe('PersonAttributeField', () => {
,
);
- const input = screen.findByLabelText(/Reffered by/i);
- expect(screen.getByText(/Attribute/i)).toBeInTheDocument();
- expect(input).not.toBeNull(); // checks that the input is not rendered when the attribute type is loading
+ await screen.findByRole('heading', { name: /attribute/i });
+ const input = screen.queryByLabelText(/Referred by/i);
+ expect(input).not.toBeInTheDocument(); // checks that the input is not rendered when the attribute type is loading
});
});
diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/select/select-input.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/select/select-input.test.tsx
index ba8735999..e1e9e8b54 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/select/select-input.test.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/select/select-input.test.tsx
@@ -40,7 +40,7 @@ describe('the select input', () => {
,
);
- await expect(screen.findByRole('combobox'));
+ await screen.findByRole('combobox');
const selectInput = screen.getByRole('combobox', { name: 'Select (optional)' }) as HTMLSelectElement;
expect(selectInput.labels).toHaveLength(1);
diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx
index 2bb5f9d2a..7e222f045 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx
@@ -38,7 +38,7 @@ describe('Autosuggest', () => {
renderAutosuggest();
expect(screen.getByRole('searchbox')).toBeInTheDocument();
- expect(screen.queryByRole('list')).toBeNull();
+ expect(screen.queryByRole('list')).not.toBeInTheDocument();
});
it('renders matching search results in a list when the user types a query', async () => {
@@ -52,7 +52,7 @@ describe('Autosuggest', () => {
const list = screen.getByRole('list');
expect(list).toBeInTheDocument();
- expect(list.children).toHaveLength(2);
+ expect(screen.getAllByRole('listitem').length).toEqual(2);
expect(screen.getAllByRole('listitem')[0]).toHaveTextContent('John Doe');
expect(screen.getAllByRole('listitem')[1]).toHaveTextContent('John Smith');
});
@@ -63,7 +63,7 @@ describe('Autosuggest', () => {
renderAutosuggest();
let list = screen.queryByRole('list');
- expect(list).toBeNull();
+ expect(list).not.toBeInTheDocument();
const searchbox = screen.getByRole('searchbox');
await user.type(searchbox, 'john');
@@ -77,7 +77,7 @@ describe('Autosuggest', () => {
expect(mockedHandleSuggestionSelected).toHaveBeenLastCalledWith('person', 'randomuuid1');
list = screen.queryByRole('list');
- expect(list).toBeNull();
+ expect(list).not.toBeInTheDocument();
});
it('changes suggestions when a search input is changed', async () => {
@@ -86,7 +86,7 @@ describe('Autosuggest', () => {
renderAutosuggest();
let list = screen.queryByRole('list');
- expect(list).toBeNull();
+ expect(list).not.toBeInTheDocument();
const searchbox = screen.getByRole('searchbox');
await user.type(searchbox, 'john');
@@ -97,7 +97,7 @@ describe('Autosuggest', () => {
await user.clear(searchbox);
list = screen.queryByRole('list');
- expect(list).toBeNull();
+ expect(list).not.toBeInTheDocument();
});
it('hides the list of suggestions when the user clicks outside of the component', async () => {
diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx
index d29f55856..adf9bc111 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable testing-library/no-node-access */
import React from 'react';
import { render, screen } from '@testing-library/react';
import { Formik, Form } from 'formik';
@@ -61,12 +62,14 @@ describe.skip('identifier input', () => {
it('exists', async () => {
const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
+
expect(identifierInput.type).toBe('text');
expect(identifierSourceSelectInput.type).toBe('select-one');
});
it('has correct props for identifier source select input', async () => {
const { identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
+
expect(identifierSourceSelectInput.childElementCount).toBe(3);
expect(identifierSourceSelectInput.value).toBe('Generator 1 for OpenMRS ID');
});
diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx
index b16339d33..3c46e30e5 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx
@@ -140,6 +140,7 @@ let mockOpenmrsConfig: RegistrationConfig = {
],
fieldDefinitions: [],
fieldConfigurations: {
+ phone: null,
dateOfBirth: {
allowEstimatedDateOfBirth: true,
useEstimatedDateOfBirth: {
@@ -170,9 +171,6 @@ let mockOpenmrsConfig: RegistrationConfig = {
},
},
},
- concepts: {
- patientPhotoUuid: '736e8771-e501-4615-bfa7-570c03f4bef5',
- },
links: {
submitButton: '#',
},
@@ -236,7 +234,6 @@ const fillRequiredFields = async () => {
const familyNameInput = within(demographicsSection).getByLabelText(/family/i) as HTMLInputElement;
const dateOfBirthInput = within(demographicsSection).getByLabelText(/date of birth/i) as HTMLInputElement;
const genderInput = within(demographicsSection).getByLabelText(/Male/) as HTMLSelectElement;
- screen.debug(dateOfBirthInput);
await user.type(givenNameInput, 'Paul');
await user.type(familyNameInput, 'Gaihre');
await user.clear(dateOfBirthInput);
@@ -268,8 +265,8 @@ describe('Registering a new patient', () => {
it('has the expected sections', async () => {
render(, { wrapper: Wrapper });
- expect(screen.getByLabelText(/Demographics Section/)).not.toBeNull();
- expect(screen.getByLabelText(/Contact Info Section/)).not.toBeNull();
+ expect(screen.getByRole('region', { name: /demographics section/i })).toBeInTheDocument();
+ expect(screen.getByRole('region', { name: /contact info section/i })).toBeInTheDocument();
});
// TODO O3-3482: Fix this test case when OpenmrsDatePicker gets fixed on core
diff --git a/packages/esm-patient-registration-app/translations/am.json b/packages/esm-patient-registration-app/translations/am.json
index b67e9967d..f6862c31e 100644
--- a/packages/esm-patient-registration-app/translations/am.json
+++ b/packages/esm-patient-registration-app/translations/am.json
@@ -14,12 +14,15 @@
"codedPersonAttributeNoAnswerSet": "The person attribute field '{{codedPersonAttributeFieldId}}' is of type 'coded' but has been defined without an answer concept set UUID. The 'answerConceptSetUuid' key is required.",
"configure": "Configure",
"configureIdentifiers": "Configure identifiers",
+ "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?",
"contactSection": "Contact Details",
"createNewPatient": "Create new patient",
"dateOfBirthLabelText": "Date of Birth",
"deathDateInputLabel": "Date of Death",
"deathdayNotInTheFuture": "Death day cannot be in the future",
"deathSection": "Death Info",
+ "deleteIdentifierModalHeading": "Remove identifier?",
+ "deleteIdentifierModalText": " has a value of ",
"deleteIdentifierTooltip": "Delete",
"deleteRelationshipTooltipText": "Delete",
"demographicsSection": "Basic Info",
@@ -74,6 +77,7 @@
"relationshipToPatient": "Relationship to patient",
"relativeFullNameLabelText": "Related person",
"relativeNamePlaceholder": "Firstname Familyname",
+ "removeIdentifierButton": "Remove Identifier",
"resetIdentifierTooltip": "Reset",
"restoreRelationshipActionButton": "Undo",
"searchAddress": "Search address",
diff --git a/packages/esm-patient-registration-app/translations/ar.json b/packages/esm-patient-registration-app/translations/ar.json
index 90d5e8fc1..dbb1f8967 100644
--- a/packages/esm-patient-registration-app/translations/ar.json
+++ b/packages/esm-patient-registration-app/translations/ar.json
@@ -14,12 +14,15 @@
"codedPersonAttributeNoAnswerSet": "The person attribute field '{{codedPersonAttributeFieldId}}' is of type 'coded' but has been defined without an answer concept set UUID. The 'answerConceptSetUuid' key is required.",
"configure": "تكوين",
"configureIdentifiers": "Configure identifiers",
+ "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?",
"contactSection": "تفاصيل الاتصال",
"createNewPatient": "Create new patient",
"dateOfBirthLabelText": "تاريخ الميلاد",
"deathDateInputLabel": "تاريخ الوفاة",
"deathdayNotInTheFuture": "Death day cannot be in the future",
"deathSection": "معلومات الوفاة",
+ "deleteIdentifierModalHeading": "Remove identifier?",
+ "deleteIdentifierModalText": " has a value of ",
"deleteIdentifierTooltip": "حذف",
"deleteRelationshipTooltipText": "حذف",
"demographicsSection": "معلومات أساسية",
@@ -74,6 +77,7 @@
"relationshipToPatient": "العلاقة بالمريض",
"relativeFullNameLabelText": "الاسم الكامل للقريب",
"relativeNamePlaceholder": "الاسم الأول اسم العائلة",
+ "removeIdentifierButton": "Remove Identifier",
"resetIdentifierTooltip": "إعادة تعيين",
"restoreRelationshipActionButton": "تراجع",
"searchAddress": "ابحث عن العنوان",
diff --git a/packages/esm-patient-registration-app/translations/es.json b/packages/esm-patient-registration-app/translations/es.json
index f82b05984..ec77cc9af 100644
--- a/packages/esm-patient-registration-app/translations/es.json
+++ b/packages/esm-patient-registration-app/translations/es.json
@@ -14,12 +14,15 @@
"codedPersonAttributeNoAnswerSet": "El campo de atributo de persona '{{codedPersonAttributeFieldId}}' es de tipo 'codificado' pero se ha definido sin UUID de conjunto de conceptos de respuesta. La clave 'answerConceptSetUuid' es requerida.",
"configure": "Configurar",
"configureIdentifiers": "Configurar identificadores",
+ "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?",
"contactSection": "Detalles de Contacto",
"createNewPatient": "Create new patient",
"dateOfBirthLabelText": "Fecha de Nacimiento",
"deathDateInputLabel": "Fecha de fallecimiento",
"deathdayNotInTheFuture": "El día de la muerte no puede ser en el futuro",
"deathSection": "Información de Fallecimiento",
+ "deleteIdentifierModalHeading": "Remove identifier?",
+ "deleteIdentifierModalText": " has a value of ",
"deleteIdentifierTooltip": "Eliminar",
"deleteRelationshipTooltipText": "Eliminar",
"demographicsSection": "Información Básica",
@@ -74,6 +77,7 @@
"relationshipToPatient": "Relación con el paciente",
"relativeFullNameLabelText": "Nombre y Apellidos",
"relativeNamePlaceholder": "Nombre Apellido",
+ "removeIdentifierButton": "Remove Identifier",
"resetIdentifierTooltip": "Resetear",
"restoreRelationshipActionButton": "Deshacer",
"searchAddress": "Buscar dirección",
diff --git a/packages/esm-patient-registration-app/translations/fr.json b/packages/esm-patient-registration-app/translations/fr.json
index 8b1c3a06d..92c4be5af 100644
--- a/packages/esm-patient-registration-app/translations/fr.json
+++ b/packages/esm-patient-registration-app/translations/fr.json
@@ -14,12 +14,15 @@
"codedPersonAttributeNoAnswerSet": "Le champ d'attribut de personne '{{codedPersonAttributeFieldId}}' est de type 'codé' mais a été défini sans UUID d'ensemble de concepts de réponse. La clé 'answerConceptSetUuid' est requise.",
"configure": "Configurer",
"configureIdentifiers": "Configurer les identifiants",
+ "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?",
"contactSection": "Détails du contact",
"createNewPatient": "Create new patient",
"dateOfBirthLabelText": "Date de Naissance",
"deathDateInputLabel": "Date de Décès",
"deathdayNotInTheFuture": "Le décès ne peut pas être dans le futur",
"deathSection": "Informations sur le décès",
+ "deleteIdentifierModalHeading": "Remove identifier?",
+ "deleteIdentifierModalText": " has a value of ",
"deleteIdentifierTooltip": "Supprimer",
"deleteRelationshipTooltipText": "Supprimer",
"demographicsSection": "Informations de base",
@@ -74,6 +77,7 @@
"relationshipToPatient": "Relation avec le patient",
"relativeFullNameLabelText": "Personne liée",
"relativeNamePlaceholder": "Prénom Nom de famille",
+ "removeIdentifierButton": "Remove Identifier",
"resetIdentifierTooltip": "Réinitialiser",
"restoreRelationshipActionButton": "Restaurer",
"searchAddress": "Rechercher une adresse",
diff --git a/packages/esm-patient-registration-app/translations/he.json b/packages/esm-patient-registration-app/translations/he.json
index 6367ec171..a69739500 100644
--- a/packages/esm-patient-registration-app/translations/he.json
+++ b/packages/esm-patient-registration-app/translations/he.json
@@ -14,12 +14,15 @@
"codedPersonAttributeNoAnswerSet": "The person attribute field '{{codedPersonAttributeFieldId}}' is of type 'coded' but has been defined without an answer concept set UUID. The 'answerConceptSetUuid' key is required.",
"configure": "הגדר",
"configureIdentifiers": "הגדר זיהויים",
+ "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?",
"contactSection": "פרטי יצירת קשר",
"createNewPatient": "Create new patient",
"dateOfBirthLabelText": "תאריך לידה",
"deathDateInputLabel": "תאריך המוות",
"deathdayNotInTheFuture": "תאריך המוות לא יכול להיות בעתיד",
"deathSection": "מידע על המוות",
+ "deleteIdentifierModalHeading": "Remove identifier?",
+ "deleteIdentifierModalText": " has a value of ",
"deleteIdentifierTooltip": "מחק",
"deleteRelationshipTooltipText": "מחק",
"demographicsSection": "מידע בסיסי",
@@ -74,6 +77,7 @@
"relationshipToPatient": "קשר למטופל",
"relativeFullNameLabelText": "שם מלא",
"relativeNamePlaceholder": "שם פרטי שם משפחה",
+ "removeIdentifierButton": "Remove Identifier",
"resetIdentifierTooltip": "איפוס",
"restoreRelationshipActionButton": "ביטול",
"searchAddress": "חיפוש כתובת",
diff --git a/packages/esm-patient-registration-app/translations/km.json b/packages/esm-patient-registration-app/translations/km.json
index bc3faed6c..533b0f00d 100644
--- a/packages/esm-patient-registration-app/translations/km.json
+++ b/packages/esm-patient-registration-app/translations/km.json
@@ -14,12 +14,15 @@
"codedPersonAttributeNoAnswerSet": "The person attribute field '{{codedPersonAttributeFieldId}}' is of type 'coded' but has been defined without an answer concept set UUID. The 'answerConceptSetUuid' key is required.",
"configure": "កំណត់រចនាសម្ព័ន្ធ",
"configureIdentifiers": "ឯកសារកំណត់អត្តសញ្ញាណផ្សេងទៀត",
+ "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?",
"contactSection": "ព័ត៌មានមរណភាព",
"createNewPatient": "Create new patient",
"dateOfBirthLabelText": "ថ្ងៃខែឆ្នាំកំណើត",
"deathDateInputLabel": "កាលបរិច្ឆេទនៃការស្លាប់",
"deathdayNotInTheFuture": "ថ្ងៃស្លាប់មិនអាចមាននាពេលអនាគតទេ។",
"deathSection": "ព័ត៌មានមរណភាព",
+ "deleteIdentifierModalHeading": "Remove identifier?",
+ "deleteIdentifierModalText": " has a value of ",
"deleteIdentifierTooltip": "លុប",
"deleteRelationshipTooltipText": "លុប",
"demographicsSection": "ព័ត៌មានផ្ទាល់ខ្លួនអ្នកជំងឺ",
@@ -74,6 +77,7 @@
"relationshipToPatient": "ការទាក់ទងទៅនឹងអ្នកជំងឺ",
"relativeFullNameLabelText": "ឈ្មោះពេញ",
"relativeNamePlaceholder": "នាមត្រកូលនាមខ្លួន",
+ "removeIdentifierButton": "Remove Identifier",
"resetIdentifierTooltip": "រៀបចំឡើងវិញ",
"restoreRelationshipActionButton": "វិលត្រឡប់មកដើមវិញ",
"searchAddress": "ស្វែងរកអាសយដ្ឋាន",
diff --git a/packages/esm-patient-registration-app/translations/zh.json b/packages/esm-patient-registration-app/translations/zh.json
index 970983115..416154962 100644
--- a/packages/esm-patient-registration-app/translations/zh.json
+++ b/packages/esm-patient-registration-app/translations/zh.json
@@ -14,12 +14,15 @@
"codedPersonAttributeNoAnswerSet": "人员属性字段'{{codedPersonAttributeFieldId}}'的类型为'coded',但未定义答案概念集UUID。'answerConceptSetUuid'键是必需的。",
"configure": "配置",
"configureIdentifiers": "配置ID标识",
+ "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?",
"contactSection": "联系方式",
"createNewPatient": "Create new patient",
"dateOfBirthLabelText": "出生日期",
"deathDateInputLabel": "死亡日期",
"deathdayNotInTheFuture": "死亡日期不能是未来的日期",
"deathSection": "死亡信息",
+ "deleteIdentifierModalHeading": "Remove identifier?",
+ "deleteIdentifierModalText": " has a value of ",
"deleteIdentifierTooltip": "删除",
"deleteRelationshipTooltipText": "删除",
"demographicsSection": "基本信息",
@@ -74,6 +77,7 @@
"relationshipToPatient": "与患者的关系",
"relativeFullNameLabelText": "关系人",
"relativeNamePlaceholder": "名字 姓氏",
+ "removeIdentifierButton": "Remove Identifier",
"resetIdentifierTooltip": "重置",
"restoreRelationshipActionButton": "撤销",
"searchAddress": "搜索地址",
diff --git a/packages/esm-patient-registration-app/translations/zh_CN.json b/packages/esm-patient-registration-app/translations/zh_CN.json
index 970983115..416154962 100644
--- a/packages/esm-patient-registration-app/translations/zh_CN.json
+++ b/packages/esm-patient-registration-app/translations/zh_CN.json
@@ -14,12 +14,15 @@
"codedPersonAttributeNoAnswerSet": "人员属性字段'{{codedPersonAttributeFieldId}}'的类型为'coded',但未定义答案概念集UUID。'answerConceptSetUuid'键是必需的。",
"configure": "配置",
"configureIdentifiers": "配置ID标识",
+ "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?",
"contactSection": "联系方式",
"createNewPatient": "Create new patient",
"dateOfBirthLabelText": "出生日期",
"deathDateInputLabel": "死亡日期",
"deathdayNotInTheFuture": "死亡日期不能是未来的日期",
"deathSection": "死亡信息",
+ "deleteIdentifierModalHeading": "Remove identifier?",
+ "deleteIdentifierModalText": " has a value of ",
"deleteIdentifierTooltip": "删除",
"deleteRelationshipTooltipText": "删除",
"demographicsSection": "基本信息",
@@ -74,6 +77,7 @@
"relationshipToPatient": "与患者的关系",
"relativeFullNameLabelText": "关系人",
"relativeNamePlaceholder": "名字 姓氏",
+ "removeIdentifierButton": "Remove Identifier",
"resetIdentifierTooltip": "重置",
"restoreRelationshipActionButton": "撤销",
"searchAddress": "搜索地址",
diff --git a/packages/esm-service-queues-app/src/add-provider-queue-room/add-provider-queue-room.test.tsx b/packages/esm-service-queues-app/src/add-provider-queue-room/add-provider-queue-room.test.tsx
index 3ec337c0b..52c886bc8 100644
--- a/packages/esm-service-queues-app/src/add-provider-queue-room/add-provider-queue-room.test.tsx
+++ b/packages/esm-service-queues-app/src/add-provider-queue-room/add-provider-queue-room.test.tsx
@@ -82,7 +82,7 @@ describe('AddProviderQueueRoom', () => {
const retainLocationCheckbox: HTMLInputElement = screen.getByRole('checkbox');
await user.click(retainLocationCheckbox);
- expect(retainLocationCheckbox.checked).toBe(true);
+ expect(retainLocationCheckbox).toBeChecked();
});
it('should submit the form and add provider to queue room when all fields are filled', async () => {
diff --git a/packages/esm-service-queues-app/src/current-visit/current-visit-summary.test.tsx b/packages/esm-service-queues-app/src/current-visit/current-visit-summary.test.tsx
index 0a954d50f..19db160ee 100644
--- a/packages/esm-service-queues-app/src/current-visit/current-visit-summary.test.tsx
+++ b/packages/esm-service-queues-app/src/current-visit/current-visit-summary.test.tsx
@@ -24,7 +24,7 @@ describe('CurrentVisit', () => {
it('renders visit details correctly', async () => {
render();
- expect(screen.queryByRole('progressbar')).toBeNull();
+ expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
expect(screen.getByText('Visit Type')).toBeInTheDocument();
expect(screen.getByText('Scheduled for today')).toBeInTheDocument();
expect(screen.getByText('On time')).toBeInTheDocument();
diff --git a/packages/esm-service-queues-app/src/helpers/helpers.test.ts b/packages/esm-service-queues-app/src/helpers/helpers.test.ts
new file mode 100644
index 000000000..1a8a2f244
--- /dev/null
+++ b/packages/esm-service-queues-app/src/helpers/helpers.test.ts
@@ -0,0 +1,24 @@
+import { updateValueInSessionStorage } from './helpers';
+
+describe('Testing updateValueInSessionStorage', () => {
+ beforeEach(() => {
+ sessionStorage.clear();
+ });
+
+ it('should save value in the session storage if valid value is passed', () => {
+ updateValueInSessionStorage('key', 'value');
+ expect(sessionStorage.getItem('key')).toBe('value');
+ });
+
+ it('should delete the key of the value passed is null or undefined', () => {
+ updateValueInSessionStorage('key1', 'v1');
+ expect(sessionStorage.getItem('key1')).toBe('v1');
+ updateValueInSessionStorage('key1', null);
+ expect(sessionStorage.getItem('key1')).toBe(null);
+
+ updateValueInSessionStorage('key2', 'v2');
+ expect(sessionStorage.getItem('key2')).toBe('v2');
+ updateValueInSessionStorage('key2', undefined);
+ expect(sessionStorage.getItem('key2')).toBe(null);
+ });
+});
diff --git a/packages/esm-service-queues-app/src/helpers/helpers.ts b/packages/esm-service-queues-app/src/helpers/helpers.ts
index 7f9b7d554..2b3d329ee 100644
--- a/packages/esm-service-queues-app/src/helpers/helpers.ts
+++ b/packages/esm-service-queues-app/src/helpers/helpers.ts
@@ -14,17 +14,42 @@ export const getServiceCountByAppointmentType = (
.reduce((count, val) => count + val, 0);
};
-const initialQueueLocationNameState = { queueLocationName: sessionStorage.getItem('queueLocationName') };
-const initialQueueLocationUuidState = { queueLocationUuid: sessionStorage.getItem('queueLocationUuid') };
+/**
+ * This function is mainly useful for not writing null/ undefined in the session storage
+ */
+export function updateValueInSessionStorage(key: string, value: string) {
+ if (value === undefined || value === null) {
+ sessionStorage.removeItem(key);
+ } else {
+ sessionStorage.setItem(key, value);
+ }
+}
+
+/**
+ * This function fetches the value for the passed key from session storage
+ */
+export function getValueFromSessionStorage(key: string): string | null {
+ return sessionStorage.getItem(key);
+}
+
+const initialQueueLocationNameState = {
+ queueLocationName: getValueFromSessionStorage('queueLocationName'),
+};
+const initialQueueLocationUuidState = {
+ queueLocationUuid: getValueFromSessionStorage('queueLocationUuid'),
+};
const initialServiceUuidState = {
- serviceUuid: sessionStorage.getItem('queueServiceUuid'),
- serviceDisplay: sessionStorage.getItem('queueServiceDisplay'),
+ serviceUuid: getValueFromSessionStorage('queueServiceUuid'),
+ serviceDisplay: getValueFromSessionStorage('queueServiceDisplay'),
+};
+const intialAppointmentStatusNameState = { status: '' };
+const initialQueueStatusState = {
+ statusUuid: getValueFromSessionStorage('queueStatusUuid'),
+ statusDisplay: getValueFromSessionStorage('queueStatusDisplay'),
};
-const intialStatusNameState = { status: '' };
-const initialQueueStatusState = { statusUuid: null, statusDisplay: null };
const initialSelectedQueueRoomTimestamp = { providerQueueRoomTimestamp: new Date() };
const initialPermanentProviderQueueRoomState = {
- isPermanentProviderQueueRoom: sessionStorage.getItem('isPermanentProviderQueueRoom'),
+ isPermanentProviderQueueRoom: getValueFromSessionStorage('isPermanentProviderQueueRoom'),
};
export function getSelectedService() {
@@ -35,7 +60,7 @@ export function getSelectedService() {
}
export function getSelectedAppointmentStatus() {
- return getGlobalStore<{ status: string }>('appointmentSelectedStatus', intialStatusNameState);
+ return getGlobalStore<{ status: string }>('appointmentSelectedStatus', intialAppointmentStatusNameState);
}
export function getSelectedQueueLocationName() {
@@ -69,8 +94,8 @@ export function getIsPermanentProviderQueueRoom() {
export const updateSelectedService = (currentServiceUuid: string, currentServiceDisplay: string) => {
const store = getSelectedService();
- sessionStorage.setItem('queueServiceDisplay', currentServiceDisplay);
- sessionStorage.setItem('queueServiceUuid', currentServiceUuid);
+ updateValueInSessionStorage('queueServiceDisplay', currentServiceDisplay);
+ updateValueInSessionStorage('queueServiceUuid', currentServiceUuid);
store.setState({ serviceUuid: currentServiceUuid, serviceDisplay: currentServiceDisplay });
};
@@ -81,13 +106,13 @@ export const updateSelectedAppointmentStatus = (currentAppointmentStatus: string
export const updateSelectedQueueLocationName = (currentLocationName: string) => {
const store = getSelectedQueueLocationName();
- sessionStorage.setItem('queueLocationName', currentLocationName);
+ updateValueInSessionStorage('queueLocationName', currentLocationName);
store.setState({ queueLocationName: currentLocationName });
};
export const updateSelectedQueueLocationUuid = (currentLocationUuid: string) => {
const store = getSelectedQueueLocationUuid();
- sessionStorage.setItem('queueLocationUuid', currentLocationUuid);
+ updateValueInSessionStorage('queueLocationUuid', currentLocationUuid);
store.setState({ queueLocationUuid: currentLocationUuid });
};
@@ -98,12 +123,14 @@ export const updatedSelectedQueueRoomTimestamp = (currentProviderRoomTimestamp:
export const updateIsPermanentProviderQueueRoom = (currentIsPermanentProviderQueueRoom) => {
const store = getIsPermanentProviderQueueRoom();
- sessionStorage.setItem('isPermanentProviderQueueRoom', currentIsPermanentProviderQueueRoom);
+ updateValueInSessionStorage('isPermanentProviderQueueRoom', currentIsPermanentProviderQueueRoom);
store.setState({ isPermanentProviderQueueRoom: currentIsPermanentProviderQueueRoom });
};
export const updateSelectedQueueStatus = (currentQueueStatusUuid: string, currentQueueStatusDisplay: string) => {
const store = getSelectedQueueStatus();
+ updateValueInSessionStorage('queueStatusUuid', currentQueueStatusUuid);
+ updateValueInSessionStorage('queueStatusDisplay', currentQueueStatusDisplay);
store.setState({ statusUuid: currentQueueStatusUuid, statusDisplay: currentQueueStatusDisplay });
};
@@ -117,7 +144,7 @@ export const useSelectedService = () => {
};
export const useSelectedAppointmentStatus = () => {
- const [currentAppointmentStatus, setCurrentAppointmentStatus] = useState(intialStatusNameState.status);
+ const [currentAppointmentStatus, setCurrentAppointmentStatus] = useState(intialAppointmentStatusNameState.status);
useEffect(() => {
getSelectedAppointmentStatus().subscribe(({ status }) => setCurrentAppointmentStatus(status));
diff --git a/packages/esm-service-queues-app/src/hooks/useQueueEntries.ts b/packages/esm-service-queues-app/src/hooks/useQueueEntries.ts
index 32ab886d4..688e92ee9 100644
--- a/packages/esm-service-queues-app/src/hooks/useQueueEntries.ts
+++ b/packages/esm-service-queues-app/src/hooks/useQueueEntries.ts
@@ -3,6 +3,7 @@ import { type QueueEntry, type QueueEntrySearchCriteria } from '../types';
import useSWR from 'swr';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSWRConfig } from 'swr/_internal';
+import isEqual from 'lodash-es/isEqual';
type QueueEntryResponse = FetchResponse<{
results: Array;
@@ -81,11 +82,37 @@ export function useQueueEntries(searchCriteria?: QueueEntrySearchCriteria, rep:
const [data, setData] = useState>>([]);
const [totalCount, setTotalCount] = useState();
const [currentPage, setCurrentPage] = useState(0);
- const [pageUrl, setPageUrl] = useState(getInitialUrl(rep, searchCriteria));
+ const [currentSearchCriteria, setCurrentSearchCriteria] = useState(searchCriteria);
+ const [currentRep, setCurrentRep] = useState(rep);
+ const [pageUrl, setPageUrl] = useState(getInitialUrl(currentRep, currentSearchCriteria));
const [error, setError] = useState();
const { mutateQueueEntries } = useMutateQueueEntries();
const [waitingForMutate, setWaitingForMutate] = useState(false);
+ const refetchAllData = useCallback(
+ (newRep: string = currentRep, newSearchCriteria: QueueEntrySearchCriteria = currentSearchCriteria) => {
+ setWaitingForMutate(true);
+ setCurrentPage(0);
+ setPageUrl(getInitialUrl(newRep, newSearchCriteria));
+ },
+ [currentRep, currentSearchCriteria],
+ );
+
+ // This hook listens to the searchCriteria and rep values and refetches the data when they change.
+ useEffect(() => {
+ const isSearchCriteriaUpdated = !isEqual(currentSearchCriteria, searchCriteria);
+ const isRepUpdated = currentRep !== rep;
+ if (isSearchCriteriaUpdated || isRepUpdated) {
+ if (isSearchCriteriaUpdated) {
+ setCurrentSearchCriteria(searchCriteria);
+ }
+ if (isRepUpdated) {
+ setCurrentRep(rep);
+ }
+ refetchAllData(rep, searchCriteria);
+ }
+ }, [searchCriteria, currentSearchCriteria, setCurrentSearchCriteria, currentRep, rep]);
+
const { data: pageData, isValidating, error: pageError } = useSWR(pageUrl, openmrsFetch);
useEffect(() => {
@@ -96,10 +123,10 @@ export function useQueueEntries(searchCriteria?: QueueEntrySearchCriteria, rep:
}
if (pageData && !isValidating && !stillWaitingForMutate) {
// We've got results! Time to update the data array and move on to the next page.
- if (pageData?.data?.totalCount && pageData?.data?.totalCount !== totalCount) {
+ if (pageData?.data?.totalCount > -1 && pageData?.data?.totalCount !== totalCount) {
setTotalCount(pageData?.data?.totalCount);
}
- if (pageData?.data?.results?.length) {
+ if (pageData?.data?.results) {
const newData = [...data];
newData[currentPage] = pageData?.data?.results;
setData(newData);
@@ -137,10 +164,8 @@ export function useQueueEntries(searchCriteria?: QueueEntrySearchCriteria, rep:
}, [pageError]);
const queueUpdateListener = useCallback(() => {
- setWaitingForMutate(true);
- setCurrentPage(0);
- setPageUrl(getInitialUrl(rep, searchCriteria));
- }, [rep, searchCriteria]);
+ refetchAllData();
+ }, [refetchAllData]);
useEffect(() => {
window.addEventListener('queue-entry-updated', queueUpdateListener);
@@ -154,7 +179,7 @@ export function useQueueEntries(searchCriteria?: QueueEntrySearchCriteria, rep:
return {
queueEntries,
totalCount,
- isLoading: !totalCount || (totalCount && queueEntries.length < totalCount),
+ isLoading: totalCount === undefined || (totalCount && queueEntries.length < totalCount),
isValidating: isValidating || currentPage < data.length,
error,
mutate: mutateQueueEntries,
diff --git a/packages/esm-service-queues-app/src/patient-search/advanced-search.test.tsx b/packages/esm-service-queues-app/src/patient-search/advanced-search.test.tsx
index a26389fad..afeec8d95 100644
--- a/packages/esm-service-queues-app/src/patient-search/advanced-search.test.tsx
+++ b/packages/esm-service-queues-app/src/patient-search/advanced-search.test.tsx
@@ -3,13 +3,15 @@ import { render, screen } from '@testing-library/react';
import AdvancedSearch from './advanced-search.component';
describe('AdvancedSearch: ', () => {
- test('renders the advanced patient search in an overlay', () => {
+ test('renders the advanced patient search in an overlay', async () => {
renderAdvancedSearch();
expect(screen.getByRole('button', { name: /back to simple search/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /^search$/i })).toBeInTheDocument();
- expect(screen.findAllByText(/any/i));
+
+ await screen.findAllByText(/any/i);
+
expect(screen.getByRole('tab', { name: /^male$/i })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: /^female$/i })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: /name/i })).toBeInTheDocument();
diff --git a/packages/esm-service-queues-app/src/patient-search/visit-form-queue-fields/visit-form-queue-fields.test.tsx b/packages/esm-service-queues-app/src/patient-search/visit-form-queue-fields/visit-form-queue-fields.test.tsx
index b5c7a72af..4a86b0acc 100644
--- a/packages/esm-service-queues-app/src/patient-search/visit-form-queue-fields/visit-form-queue-fields.test.tsx
+++ b/packages/esm-service-queues-app/src/patient-search/visit-form-queue-fields/visit-form-queue-fields.test.tsx
@@ -1,9 +1,10 @@
+/* eslint-disable testing-library/no-node-access */
import React from 'react';
import userEvent from '@testing-library/user-event';
import { render, screen } from '@testing-library/react';
-import VisitFormQueueFields from './visit-form-queue-fields.component';
import { defineConfigSchema, useLayoutType, useSession } from '@openmrs/esm-framework';
import { configSchema } from '../../config-schema';
+import VisitFormQueueFields from './visit-form-queue-fields.component';
defineConfigSchema('@openmrs/esm-service-queues-app', configSchema);
diff --git a/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.component.tsx b/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.component.tsx
index 2c9ce7857..9e7ef26cf 100644
--- a/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.component.tsx
+++ b/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.component.tsx
@@ -1,23 +1,21 @@
import React, { useState, useMemo, useEffect } from 'react';
import classNames from 'classnames';
-import debounce from 'lodash-es/debounce';
import isEmpty from 'lodash-es/isEmpty';
import { useTranslation } from 'react-i18next';
-import { Layer, Search, RadioButtonGroup, RadioButton, StructuredListSkeleton, Tile } from '@carbon/react';
import {
- ResponsiveWrapper,
- reportError,
- useDebounce,
- useLayoutType,
- usePagination,
- useVisitTypes,
- type VisitType,
-} from '@openmrs/esm-framework';
+ InlineNotification,
+ Layer,
+ RadioButton,
+ RadioButtonGroup,
+ Search,
+ StructuredListSkeleton,
+ Tile,
+} from '@carbon/react';
+import { ResponsiveWrapper, useDebounce, useLayoutType, useVisitTypes, type VisitType } from '@openmrs/esm-framework';
import EmptyDataIllustration from '../empty-data-illustration.component';
import styles from './visit-type-selector.scss';
import { useRecommendedVisitTypes } from '../hooks/useRecommendedVisitTypes';
import { type PatientProgram } from '../../types';
-import { InlineNotification } from '@carbon/react';
export interface VisitTypeSelectorProps {
onChange: (event) => void;
diff --git a/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.test.tsx b/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.test.tsx
index e2ee35326..e4117ca22 100644
--- a/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.test.tsx
+++ b/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.test.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable testing-library/no-node-access */
import React from 'react';
import userEvent from '@testing-library/user-event';
import { VisitTypeSelector } from './visit-type-selector.component';
@@ -28,7 +29,7 @@ describe('VisitTypeSelector', () => {
it('renders the first 5 visit types with a search bar if there are more than 5', () => {
render( {}} />);
- expect(screen.queryByRole('searchbox')).toBeInTheDocument();
+ expect(screen.getByRole('searchbox')).toBeInTheDocument();
mockVisitTypes.slice(0, 5).forEach((visitType) => {
const radioButton = screen.getByLabelText(visitType.display);
diff --git a/packages/esm-service-queues-app/src/queue-patient-linelists/queue-linelist-base-table.test.tsx b/packages/esm-service-queues-app/src/queue-patient-linelists/queue-linelist-base-table.test.tsx
index 5e6c033d5..153d26397 100644
--- a/packages/esm-service-queues-app/src/queue-patient-linelists/queue-linelist-base-table.test.tsx
+++ b/packages/esm-service-queues-app/src/queue-patient-linelists/queue-linelist-base-table.test.tsx
@@ -52,7 +52,7 @@ describe('QueuePatientBaseTable: ', () => {
renderQueueBaseTable();
- expect(screen.queryByText(/scheduled appointments/i)).toBeInTheDocument();
+ expect(screen.getByText(/scheduled appointments/i)).toBeInTheDocument();
const expectedColumnHeaders = [/name/, /return date/, /gender/, /age/, /visit type/, /phone number/];
expectedColumnHeaders.forEach((header) => {
expect(screen.getByRole('columnheader', { name: new RegExp(header, 'i') })).toBeInTheDocument();
@@ -70,7 +70,7 @@ describe('QueuePatientBaseTable: ', () => {
const searchBox = screen.getByRole('searchbox');
await user.type(searchBox, 'John');
- expect(screen.queryByText(/john wilson/i)).toBeInTheDocument();
+ expect(screen.getByText(/john wilson/i)).toBeInTheDocument();
expect(screen.queryByText(/eric test ric/i)).not.toBeInTheDocument();
await user.clear(searchBox);
diff --git a/packages/esm-service-queues-app/src/queue-table/default-queue-table.component.tsx b/packages/esm-service-queues-app/src/queue-table/default-queue-table.component.tsx
index 574a58510..144ad74bf 100644
--- a/packages/esm-service-queues-app/src/queue-table/default-queue-table.component.tsx
+++ b/packages/esm-service-queues-app/src/queue-table/default-queue-table.component.tsx
@@ -37,12 +37,16 @@ function DefaultQueueTable() {
const selectedService = useSelectedService();
const currentLocationUuid = useSelectedQueueLocationUuid();
const selectedQueueStatus = useSelectedQueueStatus();
- const { queueEntries, isLoading, error, isValidating } = useQueueEntries({
- service: selectedService?.serviceUuid,
- location: currentLocationUuid,
- isEnded: false,
- status: selectedQueueStatus?.statusUuid,
- });
+ const searchCriteria = useMemo(
+ () => ({
+ service: selectedService?.serviceUuid,
+ location: currentLocationUuid,
+ isEnded: false,
+ status: selectedQueueStatus?.statusUuid,
+ }),
+ [selectedService?.serviceUuid, currentLocationUuid, selectedQueueStatus?.statusUuid],
+ );
+ const { queueEntries, isLoading, error, isValidating } = useQueueEntries(searchCriteria);
const { t } = useTranslation();
diff --git a/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-actions.test.tsx b/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-actions.test.tsx
index 2b59e6b64..e5e596c79 100644
--- a/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-actions.test.tsx
+++ b/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-actions.test.tsx
@@ -76,7 +76,7 @@ describe('TransitionQueueEntryModal: ', () => {
await inServiceRadioButton.click();
const submitButton = screen.getByRole('button', { name: /Transition patient/ });
- expect(submitButton).not.toBeDisabled();
+ expect(submitButton).toBeEnabled();
await submitButton.click();
expect(mockedOpenmrsFetch).toHaveBeenCalled();
@@ -143,7 +143,7 @@ describe('EditQueueEntryModal: ', () => {
await inServiceRadioButton.click();
const submitButton = screen.getByRole('button', { name: /Edit queue entry/ });
- expect(submitButton).not.toBeDisabled();
+ expect(submitButton).toBeEnabled();
await submitButton.click();
expect(mockedOpenmrsFetch).toHaveBeenCalled();
diff --git a/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-confirm-action.test.tsx b/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-confirm-action.test.tsx
index 4e41848f7..ac4187294 100644
--- a/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-confirm-action.test.tsx
+++ b/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-confirm-action.test.tsx
@@ -39,7 +39,7 @@ describe('UndoTransitionQueueEntryModal: ', () => {
renderWithSwr( {}} />);
const submitButton = screen.getByRole('button', { name: /Undo transition/ });
- expect(submitButton).not.toBeDisabled();
+ expect(submitButton).toBeEnabled();
await user.click(submitButton);
expect(mockedOpenmrsFetch).toHaveBeenCalled();
@@ -68,7 +68,7 @@ describe('VoidQueueEntryModal: ', () => {
renderWithSwr( {}} />);
const submitButton = screen.getByRole('button', { name: /Delete queue entry/ });
- expect(submitButton).not.toBeDisabled();
+ expect(submitButton).toBeEnabled();
await user.click(submitButton);
expect(mockedOpenmrsFetch).toHaveBeenCalled();
@@ -97,7 +97,7 @@ describe('EndQueueEntryModal: ', () => {
renderWithSwr( {}} />);
const submitButton = screen.getByRole('button', { name: /Remove patient/ });
- expect(submitButton).not.toBeDisabled();
+ expect(submitButton).toBeEnabled();
await user.click(submitButton);
expect(mockedOpenmrsFetch).toHaveBeenCalled();
diff --git a/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-undo-actions.test.tsx b/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-undo-actions.test.tsx
index 429746ebd..630cb2225 100644
--- a/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-undo-actions.test.tsx
+++ b/packages/esm-service-queues-app/src/queue-table/queue-entry-actions/queue-entry-undo-actions.test.tsx
@@ -38,7 +38,7 @@ describe('UndoTransitionQueueEntryModal: ', () => {
renderWithSwr( {}} />);
const submitButton = screen.getByRole('button', { name: /Undo transition/ });
- expect(submitButton).not.toBeDisabled();
+ expect(submitButton).toBeEnabled();
await user.click(submitButton);
expect(mockedOpenmrsFetch).toHaveBeenCalled();
@@ -67,7 +67,7 @@ describe('VoidQueueEntryModal: ', () => {
renderWithSwr( {}} />);
const submitButton = screen.getByRole('button', { name: /Delete queue entry/ });
- expect(submitButton).not.toBeDisabled();
+ expect(submitButton).toBeEnabled();
await user.click(submitButton);
expect(mockedOpenmrsFetch).toHaveBeenCalled();
diff --git a/packages/esm-service-queues-app/src/queue-table/queue-table.test.tsx b/packages/esm-service-queues-app/src/queue-table/queue-table.test.tsx
index 7dfeec3db..702f1f1f2 100644
--- a/packages/esm-service-queues-app/src/queue-table/queue-table.test.tsx
+++ b/packages/esm-service-queues-app/src/queue-table/queue-table.test.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable testing-library/no-node-access */
import React from 'react';
import { defineConfigSchema, getDefaultsFromConfigSchema, useConfig, useSession } from '@openmrs/esm-framework';
import { screen, within } from '@testing-library/react';
diff --git a/packages/esm-service-queues-app/src/views/queue-tables-for-all-statuses.component.tsx b/packages/esm-service-queues-app/src/views/queue-tables-for-all-statuses.component.tsx
index 7338bd223..5d9584b6a 100644
--- a/packages/esm-service-queues-app/src/views/queue-tables-for-all-statuses.component.tsx
+++ b/packages/esm-service-queues-app/src/views/queue-tables-for-all-statuses.component.tsx
@@ -1,18 +1,16 @@
import React, { useCallback, useState } from 'react';
-import { InlineNotification, Search } from '@carbon/react';
+import { InlineNotification, Search, SkeletonText } from '@carbon/react';
import { Add } from '@carbon/react/icons';
-import { ExtensionSlot, isDesktop, launchWorkspace, showToast, useLayoutType } from '@openmrs/esm-framework';
import { useTranslation } from 'react-i18next';
+import { ExtensionSlot, isDesktop, launchWorkspace, showToast, useLayoutType } from '@openmrs/esm-framework';
+import type { Concept, Queue, QueueEntry } from '../types';
import { useQueueEntries } from '../hooks/useQueueEntries';
import { useColumns } from '../queue-table/cells/columns.resource';
import { QueueTableByStatusSkeleton } from '../queue-table/queue-table-by-status-skeleton.component';
import QueueTable from '../queue-table/queue-table.component';
import QueueTableMetrics from '../queue-table/queue-table-metrics.component';
-import styles from '../queue-table/queue-table.scss';
-import type { Concept, Queue, QueueEntry } from '../types';
import PatientQueueHeader from '../patient-queue-header/patient-queue-header.component';
-import { SearchSkeleton } from '@carbon/react';
-import { SkeletonText } from '@carbon/react';
+import styles from '../queue-table/queue-table.scss';
interface QueueTablesForAllStatusesProps {
selectedQueue: Queue; // the selected queue
diff --git a/packages/esm-service-queues-app/translations/am.json b/packages/esm-service-queues-app/translations/am.json
index d93f459c3..04aaa1aa1 100644
--- a/packages/esm-service-queues-app/translations/am.json
+++ b/packages/esm-service-queues-app/translations/am.json
@@ -83,6 +83,8 @@
"femaleLabelText": "Female",
"fields": "of the following fields",
"filter": "Filter (1)",
+ "filterByService": "Filter by service :",
+ "filterByStatus": "Filter by status :",
"filterTable": "Filter table",
"firstName": "First name",
"firstNameSort": "First name (a-z)",
@@ -257,7 +259,6 @@
"serviceQueue": "Service queue",
"serviceQueues": "Service queues",
"sex": "Sex",
- "showPatientsWaitingFor": "Show patients waiting for",
"sortBy": "Sort by",
"sortWeight": "Sort weight",
"sp02": "Sp02",
@@ -268,7 +269,7 @@
"startVisitQueueSuccessfully": "Patient has been added to active visits list and queue.",
"status": "Status",
"statusIsRequired": "Status is required",
- "submitting": "Submitting",
+ "submitting": "Submitting...",
"success": "Success",
"temperature": "Temperature",
"ticketNumber": "Ticket number",
diff --git a/packages/esm-service-queues-app/translations/ar.json b/packages/esm-service-queues-app/translations/ar.json
index 0245d7456..4bb22f0c5 100644
--- a/packages/esm-service-queues-app/translations/ar.json
+++ b/packages/esm-service-queues-app/translations/ar.json
@@ -83,6 +83,8 @@
"femaleLabelText": "أنثى",
"fields": "من الحقول التالية",
"filter": "تصفية (1)",
+ "filterByService": "Filter by service :",
+ "filterByStatus": "Filter by status :",
"filterTable": "جدول التصفية",
"firstName": "الاسم الأول",
"firstNameSort": "الاسم الأول (أ-ي)",
@@ -257,7 +259,6 @@
"serviceQueue": "طابور الخدمة",
"serviceQueues": "طوابير الخدمة",
"sex": "الجنس",
- "showPatientsWaitingFor": "عرض المرضى في الانتظار لـ",
"sortBy": "ترتيب حسب",
"sortWeight": "ترتيب حسب الوزن",
"sp02": "Sp02",
@@ -268,7 +269,7 @@
"startVisitQueueSuccessfully": "تمت إضافة المريض إلى قائمة الزيارات النشطة والطابور.",
"status": "الحالة",
"statusIsRequired": "Status is required",
- "submitting": "Submitting",
+ "submitting": "Submitting...",
"success": "نجاح",
"temperature": "درجة الحرارة",
"ticketNumber": "رقم التذكرة",
diff --git a/packages/esm-service-queues-app/translations/es.json b/packages/esm-service-queues-app/translations/es.json
index d93f459c3..04aaa1aa1 100644
--- a/packages/esm-service-queues-app/translations/es.json
+++ b/packages/esm-service-queues-app/translations/es.json
@@ -83,6 +83,8 @@
"femaleLabelText": "Female",
"fields": "of the following fields",
"filter": "Filter (1)",
+ "filterByService": "Filter by service :",
+ "filterByStatus": "Filter by status :",
"filterTable": "Filter table",
"firstName": "First name",
"firstNameSort": "First name (a-z)",
@@ -257,7 +259,6 @@
"serviceQueue": "Service queue",
"serviceQueues": "Service queues",
"sex": "Sex",
- "showPatientsWaitingFor": "Show patients waiting for",
"sortBy": "Sort by",
"sortWeight": "Sort weight",
"sp02": "Sp02",
@@ -268,7 +269,7 @@
"startVisitQueueSuccessfully": "Patient has been added to active visits list and queue.",
"status": "Status",
"statusIsRequired": "Status is required",
- "submitting": "Submitting",
+ "submitting": "Submitting...",
"success": "Success",
"temperature": "Temperature",
"ticketNumber": "Ticket number",
diff --git a/packages/esm-service-queues-app/translations/fr.json b/packages/esm-service-queues-app/translations/fr.json
index d93f459c3..04aaa1aa1 100644
--- a/packages/esm-service-queues-app/translations/fr.json
+++ b/packages/esm-service-queues-app/translations/fr.json
@@ -83,6 +83,8 @@
"femaleLabelText": "Female",
"fields": "of the following fields",
"filter": "Filter (1)",
+ "filterByService": "Filter by service :",
+ "filterByStatus": "Filter by status :",
"filterTable": "Filter table",
"firstName": "First name",
"firstNameSort": "First name (a-z)",
@@ -257,7 +259,6 @@
"serviceQueue": "Service queue",
"serviceQueues": "Service queues",
"sex": "Sex",
- "showPatientsWaitingFor": "Show patients waiting for",
"sortBy": "Sort by",
"sortWeight": "Sort weight",
"sp02": "Sp02",
@@ -268,7 +269,7 @@
"startVisitQueueSuccessfully": "Patient has been added to active visits list and queue.",
"status": "Status",
"statusIsRequired": "Status is required",
- "submitting": "Submitting",
+ "submitting": "Submitting...",
"success": "Success",
"temperature": "Temperature",
"ticketNumber": "Ticket number",
diff --git a/packages/esm-service-queues-app/translations/he.json b/packages/esm-service-queues-app/translations/he.json
index 4543f063e..a95042b6b 100644
--- a/packages/esm-service-queues-app/translations/he.json
+++ b/packages/esm-service-queues-app/translations/he.json
@@ -83,6 +83,8 @@
"femaleLabelText": "נקבה",
"fields": "מהשדות הבאים",
"filter": "סנן",
+ "filterByService": "Filter by service :",
+ "filterByStatus": "Filter by status :",
"filterTable": "סנן טבלה",
"firstName": "שם פרטי",
"firstNameSort": "שם פרטי (א-ת)",
@@ -257,7 +259,6 @@
"serviceQueue": "תור השירות",
"serviceQueues": "תורי השירות",
"sex": "מגדר",
- "showPatientsWaitingFor": "הצג מטופלים הממתינים ל",
"sortBy": "מיין לפי",
"sortWeight": "מיון לפי חוזק",
"sp02": "רמת חמצן",
@@ -268,7 +269,7 @@
"startVisitQueueSuccessfully": "המטופל הוסף לרשימת הביקורים הפעילים ולתור בהצלחה",
"status": "מצב",
"statusIsRequired": "Status is required",
- "submitting": "Submitting",
+ "submitting": "Submitting...",
"success": "הצלחה",
"temperature": "טמפרטורה",
"ticketNumber": "מספר כרטיס",
diff --git a/packages/esm-service-queues-app/translations/km.json b/packages/esm-service-queues-app/translations/km.json
index 355743c32..1cf291c1f 100644
--- a/packages/esm-service-queues-app/translations/km.json
+++ b/packages/esm-service-queues-app/translations/km.json
@@ -83,6 +83,8 @@
"femaleLabelText": "ស្រី",
"fields": "នៃផ្នែកខាងក្រោម",
"filter": "តម្រង",
+ "filterByService": "Filter by service :",
+ "filterByStatus": "Filter by status :",
"filterTable": "Filter table",
"firstName": "នាមខ្លួន",
"firstNameSort": "នាមខ្លួន (ពីa ដល់ z ) ",
@@ -257,7 +259,6 @@
"serviceQueue": "ជួរសេវាកម្ម",
"serviceQueues": "ជួរនៃសេវា",
"sex": "ភេទ",
- "showPatientsWaitingFor": "បង្ហាញអ្នកជំងឺដែលកំពុងរង់ចាំ",
"sortBy": "តម្រៀបតាម",
"sortWeight": "Sort weight",
"sp02": "រកមើល បរិមាណកំហាប់អុកស៊ីសែន",
@@ -268,7 +269,7 @@
"startVisitQueueSuccessfully": "អ្នកជំងឺត្រូវបានបន្ថែមទៅបញ្ជីមកពិនិត្យជំងឺសកម្ម និងតាមជួរ",
"status": "ស្ថានភាព",
"statusIsRequired": "Status is required",
- "submitting": "Submitting",
+ "submitting": "Submitting...",
"success": "Success",
"temperature": "សីតុណ្ហភាព",
"ticketNumber": "Ticket number",
diff --git a/packages/esm-service-queues-app/translations/zh.json b/packages/esm-service-queues-app/translations/zh.json
index 8e2d289a3..0edce6b47 100644
--- a/packages/esm-service-queues-app/translations/zh.json
+++ b/packages/esm-service-queues-app/translations/zh.json
@@ -83,6 +83,8 @@
"femaleLabelText": "女性",
"fields": "of the following fields",
"filter": "筛选",
+ "filterByService": "Filter by service :",
+ "filterByStatus": "Filter by status :",
"filterTable": "筛选表格",
"firstName": "名字",
"firstNameSort": "名字(a-z)",
@@ -257,7 +259,6 @@
"serviceQueue": "服务队列",
"serviceQueues": "服务队列",
"sex": "性别",
- "showPatientsWaitingFor": "显示等待的患者",
"sortBy": "排序方式",
"sortWeight": "排序权重",
"sp02": "血氧",
@@ -268,7 +269,7 @@
"startVisitQueueSuccessfully": "患者已添加到活动就诊列表和队列中。",
"status": "状态",
"statusIsRequired": "Status is required",
- "submitting": "Submitting",
+ "submitting": "Submitting...",
"success": "成功",
"temperature": "体温",
"ticketNumber": "序号",
diff --git a/packages/esm-service-queues-app/translations/zh_CN.json b/packages/esm-service-queues-app/translations/zh_CN.json
index 1a7337596..f73658ac6 100644
--- a/packages/esm-service-queues-app/translations/zh_CN.json
+++ b/packages/esm-service-queues-app/translations/zh_CN.json
@@ -83,6 +83,8 @@
"femaleLabelText": "女性",
"fields": "of the following fields",
"filter": "筛选",
+ "filterByService": "Filter by service :",
+ "filterByStatus": "Filter by status :",
"filterTable": "筛选表格",
"firstName": "名字",
"firstNameSort": "名字(a-z)",
@@ -257,7 +259,6 @@
"serviceQueue": "服务队列",
"serviceQueues": "服务队列",
"sex": "性别",
- "showPatientsWaitingFor": "显示等待的患者",
"sortBy": "排序方式",
"sortWeight": "排序权重",
"sp02": "血氧",
@@ -268,7 +269,7 @@
"startVisitQueueSuccessfully": "患者已添加到活动就诊列表和队列中。",
"status": "状态",
"statusIsRequired": "Status is required",
- "submitting": "Submitting",
+ "submitting": "Submitting...",
"success": "成功",
"temperature": "体温",
"ticketNumber": "序号",
diff --git a/packages/esm-ward-app/src/hooks/useAdmissionLocation.ts b/packages/esm-ward-app/src/hooks/useAdmissionLocation.ts
index 5b46d448f..f41e17d54 100644
--- a/packages/esm-ward-app/src/hooks/useAdmissionLocation.ts
+++ b/packages/esm-ward-app/src/hooks/useAdmissionLocation.ts
@@ -1,13 +1,12 @@
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
-import useSWR from 'swr';
import { type AdmissionLocation } from '../types/index';
+import useSWRImmutable from 'swr/immutable';
export function useAdmissionLocation(locationUuid: string, rep: string = 'full') {
- const apiUrl = `${restBaseUrl}/admissionLocation/${locationUuid}` + (rep ? `?v=${rep}` : '');
- const { data, ...rest } = useSWR<{ data: AdmissionLocation }, Error>(apiUrl, openmrsFetch);
-
+ const apiUrl = locationUuid ? `${restBaseUrl}/admissionLocation/${locationUuid}?v=${rep}` : null;
+ const { data, ...rest } = useSWRImmutable<{ data: AdmissionLocation }, Error>(apiUrl, openmrsFetch);
return {
- admissionLocation: data?.data ?? null,
+ admissionLocation: data?.data,
...rest,
};
}
diff --git a/packages/esm-ward-app/src/hooks/useInpatientRequest.ts b/packages/esm-ward-app/src/hooks/useInpatientRequest.ts
index 7cfde3b8a..589f0fcc7 100644
--- a/packages/esm-ward-app/src/hooks/useInpatientRequest.ts
+++ b/packages/esm-ward-app/src/hooks/useInpatientRequest.ts
@@ -1,13 +1,15 @@
-import { openmrsFetch } from '@openmrs/esm-framework';
-import useSWR from 'swr';
+import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import type { InpatientRequest } from '../types';
+import useSWR from 'swr';
export function useInpatientRequest(locationUuid: string) {
- const apiUrl = `/ws/rest/emrapi/inpatient/admissionRequests?admissionLocation=${locationUuid}`;
- const { data, ...rest } = useSWR<{ data: Array }, Error>(apiUrl, openmrsFetch);
+ const { data, ...rest } = useSWR>, Error>(
+ locationUuid ? `${restBaseUrl}/emrapi/inpatient/admissionRequests?admissionLocation=${locationUuid}` : null,
+ openmrsFetch,
+ );
return {
- inpatientRequests: data?.data || null,
+ inpatientRequests: data?.data,
...rest,
};
}
diff --git a/packages/esm-ward-app/src/hooks/useLocation.test.ts b/packages/esm-ward-app/src/hooks/useLocation.test.ts
new file mode 100644
index 000000000..a4847d19d
--- /dev/null
+++ b/packages/esm-ward-app/src/hooks/useLocation.test.ts
@@ -0,0 +1,38 @@
+import { renderHook } from '@testing-library/react';
+import useLocation from './useLocation';
+import useSWRImmutable from 'swr/immutable';
+import { restBaseUrl } from '@openmrs/esm-framework';
+
+jest.mock('swr/immutable', () =>
+ jest.fn().mockReturnValue({
+ data: {},
+ error: null,
+ isValidating: false,
+ mutate: jest.fn(),
+ }),
+);
+
+const useSWRImmutableMock = useSWRImmutable as jest.Mock;
+
+describe('useLocation hook', () => {
+ it('should call useLocation', () => {
+ const { result } = renderHook(() => useLocation('testUUID'));
+ expect(useSWRImmutableMock).toHaveBeenCalledWith(
+ `${restBaseUrl}/location/testUUID?v=custom:(display,uuid)`,
+ expect.any(Function),
+ );
+ });
+
+ it('should call useLocation with given rep', () => {
+ const { result } = renderHook(() => useLocation('testUUID', 'custom:(display,uuid,links)'));
+ expect(useSWRImmutableMock).toHaveBeenCalledWith(
+ `${restBaseUrl}/location/testUUID?v=custom:(display,uuid,links)`,
+ expect.any(Function),
+ );
+ });
+
+ it('should call useSWR with key=null', () => {
+ const { result } = renderHook(() => useLocation(null, 'custom:(display,uuid,links)'));
+ expect(useSWRImmutableMock).toHaveBeenCalledWith(null, expect.any(Function));
+ });
+});
diff --git a/packages/esm-ward-app/src/hooks/useLocation.ts b/packages/esm-ward-app/src/hooks/useLocation.ts
new file mode 100644
index 000000000..460c169e3
--- /dev/null
+++ b/packages/esm-ward-app/src/hooks/useLocation.ts
@@ -0,0 +1,9 @@
+import { type Location, openmrsFetch, restBaseUrl, type FetchResponse } from '@openmrs/esm-framework';
+import useSWRImmutable from 'swr/immutable';
+
+export default function useLocation(locationUuid: string, rep: string = 'custom:(display,uuid)') {
+ return useSWRImmutable>(
+ locationUuid ? `${restBaseUrl}/location/${locationUuid}?v=${rep}` : null,
+ openmrsFetch,
+ );
+}
diff --git a/packages/esm-ward-app/src/hooks/useWardLocation.test.ts b/packages/esm-ward-app/src/hooks/useWardLocation.test.ts
new file mode 100644
index 000000000..6eb9c1158
--- /dev/null
+++ b/packages/esm-ward-app/src/hooks/useWardLocation.test.ts
@@ -0,0 +1,78 @@
+import { renderHook } from '@testing-library/react';
+import { useSession } from '@openmrs/esm-framework';
+import { useParams } from 'react-router-dom';
+import useWardLocation from './useWardLocation';
+import useLocation from './useLocation';
+
+jest.mock('@openmrs/esm-framework', () => ({
+ useSession: jest.fn(),
+}));
+jest.mock('react-router-dom', () => ({
+ useParams: jest.fn(),
+}));
+jest.mock('./useLocation', () => jest.fn());
+
+const mockedUseParams = useParams as jest.Mock;
+const mockedUseSession = useSession as jest.Mock;
+const mockedUseLocation = useLocation as jest.Mock;
+
+describe('useWardLocation', () => {
+ it('returns session location when locationUuidFromUrl is not provided', async () => {
+ mockedUseParams.mockReturnValue({});
+ mockedUseSession.mockReturnValue({ sessionLocation: 'sessionLocation' });
+ mockedUseLocation.mockReturnValue({});
+
+ const { result } = renderHook(() => useWardLocation());
+
+ expect(result.current.location).toBe('sessionLocation');
+ });
+
+ it('returns location from useLocation when locationUuidFromUrl is provided', async () => {
+ mockedUseParams.mockReturnValue({ locationUuid: 'uuid' });
+ mockedUseLocation.mockReturnValue({
+ data: { data: 'locationData' },
+ isLoading: false,
+ error: null,
+ });
+
+ const { result } = renderHook(() => useWardLocation());
+
+ expect(result.current.location).toBe('locationData');
+ expect(result.current.invalidLocation).toBeFalsy();
+ });
+
+ it('handles loading state correctly', async () => {
+ mockedUseParams.mockReturnValue({ locationUuid: 'uuid' });
+ mockedUseLocation.mockReturnValue({
+ isLoading: true,
+ });
+
+ const { result } = renderHook(() => useWardLocation());
+
+ expect(result.current.isLoadingLocation).toBe(true);
+ });
+
+ it('handles error state correctly when fetching location fails', async () => {
+ const error = new Error('Error fetching location');
+ mockedUseParams.mockReturnValue({ locationUuid: 'uuid' });
+ mockedUseLocation.mockReturnValue({
+ error,
+ });
+
+ const { result } = renderHook(() => useWardLocation());
+
+ expect(result.current.errorFetchingLocation).toBe(error);
+ });
+
+ it('identifies invalid location correctly', async () => {
+ const error = new Error('Error fetching location');
+ mockedUseParams.mockReturnValue({ locationUuid: 'uuid' });
+ mockedUseLocation.mockReturnValue({
+ error,
+ });
+
+ const { result } = renderHook(() => useWardLocation());
+
+ expect(result.current.invalidLocation).toBeTruthy();
+ });
+});
diff --git a/packages/esm-ward-app/src/hooks/useWardLocation.ts b/packages/esm-ward-app/src/hooks/useWardLocation.ts
new file mode 100644
index 000000000..bd7e6fc96
--- /dev/null
+++ b/packages/esm-ward-app/src/hooks/useWardLocation.ts
@@ -0,0 +1,26 @@
+import { type Location, useLocations, useSession } from '@openmrs/esm-framework';
+import { useParams } from 'react-router-dom';
+import useLocation from './useLocation';
+
+export default function useWardLocation(): {
+ location: Location;
+ isLoadingLocation: boolean;
+ errorFetchingLocation: Error;
+ invalidLocation: boolean;
+} {
+ const { locationUuid: locationUuidFromUrl } = useParams();
+ const { sessionLocation } = useSession();
+ const {
+ data: locationResponse,
+ isLoading: isLoadingLocation,
+ error: errorFetchingLocation,
+ } = useLocation(locationUuidFromUrl ? locationUuidFromUrl : null);
+ const invalidLocation = locationUuidFromUrl && errorFetchingLocation;
+
+ return {
+ location: locationUuidFromUrl ? locationResponse?.data : sessionLocation,
+ isLoadingLocation,
+ errorFetchingLocation,
+ invalidLocation,
+ };
+}
diff --git a/packages/esm-ward-app/src/index.ts b/packages/esm-ward-app/src/index.ts
index ec0f7a0d8..932466d99 100644
--- a/packages/esm-ward-app/src/index.ts
+++ b/packages/esm-ward-app/src/index.ts
@@ -1,8 +1,14 @@
-import { defineConfigSchema, getSyncLifecycle, registerBreadcrumbs, registerFeatureFlag } from '@openmrs/esm-framework';
+import {
+ defineConfigSchema,
+ getAsyncLifecycle,
+ getSyncLifecycle,
+ registerBreadcrumbs,
+ registerFeatureFlag,
+} from '@openmrs/esm-framework';
import { configSchema } from './config-schema';
import rootComponent from './root.component';
import { moduleName } from './constant';
-import admissionRequestsWorkspace from "./ward-workspace/admission-requests-workspace.component"
+import WardPatientActionButton from './ward-patient-workspace/ward-patient-action-button.extension';
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
@@ -13,7 +19,15 @@ const options = {
export const root = getSyncLifecycle(rootComponent, options);
-export const admissionRequestWorkspace = getSyncLifecycle(admissionRequestsWorkspace, options);
+export const admissionRequestWorkspace = getAsyncLifecycle(
+ () => import('./ward-workspace/admission-requests-workspace.component'),
+ options,
+);
+
+export const wardPatientWorkspace = getAsyncLifecycle(() => import('./ward-patient-workspace/ward-patient.workspace'), options);
+
+export const wardPatientActionButtonExtension = getSyncLifecycle(WardPatientActionButton, options);
+
export function startupApp() {
registerBreadcrumbs([]);
defineConfigSchema(moduleName, configSchema);
diff --git a/packages/esm-ward-app/src/routes.json b/packages/esm-ward-app/src/routes.json
index 55e7dbd9c..2e9976cc8 100644
--- a/packages/esm-ward-app/src/routes.json
+++ b/packages/esm-ward-app/src/routes.json
@@ -14,18 +14,31 @@
}
}
},
- "workspaces": [
- {
- "name":"admission-requests-cards",
- "component": "admissionRequestWorkspace",
- "title":"admissionRequests",
- "type":"admission-requests"
- }
- ],
"pages": [
{
"component": "root",
"route": "ward"
}
- ]
+ ],
+ "extensions": [{
+ "component": "wardPatientActionButtonExtension",
+ "name": "ward-patient-action-button",
+ "slot": "action-menu-ward-patient-items-slot"
+ }],
+ "workspaces": [
+ {
+ "name":"admission-requests-workspace",
+ "component": "admissionRequestWorkspace",
+ "title":"admissionRequests",
+ "type":"admission-requests"
+ },
+ {
+ "name": "ward-patient-workspace",
+ "component": "wardPatientWorkspace",
+ "type": "ward",
+ "title": "Ward Patient",
+ "width": "extra-wide",
+ "hasOwnSidebar": true,
+ "sidebarFamily": "ward-patient"
+ }]
}
diff --git a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx
index 601816f33..58ed3c612 100644
--- a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx
+++ b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx
@@ -45,7 +45,7 @@ const wardPatientCodedObsTags = (config: PatientCodedObsTagsElementConfig) => {
const color = conceptToTagColorMap?.get(uuid);
if (color) {
return (
-
+
{display}
);
diff --git a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.scss b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.scss
index 5db8acdfa..ef931affb 100644
--- a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.scss
+++ b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.scss
@@ -13,11 +13,37 @@
gap: spacing.$spacing-02;
background-color: $ui-02;
+ position: relative; // this allows positioning the button correctly
+
> .wardPatientCardRow:not(:first-child) {
border-top: 1px colors.$gray-20 solid;
}
}
+.wardPatientCardButton {
+ border: none;
+ padding: 0;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ z-index: 1;
+ cursor: pointer;
+ border: 2px solid transparent;
+ transition: border-color 200ms;
+ }
+
+ &:hover::before,
+ &:focus::before {
+ border-color: $interactive-01;
+ }
+
+ &:focus {
+ outline: none;
+ }
+}
+
.wardPatientCardRow {
width: 100%;
padding: spacing.$spacing-04;
diff --git a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.tsx b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.tsx
index 0ec4e85be..2ca4e2cb5 100644
--- a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.tsx
+++ b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.tsx
@@ -3,6 +3,8 @@ import { useParams } from 'react-router-dom';
import { type WardPatientCardProps } from '../types';
import { usePatientCardRows } from './ward-patient-card-row.resources';
import styles from './ward-patient-card.scss';
+import { getPatientName, launchWorkspace } from '@openmrs/esm-framework';
+import { type WardPatientWorkspaceProps } from '../ward-patient-workspace/ward-patient.workspace';
const WardPatientCard: React.FC = (props) => {
const { locationUuid } = useParams();
@@ -13,6 +15,14 @@ const WardPatientCard: React.FC = (props) => {
{patientCardRows.map((WardPatientCardRow, i) => (
))}
+
);
};
diff --git a/packages/esm-ward-app/src/ward-patient-workspace/ward-patient-action-button.extension.tsx b/packages/esm-ward-app/src/ward-patient-workspace/ward-patient-action-button.extension.tsx
new file mode 100644
index 000000000..060c0bb57
--- /dev/null
+++ b/packages/esm-ward-app/src/ward-patient-workspace/ward-patient-action-button.extension.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { UserAvatarIcon } from '@openmrs/esm-framework';
+import { ActionMenuButton, launchWorkspace } from '@openmrs/esm-framework';
+
+export default function WardPatientActionButton() {
+ const { t } = useTranslation();
+
+ return (
+ }
+ label={t('Patient', 'patient')}
+ iconDescription={t('Patient', 'patient')}
+ handler={() => launchWorkspace('ward-patient-workspace')}
+ type={'ward'}
+ />
+ );
+}
diff --git a/packages/esm-ward-app/src/ward-patient-workspace/ward-patient.style.scss b/packages/esm-ward-app/src/ward-patient-workspace/ward-patient.style.scss
new file mode 100644
index 000000000..cff5179a4
--- /dev/null
+++ b/packages/esm-ward-app/src/ward-patient-workspace/ward-patient.style.scss
@@ -0,0 +1,11 @@
+@use '@carbon/styles/scss/spacing';
+@use '@carbon/styles/scss/type';
+
+.workspaceContainer {
+ min-height: var(--desktop-workspace-window-height);
+}
+
+.headerPatientDetail {
+ @include type.type-style('body-compact-02');
+ margin: 0 spacing.$spacing-02;
+}
diff --git a/packages/esm-ward-app/src/ward-patient-workspace/ward-patient.workspace.tsx b/packages/esm-ward-app/src/ward-patient-workspace/ward-patient.workspace.tsx
new file mode 100644
index 000000000..ff9e6ff2f
--- /dev/null
+++ b/packages/esm-ward-app/src/ward-patient-workspace/ward-patient.workspace.tsx
@@ -0,0 +1,79 @@
+import React, { useEffect, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { InlineNotification } from '@carbon/react';
+import { InlineLoading } from '@carbon/react';
+import {
+ type DefaultWorkspaceProps,
+ ExtensionSlot,
+ attach,
+ getPatientName,
+ usePatient,
+ age,
+} from '@openmrs/esm-framework';
+import styles from './ward-patient.style.scss';
+
+attach('ward-patient-workspace-header-slot', 'patient-vitals-info');
+
+export interface WardPatientWorkspaceProps extends DefaultWorkspaceProps {
+ patientUuid: string;
+}
+
+export default function WardPatientWorkspace({ patientUuid, setTitle }: WardPatientWorkspaceProps) {
+ const { t } = useTranslation();
+ const { patient, isLoading, error } = usePatient(patientUuid);
+
+ useEffect(() => {
+ if (isLoading) {
+ setTitle(t('wardPatientWorkspaceTitle', 'Ward Patient'), );
+ } else if (patient) {
+ setTitle(getPatientName(patient), );
+ } else if (error) {
+ setTitle(t('wardPatientWorkspaceTitle', 'Ward Patient'));
+ }
+ }, [patient]);
+
+ return (
+
+ {isLoading ? (
+
+ ) : patient ? (
+
+ ) : error ? (
+ {error.message}
+ ) : (
+
+ {t('failedToLoadPatientWorkspace', 'Ward patient workspace has failed to load.')}
+
+ )}
+
+ );
+}
+
+interface WardPatientWorkspaceViewProps {
+ patient: fhir.Patient;
+}
+
+function WardPatientWorkspaceView({ patient }: WardPatientWorkspaceViewProps) {
+ const extensionSlotState = useMemo(() => ({ patient, patientUuid: patient.id }), [patient]);
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+}
+
+function PatientWorkspaceTitle({ patient }: { patient: fhir.Patient }) {
+ return (
+ <>
+ {getPatientName(patient)}
+ · {patient.gender}
+ · {age(patient.birthDate)}
+ >
+ );
+}
diff --git a/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.component.tsx b/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.component.tsx
index c7c6ba65f..81de509d4 100644
--- a/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.component.tsx
+++ b/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.component.tsx
@@ -1,45 +1,49 @@
-import { SkeletonIcon } from '@carbon/react';
-import { Movement } from '@carbon/react/icons';
-import { launchWorkspace, showNotification, type Location } from '@openmrs/esm-framework';
import React from 'react';
-import { useTranslation } from 'react-i18next';
+import { Movement } from '@carbon/react/icons';
+import { Button, InlineNotification } from '@carbon/react';
+import { ArrowRightIcon, isDesktop, launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
import { useInpatientRequest } from '../hooks/useInpatientRequest';
+import { useTranslation } from 'react-i18next';
+import useWardLocation from '../hooks/useWardLocation';
import styles from './admission-requests.scss';
-interface AdmissionRequestsBarProps {
- location: Location;
-}
-
-const AdmissionRequestsBar: React.FC = ({ location }) => {
- const { inpatientRequests, isLoading, error } = useInpatientRequest(location.uuid);
+const AdmissionRequestsBar = () => {
+ const { location } = useWardLocation();
+ const { inpatientRequests, isLoading, error } = useInpatientRequest(location?.uuid);
const admissionRequests = inpatientRequests?.filter((request) => request.type == 'ADMISSION');
const { t } = useTranslation();
+ const layout = useLayoutType();
- if (isLoading) {
- return ;
+ if (isLoading || !admissionRequests?.length) {
+ return null;
}
if (error) {
- showNotification({
- kind: 'error',
- title: t('errorLoadingPatientAdmissionRequests', 'Error Loading Patient Admission Requests'),
- description: error.message,
- });
- return <>>;
+ console.error(error);
+ return (
+
+ );
}
- return admissionRequests.length > 0 ? (
+ return (
- {admissionRequests.length} admission requests
-
+
+ {t('admissionRequestsCount', '{{count}} admission requests', {
+ count: admissionRequests.length,
+ })}
+
+
- ) : (
- <>>
);
};
diff --git a/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.test.tsx b/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.test.tsx
index c00d76004..8fd887961 100644
--- a/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.test.tsx
+++ b/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.test.tsx
@@ -1,12 +1,11 @@
+import React from 'react';
import userEvent from '@testing-library/user-event';
import { renderWithSwr } from '../../../../tools/test-utils';
import { screen } from '@testing-library/react';
import { launchWorkspace } from '@openmrs/esm-framework';
-import React from 'react';
import AdmissionRequestsBar from './admission-requests-bar.component';
import { mockInpatientRequest } from '../../../../__mocks__/ward-patient';
import { useInpatientRequest } from '../hooks/useInpatientRequest';
-import { mockLocationInpatientWard } from '../../../../__mocks__/locations.mock';
jest.mock('@openmrs/esm-framework', () => {
return {
@@ -30,13 +29,15 @@ jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse);
describe('Admission Requests Button', () => {
it('call launch workspace when clicked on manage button', async () => {
const user = userEvent.setup();
- renderWithSwr();
+ renderWithSwr();
+
await user.click(screen.getByRole('button', { name: /manage/i }));
expect(launchWorkspace).toHaveBeenCalled();
});
it('there should be one admission request', () => {
- const { getByText } = renderWithSwr();
- expect(getByText('1 admission requests')).toBeInTheDocument();
+ renderWithSwr();
+
+ expect(screen.getByText('1 admission requests')).toBeInTheDocument();
});
});
diff --git a/packages/esm-ward-app/src/ward-view-header/admission-requests.scss b/packages/esm-ward-app/src/ward-view-header/admission-requests.scss
index e22cb1d0b..83a4ebd2c 100644
--- a/packages/esm-ward-app/src/ward-view-header/admission-requests.scss
+++ b/packages/esm-ward-app/src/ward-view-header/admission-requests.scss
@@ -3,12 +3,19 @@
@import '~@openmrs/esm-styleguide/src/vars';
.admissionRequestsContainer {
- width: fit-content;
- border-left: 2px solid $color-blue-60-2;
background-color: $color-gray-70;
display: flex;
align-items: center;
- padding: spacing.$spacing-02;
+ padding: spacing.$spacing-02 0 spacing.$spacing-02 spacing.$spacing-04;
+ background-color: #393939;
+
+ & > button {
+ color: #78a9ff;
+
+ svg {
+ fill: #78a9ff !important;
+ }
+ }
}
.movementIcon {
@@ -16,27 +23,11 @@
border-radius: 50%;
fill: $ui-03;
background-color: $color-blue-60-2;
-}
-
-.manageButton {
- background-color: transparent;
- border: none;
- color: $inverse-link;
- cursor: pointer;
- margin-left: spacing.$spacing-04;
- &::after {
- content: '→';
- padding: spacing.$spacing-02;
- }
+ margin-right: spacing.$spacing-03;
}
.content {
@include type.type-style('heading-compact-01');
color: $ui-02;
- margin-left: spacing.$spacing-02;
-}
-
-.skeleton {
- height: 20px;
- width: 120px;
+ margin-right: spacing.$spacing-03;
}
diff --git a/packages/esm-ward-app/src/ward-view-header/ward-view-header.component.tsx b/packages/esm-ward-app/src/ward-view-header/ward-view-header.component.tsx
index 025abafb8..7932cfa92 100644
--- a/packages/esm-ward-app/src/ward-view-header/ward-view-header.component.tsx
+++ b/packages/esm-ward-app/src/ward-view-header/ward-view-header.component.tsx
@@ -1,16 +1,16 @@
import React from 'react';
import styles from './ward-view-header.scss';
import AdmissionRequestsBar from './admission-requests-bar.component';
-import { type Location } from '@openmrs/esm-framework';
+import useWardLocation from '../hooks/useWardLocation';
-interface WardViewHeaderProps {
- location: Location;
-}
-const WardViewHeader: React.FC = ({ location }) => {
+interface WardViewHeaderProps {}
+
+const WardViewHeader: React.FC = () => {
+ const { location } = useWardLocation();
return (
-
{location.display}
-
+
{location?.display}
+
);
};
diff --git a/packages/esm-ward-app/src/ward-view/ward-view.component.tsx b/packages/esm-ward-app/src/ward-view/ward-view.component.tsx
index 8aeec9f20..df9f8c59c 100644
--- a/packages/esm-ward-app/src/ward-view/ward-view.component.tsx
+++ b/packages/esm-ward-app/src/ward-view/ward-view.component.tsx
@@ -1,8 +1,7 @@
-import { InlineNotification } from '@carbon/react';
-import { WorkspaceContainer, useFeatureFlag, useLocations, useSession, type Location } from '@openmrs/esm-framework';
import React, { useMemo } from 'react';
+import { InlineNotification } from '@carbon/react';
import { useTranslation } from 'react-i18next';
-import { useParams } from 'react-router-dom';
+import { WorkspaceContainer, useFeatureFlag } from '@openmrs/esm-framework';
import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
import WardBed from './ward-bed.component';
@@ -11,47 +10,42 @@ import styles from './ward-view.scss';
import WardViewHeader from '../ward-view-header/ward-view-header.component';
import { type AdmittedPatient, type WardPatient } from '../types';
import { useAdmittedPatients } from '../hooks/useAdmittedPatients';
+import useWardLocation from '../hooks/useWardLocation';
const WardView = () => {
- const { locationUuid: locationUuidFromUrl } = useParams();
- const { sessionLocation } = useSession();
- const allLocations = useLocations();
+ const response = useWardLocation();
+ const { isLoadingLocation, errorFetchingLocation, invalidLocation } = response;
+
const { t } = useTranslation();
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
- const locationFromUrl = allLocations.find((l) => l.uuid === locationUuidFromUrl);
- const invalidLocation = Boolean(locationUuidFromUrl && !locationFromUrl);
- const location = (locationFromUrl ?? sessionLocation) as any as Location;
+
//TODO:Display patients with admitted status (based on their observations) that have no beds assigned
- if (!isBedManagementModuleInstalled) {
+ if (!isBedManagementModuleInstalled || isLoadingLocation) {
return <>>;
}
- return invalidLocation ? (
-
- ) : (
+ if (invalidLocation) {
+ return ;
+ }
+
+ return (
);
};
-const WardViewByLocation = ({ location }: { location: Location }) => {
+const WardViewByLocation = () => {
+ const { location } = useWardLocation();
const {
admissionLocation,
isLoading: isLoadingLocation,
error: errorLoadingLocation,
- } = useAdmissionLocation(location.uuid);
+ } = useAdmissionLocation(location?.uuid);
const {
admittedPatients,
isLoading: isLoadingPatients,
@@ -80,7 +74,8 @@ const WardViewByLocation = ({ location }: { location: Location }) => {
// and not need the one from bedLayouts, however, the emr api
// does not respect custom representation right now and does not return
// all required fields for the patient object
- return { ...admittedPatient, admitted: true };
+ // TODO: change after this is done. https://openmrs.atlassian.net/browse/EA-192
+ return { ...admittedPatient, patient, admitted: true };
}
// patient assigned a bed but *not* admitted
diff --git a/packages/esm-ward-app/src/ward-view/ward-view.test.tsx b/packages/esm-ward-app/src/ward-view/ward-view.test.tsx
index e2db90508..59387c382 100644
--- a/packages/esm-ward-app/src/ward-view/ward-view.test.tsx
+++ b/packages/esm-ward-app/src/ward-view/ward-view.test.tsx
@@ -1,22 +1,21 @@
+import React from 'react';
+import { screen } from '@testing-library/react';
import {
type Person,
type ConfigSchema,
getDefaultsFromConfigSchema,
useConfig,
- useSession,
useFeatureFlag,
} from '@openmrs/esm-framework';
-import { screen } from '@testing-library/react';
-import React from 'react';
import { useParams } from 'react-router-dom';
-import { mockLocations } from '../../../../__mocks__/locations.mock';
import { mockAdmissionLocation } from '../../../../__mocks__/wards.mock';
import { renderWithSwr } from '../../../../tools/test-utils';
import { configSchema } from '../config-schema';
import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
-import WardView from './ward-view.component';
import { mockPatientAlice } from '../../../../__mocks__/patient.mock';
import { useAdmittedPatients } from '../hooks/useAdmittedPatients';
+import useWardLocation from '../hooks/useWardLocation';
+import WardView from './ward-view.component';
jest.replaceProperty(mockPatientAlice.person as Person, 'preferredName', {
uuid: '',
@@ -28,21 +27,18 @@ jest.mocked(useConfig).mockReturnValue({
...getDefaultsFromConfigSchema(configSchema),
});
-const mockedSessionLocation = { uuid: 'abcd', display: 'mock location', links: [] };
-jest.mocked(useSession).mockReturnValue({
- sessionLocation: mockedSessionLocation,
- authenticated: true,
- sessionId: 'sessionId',
-});
-
const mockedUseFeatureFlag = useFeatureFlag as jest.Mock;
-jest.mock('@openmrs/esm-framework', () => {
- return {
- ...jest.requireActual('@openmrs/esm-framework'),
- useLocations: jest.fn().mockImplementation(() => mockLocations.data.results),
- };
-});
+jest.mock('../hooks/useWardLocation', () =>
+ jest.fn().mockReturnValue({
+ location: { uuid: 'abcd', display: 'mock location' },
+ isLoadingLocation: false,
+ errorFetchingLocation: null,
+ invalidLocation: false,
+ }),
+);
+
+const mockedUseWardLocation = useWardLocation as jest.Mock;
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
@@ -75,15 +71,14 @@ jest.mocked(useAdmittedPatients).mockReturnValue({
describe('WardView:', () => {
it('renders the session location when no location provided in URL', () => {
renderWithSwr();
- const header = screen.getByRole('heading', { name: mockedSessionLocation.display });
+ const header = screen.getByRole('heading', { name: 'mock location' });
expect(header).toBeInTheDocument();
});
it('renders the location provided in URL', () => {
- const locationToUse = mockLocations.data.results[0];
- mockedUseParams.mockReturnValueOnce({ locationUuid: locationToUse.uuid });
+ mockedUseParams.mockReturnValueOnce({ locationUuid: 'abcd' });
renderWithSwr();
- const header = screen.getByRole('heading', { name: locationToUse.display });
+ const header = screen.getByRole('heading', { name: 'mock location' });
expect(header).toBeInTheDocument();
});
@@ -94,17 +89,23 @@ describe('WardView:', () => {
});
it('renders notification for invalid location uuid', () => {
- mockedUseParams.mockReturnValueOnce({ locationUuid: 'invalid-uuid' });
+ mockedUseWardLocation.mockReturnValueOnce({
+ location: null,
+ isLoadingLocation: false,
+ errorFetchingLocation: null,
+ invalidLocation: true,
+ });
+
renderWithSwr();
const notification = screen.getByRole('status');
expect(notification).toBeInTheDocument();
- const invalidText = screen.getByText('Unknown location uuid: invalid-uuid');
+ const invalidText = screen.getByText('Invalid location specified');
expect(invalidText).toBeInTheDocument();
});
it('screen should be empty if backend module is not installed', () => {
mockedUseFeatureFlag.mockReturnValueOnce(false);
const { container } = renderWithSwr();
- expect(container.firstChild).not.toBeInTheDocument();
+ expect(container).toBeEmptyDOMElement();
});
});
diff --git a/packages/esm-ward-app/src/ward-workspace/admission-request-workspace.test.tsx b/packages/esm-ward-app/src/ward-workspace/admission-request-workspace.test.tsx
index 1af2ac831..e90832b85 100644
--- a/packages/esm-ward-app/src/ward-workspace/admission-request-workspace.test.tsx
+++ b/packages/esm-ward-app/src/ward-workspace/admission-request-workspace.test.tsx
@@ -1,16 +1,10 @@
-import { renderWithSwr } from '../../../../tools/test-utils';
import React from 'react';
+import { screen } from '@testing-library/react';
import AdmissionRequestsWorkspace from './admission-requests-workspace.component';
-import {
- type ConfigSchema,
- type Person,
- closeWorkspace,
- getDefaultsFromConfigSchema,
- useConfig,
-} from '@openmrs/esm-framework';
+import { type ConfigSchema, type Person, getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
+import { renderWithSwr } from 'tools';
+import { mockInpatientRequest } from '__mocks__';
import { configSchema } from '../config-schema';
-import { useInpatientRequest } from '../hooks/useInpatientRequest';
-import { mockInpatientRequest } from '../../../../__mocks__/ward-patient';
jest.replaceProperty(mockInpatientRequest.patient.person as Person, 'preferredName', {
uuid: '',
@@ -31,8 +25,8 @@ jest.mock('@openmrs/esm-framework', () => {
describe('Admission Requests Workspace', () => {
it('should render a admission request card', () => {
- const { getByText } = renderWithSwr();
+ renderWithSwr();
const { givenName, familyName } = mockInpatientRequest.patient.person!.preferredName!;
- expect(getByText(givenName + ' ' + familyName)).toBeInTheDocument();
+ expect(screen.getByText(givenName + ' ' + familyName)).toBeInTheDocument();
});
});
diff --git a/packages/esm-ward-app/translations/en.json b/packages/esm-ward-app/translations/en.json
index b47440f91..dd1c76c5a 100644
--- a/packages/esm-ward-app/translations/en.json
+++ b/packages/esm-ward-app/translations/en.json
@@ -1,10 +1,13 @@
{
+ "admissionRequestsCount_one": "{{count}} admission request",
+ "admissionRequestsCount_other": "{{count}} admission requests",
"bedShare": "Bed share",
"emptyBed": "Empty bed",
- "errorLoadingPatientAdmissionRequests": "Error Loading Patient Admission Requests",
+ "errorLoadingPatientAdmissionRequests": "Error Loading patient admission requests",
+ "errorLoadingPatients": "Error loading admitted patients",
"errorLoadingWardLocation": "Error loading ward location",
"invalidLocationSpecified": "Invalid location specified",
"invalidWardLocation": "Invalid ward location: {{location}}",
- "noBedsConfigured": "No beds configured for this location",
- "unknownLocationUuid": "Unknown location uuid: {{locationUuidFromUrl}}"
+ "manage": "Manage",
+ "noBedsConfigured": "No beds configured for this location"
}
diff --git a/yarn.lock b/yarn.lock
index 82bcfc64c..756add9fa 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1282,6 +1282,15 @@ __metadata:
languageName: node
linkType: hard
+"@babel/runtime@npm:^7.16.3":
+ version: 7.24.7
+ resolution: "@babel/runtime@npm:7.24.7"
+ dependencies:
+ regenerator-runtime: "npm:^0.14.0"
+ checksum: 10/7b77f566165dee62db3db0296e71d08cafda3f34e1b0dcefcd68427272e17c1704f4e4369bff76651b07b6e49d3ea5a0ce344818af9116e9292e4381e0918c76
+ languageName: node
+ linkType: hard
+
"@babel/template@npm:^7.18.10, @babel/template@npm:^7.3.3":
version: 7.18.10
resolution: "@babel/template@npm:7.18.10"
@@ -2638,9 +2647,9 @@ __metadata:
languageName: unknown
linkType: soft
-"@openmrs/esm-api@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-api@npm:5.6.1-pre.1966"
+"@openmrs/esm-api@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-api@npm:5.6.1-pre.2029"
dependencies:
"@types/fhir": "npm:0.0.31"
lodash-es: "npm:^4.17.21"
@@ -2649,17 +2658,17 @@ __metadata:
"@openmrs/esm-error-handling": 5.x
"@openmrs/esm-navigation": 5.x
"@openmrs/esm-offline": 5.x
- checksum: 10/ff4e8692bd76f90a080448bb9651aa001c5ed84cf52f115bdb00cc39090a58a0919aa9a5456dd639886205b2010c9ec325053c3d4d56a119606e8ed065d471bd
+ checksum: 10/30fd11ba6e965529b9268b982c4fcf8f9dab4ccdf6d9f6bfecfd77ff2b7511487444f7b1f2d4c33d8d36c2aa3c3e1c8de0706babdca537a48329acaffa8f7d79
languageName: node
linkType: hard
-"@openmrs/esm-app-shell@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-app-shell@npm:5.6.1-pre.1966"
+"@openmrs/esm-app-shell@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-app-shell@npm:5.6.1-pre.2029"
dependencies:
"@carbon/react": "npm:~1.37.0"
- "@openmrs/esm-framework": "npm:5.6.1-pre.1966"
- "@openmrs/esm-styleguide": "npm:5.6.1-pre.1966"
+ "@openmrs/esm-framework": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-styleguide": "npm:5.6.1-pre.2029"
dayjs: "npm:^1.10.4"
dexie: "npm:^3.0.3"
html-webpack-plugin: "npm:^5.5.0"
@@ -2684,7 +2693,7 @@ __metadata:
workbox-strategies: "npm:^6.1.5"
workbox-webpack-plugin: "npm:^6.1.5"
workbox-window: "npm:^6.1.5"
- checksum: 10/0966ee0a1b1a2b84d98f2dd2c1a73651736e92af9c7c6bbb842989c3e3f903f63e307556c1e919edc6205fcd8d016d8ff8be51c23d49e29d9980a2ab605103f8
+ checksum: 10/5e08556eba9cac0c0193decdb098be8b6c49b31a7b075147db98c6637ae0a45c532a61bcdcb4e36a451505f89ccdc0f9b7c24d6a4d02cc0c281f6653e6cfa392
languageName: node
linkType: hard
@@ -2707,53 +2716,53 @@ __metadata:
languageName: unknown
linkType: soft
-"@openmrs/esm-config@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-config@npm:5.6.1-pre.1966"
+"@openmrs/esm-config@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-config@npm:5.6.1-pre.2029"
dependencies:
ramda: "npm:^0.26.1"
peerDependencies:
"@openmrs/esm-globals": 5.x
"@openmrs/esm-state": 5.x
single-spa: 5.x
- checksum: 10/a2a7a7a4a86ade3585e501ebb748fc7cca67b4f9338bda7c2c12ebe2c5bcfb9698bc4a8205aad9056da019c51c74b90849cb4a3c2c1c4f62a36caf5c38f2163a
+ checksum: 10/c4b21eecf45c71fbf1368912068cee41415bd39b30c1861647fb4e037fb771df5ffd24119a98c0e2babac91f2eee8a7aab808833aee17466d0cc4d1764e1d32b
languageName: node
linkType: hard
-"@openmrs/esm-context@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-context@npm:5.6.1-pre.1966"
+"@openmrs/esm-context@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-context@npm:5.6.1-pre.2029"
dependencies:
immer: "npm:^10.0.4"
peerDependencies:
"@openmrs/esm-globals": 5.x
"@openmrs/esm-state": 5.x
- checksum: 10/24b0796c8711cb5eaeeb15f3d4d1ace491eb1402f2a492f49d30fade5a1765a878e654d94e525134cc7c59af470c30c3cd4164b7a78f0736e080ac5af0b83343
+ checksum: 10/da7eb4e7a01b90f5677ede602b87d4078b9ce1e0ff58002b889a194786156d3bb9841fb9f6c882c57f1dae1c557628c6655e24ce1bb5bc91a7414c9f20112341
languageName: node
linkType: hard
-"@openmrs/esm-dynamic-loading@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-dynamic-loading@npm:5.6.1-pre.1966"
+"@openmrs/esm-dynamic-loading@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-dynamic-loading@npm:5.6.1-pre.2029"
peerDependencies:
"@openmrs/esm-globals": 5.x
"@openmrs/esm-translations": 5.x
- checksum: 10/143d5ead7af3495c7f5210794dea257ff6b14c4d995805302e7d4a4e61082b47b354eaabfdd6dafeed810b1959779c23e2a4bb26644ffa7a19273305e876f804
+ checksum: 10/b09a3007a56adb9b2e3d70a68b7149dcc9fed5003fd1806aedb675d979bff3f8abc363e57b92e322c8e97ea42628bf779da9c0b907bc9213e377632f25fb7de6
languageName: node
linkType: hard
-"@openmrs/esm-error-handling@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-error-handling@npm:5.6.1-pre.1966"
+"@openmrs/esm-error-handling@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-error-handling@npm:5.6.1-pre.2029"
peerDependencies:
"@openmrs/esm-globals": 5.x
- checksum: 10/c23c5e1ee5190b6514d000b7437ee31e573d96a0c8f182bcc65690babaa5dbce3ac73432d94c5349cd28daaa23d6e6e51ae3008e193109c1aaf071a1787ba822
+ checksum: 10/348fa380407b766ddfe51760da2a5a8e07978a17585eb1563ac6d76f2a62b4df606e5a8a6e33a08701fd2538547a9ca2ff7cbfe76fc584dcfeaf4155d17c9ca5
languageName: node
linkType: hard
-"@openmrs/esm-extensions@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-extensions@npm:5.6.1-pre.1966"
+"@openmrs/esm-extensions@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-extensions@npm:5.6.1-pre.2029"
dependencies:
lodash-es: "npm:^4.17.21"
peerDependencies:
@@ -2763,43 +2772,43 @@ __metadata:
"@openmrs/esm-state": 5.x
"@openmrs/esm-utils": 5.x
single-spa: 5.x
- checksum: 10/697c8e1ed34b84df44627c5ed389a7f3cc86de65992c96d6c3b41343872a0530f89576db1405527631bb1e8a9d58c2b26bb6b270910f6b15439b867472068ba6
+ checksum: 10/dd00a667cd27d858da12a2cd2701066c4215a81bf2166cd450afc35005b4741304e12320357b0f35d65012a22bcc787d0e3ceceda67c800d9c5d9d9af6f73b35
languageName: node
linkType: hard
-"@openmrs/esm-feature-flags@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-feature-flags@npm:5.6.1-pre.1966"
+"@openmrs/esm-feature-flags@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-feature-flags@npm:5.6.1-pre.2029"
dependencies:
ramda: "npm:^0.26.1"
peerDependencies:
"@openmrs/esm-globals": 5.x
"@openmrs/esm-state": 5.x
single-spa: 5.x
- checksum: 10/d3c90922298a25e34cdd323f9e5a94ac8dfaab1dc43e5012ea114e15fc67f1f60a410b81294bd8e1f1b9c5f6b31962593cd2be171710f964d29c405d33d3e361
- languageName: node
- linkType: hard
-
-"@openmrs/esm-framework@npm:5.6.1-pre.1966, @openmrs/esm-framework@npm:next":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-framework@npm:5.6.1-pre.1966"
- dependencies:
- "@openmrs/esm-api": "npm:5.6.1-pre.1966"
- "@openmrs/esm-config": "npm:5.6.1-pre.1966"
- "@openmrs/esm-context": "npm:5.6.1-pre.1966"
- "@openmrs/esm-dynamic-loading": "npm:5.6.1-pre.1966"
- "@openmrs/esm-error-handling": "npm:5.6.1-pre.1966"
- "@openmrs/esm-extensions": "npm:5.6.1-pre.1966"
- "@openmrs/esm-feature-flags": "npm:5.6.1-pre.1966"
- "@openmrs/esm-globals": "npm:5.6.1-pre.1966"
- "@openmrs/esm-navigation": "npm:5.6.1-pre.1966"
- "@openmrs/esm-offline": "npm:5.6.1-pre.1966"
- "@openmrs/esm-react-utils": "npm:5.6.1-pre.1966"
- "@openmrs/esm-routes": "npm:5.6.1-pre.1966"
- "@openmrs/esm-state": "npm:5.6.1-pre.1966"
- "@openmrs/esm-styleguide": "npm:5.6.1-pre.1966"
- "@openmrs/esm-translations": "npm:5.6.1-pre.1966"
- "@openmrs/esm-utils": "npm:5.6.1-pre.1966"
+ checksum: 10/afb67fd891f3faea05a23bdac50c75c26ef435ce729a3dda8203f5ab14f8ed255a54dce378c2ab43d7c740fcb56ad5f6c9d36bc9ac72b593d12e55f495ea775a
+ languageName: node
+ linkType: hard
+
+"@openmrs/esm-framework@npm:5.6.1-pre.2029, @openmrs/esm-framework@npm:next":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-framework@npm:5.6.1-pre.2029"
+ dependencies:
+ "@openmrs/esm-api": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-config": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-context": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-dynamic-loading": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-error-handling": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-extensions": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-feature-flags": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-globals": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-navigation": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-offline": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-react-utils": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-routes": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-state": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-styleguide": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-translations": "npm:5.6.1-pre.2029"
+ "@openmrs/esm-utils": "npm:5.6.1-pre.2029"
dayjs: "npm:^1.10.7"
peerDependencies:
dayjs: 1.x
@@ -2810,35 +2819,35 @@ __metadata:
rxjs: 6.x
single-spa: 5.x
swr: 2.x
- checksum: 10/fb83452f829440fb509a18f33c010293bcb361cb41c57f793a92f1dd71aef30dde6f24ed15cf3cd662387439d3ba5ffc119b06a1f4827965d7895e9890ef138f
+ checksum: 10/3dba6b594d295bf51e693371be0222aedff18f7d78860c1270017cd174e20c3c32632363399f76e81c5454f48dbd401a5fdd41d4e08cac228061d05ef53e345b
languageName: node
linkType: hard
-"@openmrs/esm-globals@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-globals@npm:5.6.1-pre.1966"
+"@openmrs/esm-globals@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-globals@npm:5.6.1-pre.2029"
dependencies:
"@types/fhir": "npm:0.0.31"
peerDependencies:
single-spa: 5.x
- checksum: 10/37cbf5f718efa5a9f448a090950830fbff413c3b195ef05f9ca6bafb9ea9fdd27921bcd83411629ef6777c64f233a0af6af0d7a56c0461dcbcb17d792559c277
+ checksum: 10/1e8674e73be81ee6af955ebcf30d67bb92c53b62bf26519b40cdf9baa31489a8a2b1b890f6c24060ab7b61501ed48d95ab67693600956d82df2001abb4bdd3d1
languageName: node
linkType: hard
-"@openmrs/esm-navigation@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-navigation@npm:5.6.1-pre.1966"
+"@openmrs/esm-navigation@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-navigation@npm:5.6.1-pre.2029"
dependencies:
path-to-regexp: "npm:6.1.0"
peerDependencies:
"@openmrs/esm-state": 5.x
- checksum: 10/fe5a6854b268cd759235da05ae5a0762cc1710f6ebf4e02b80fc52dc06fb4227c67f58512707e68afc036d0f652312e81a51533fa7947d4ea3cb432fec810511
+ checksum: 10/73aa79e91b1582910c5798459f4bc1d9f02144dedcf95d83d937afe250e74d420167e70b9549d8de3457ab7bce4d553ca5235000b0e482308d53fb5f361efe39
languageName: node
linkType: hard
-"@openmrs/esm-offline@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-offline@npm:5.6.1-pre.1966"
+"@openmrs/esm-offline@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-offline@npm:5.6.1-pre.2029"
dependencies:
dexie: "npm:^3.0.3"
lodash-es: "npm:^4.17.21"
@@ -2849,7 +2858,7 @@ __metadata:
"@openmrs/esm-globals": 5.x
"@openmrs/esm-state": 5.x
rxjs: 6.x
- checksum: 10/c044b041d1ccbc51190b1958fc85cdaf98aa98d24a9dcbdd50bb6007829c9a1da51466835e1a4391eb7851588a8c4c58efc9ff4fa99e3f0524e27441df31e64d
+ checksum: 10/da8505503afec8a710c0a72d5cd80f9a56d2b1ad2339c4ecc6e24932b99f44334079dc1201c5d31de778baeeaddf1cc6bf9f0e22b9d2f7daa2388c7dca309a62
languageName: node
linkType: hard
@@ -2921,7 +2930,9 @@ __metadata:
dayjs: "npm:^1.8.36"
dotenv: "npm:^16.0.3"
eslint: "npm:^8.55.0"
+ eslint-plugin-jest-dom: "npm:^5.4.0"
eslint-plugin-react-hooks: "npm:^4.6.0"
+ eslint-plugin-testing-library: "npm:^6.2.2"
husky: "npm:^8.0.3"
i18next: "npm:^21.10.0"
i18next-parser: "npm:^6.6.0"
@@ -2988,9 +2999,9 @@ __metadata:
languageName: unknown
linkType: soft
-"@openmrs/esm-react-utils@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-react-utils@npm:5.6.1-pre.1966"
+"@openmrs/esm-react-utils@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-react-utils@npm:5.6.1-pre.2029"
dependencies:
lodash-es: "npm:^4.17.21"
single-spa-react: "npm:^6.0.0"
@@ -3011,17 +3022,17 @@ __metadata:
react-i18next: 11.x
rxjs: 6.x
swr: 2.x
- checksum: 10/24a3c038a74b416fca70d92b48cf4454d8e19d43724e1fdf10a9b6f6bf660debdf2c69c99876ea43e3762441140f6a821a71befadcdc9b5c7b2fac33d10e36f1
+ checksum: 10/480a6b8b0f1b5872ebf371fa76ebde727185862b20c2e7aca63be226f3ac4268ec56fd719709d3d79f1a82a2b263997703932eccc70aa8b84bdba208ab96c68c
languageName: node
linkType: hard
-"@openmrs/esm-routes@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-routes@npm:5.6.1-pre.1966"
+"@openmrs/esm-routes@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-routes@npm:5.6.1-pre.2029"
peerDependencies:
"@openmrs/esm-globals": 5.x
"@openmrs/esm-utils": 5.x
- checksum: 10/6f4d5445f37befb78a69f63ac92b35f2a8bf34f6cfb5f1a7b63063aaabe9770eefb4938957eba039692c6a2ca49e136d64f12a5268002f87d158a2e248eaa521
+ checksum: 10/d7b4a027d9898a278b3c4f80f951c88d6f5d61f41d4afb69c47b672b6a95502e3df17e5227ccd328006686d1c727242aad6fc00cb04e467a7d4ec7f93579605f
languageName: node
linkType: hard
@@ -3041,20 +3052,20 @@ __metadata:
languageName: unknown
linkType: soft
-"@openmrs/esm-state@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-state@npm:5.6.1-pre.1966"
+"@openmrs/esm-state@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-state@npm:5.6.1-pre.2029"
dependencies:
zustand: "npm:^4.3.6"
peerDependencies:
"@openmrs/esm-globals": 5.x
- checksum: 10/cbfe102a5c57f92aee0a153ac3c9433203a1e5469d821fc8e44cef7f3ecbac1ef08307c6a7983db418e3830a0cb533da3db890926a6084444266035187c732a3
+ checksum: 10/8d61ebfd72d3b8915f13ddc7434055046bab0fbf533d58d6e149d7d351aec4e57e85b8c4fd6492646a65ee368cea86d896b68bb2479c999f71703bd7ddea7ce1
languageName: node
linkType: hard
-"@openmrs/esm-styleguide@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-styleguide@npm:5.6.1-pre.1966"
+"@openmrs/esm-styleguide@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-styleguide@npm:5.6.1-pre.2029"
dependencies:
"@carbon/charts": "npm:^1.12.0"
"@carbon/react": "npm:~1.37.0"
@@ -3077,24 +3088,24 @@ __metadata:
react: 18.x
react-dom: 18.x
rxjs: 6.x
- checksum: 10/2984390db19c247b90fea11ef462de051d314330377bbb12ba1b9dc8d08229a33b2eac87e94c773bff28cd32e185340aba9b7a5071b129ea29695b6d76cbc31f
+ checksum: 10/9d86ef524495d01a0f1c7a581a25416dd3e4f923c31decdde8ed3087094ed56b4406a6c4c2c85afd9d4935ee6cb96c4f3a4be112fe8ec59fe5f72ba3ab750e2b
languageName: node
linkType: hard
-"@openmrs/esm-translations@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-translations@npm:5.6.1-pre.1966"
+"@openmrs/esm-translations@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-translations@npm:5.6.1-pre.2029"
dependencies:
i18next: "npm:21.10.0"
peerDependencies:
i18next: 21.x
- checksum: 10/d8f456b85d1bb3d51cf44af8fcdd6e9958db206426819fcc67b742a061d24a952537392d4090113174167b77cdb455c3735ddc57655400513d6bd13604807b03
+ checksum: 10/56779135eb11fb523b316233232055e0416e0c9dbb82c279585b2395d30cd7715391d1d7367e0bc12cb0301f06cff6c72fdc13b491b3fe0d7ed8df645486eea1
languageName: node
linkType: hard
-"@openmrs/esm-utils@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/esm-utils@npm:5.6.1-pre.1966"
+"@openmrs/esm-utils@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/esm-utils@npm:5.6.1-pre.2029"
dependencies:
"@internationalized/date": "npm:^3.5.4"
semver: "npm:7.3.2"
@@ -3103,7 +3114,7 @@ __metadata:
dayjs: 1.x
i18next: 21.x
rxjs: 6.x
- checksum: 10/99ab688ad264b507d0189eeb93763db1de3dc2f4d7949d6a422f773f4aeac0cd0fadc7895f255675e8d2a80aa2049af7aeea6678362ee4aeedd7d7ab6e07a4f0
+ checksum: 10/723a7eeed636eddae7ea06042b4723743afe18dc0f7ad60c6d2b6703dd770194a65020024e1a37da8e443603050f2822cd45ab9165a0e91528b52f907d7f2a46
languageName: node
linkType: hard
@@ -3123,9 +3134,9 @@ __metadata:
languageName: unknown
linkType: soft
-"@openmrs/webpack-config@npm:5.6.1-pre.1966":
- version: 5.6.1-pre.1966
- resolution: "@openmrs/webpack-config@npm:5.6.1-pre.1966"
+"@openmrs/webpack-config@npm:5.6.1-pre.2029":
+ version: 5.6.1-pre.2029
+ resolution: "@openmrs/webpack-config@npm:5.6.1-pre.2029"
dependencies:
"@swc/core": "npm:^1.3.58"
clean-webpack-plugin: "npm:^4.0.0"
@@ -3142,7 +3153,7 @@ __metadata:
webpack-stats-plugin: "npm:^1.0.3"
peerDependencies:
webpack: 5.x
- checksum: 10/bad42e4401b0ca5e91c9033c9d043e834ab46a0904691f85b3239191ab0ef6b285c5552e4f47a0074ae7ce72911479e46f8f8820c4025c4ca263159f80db75ab
+ checksum: 10/9697e45c4b55d3344ee4f6f01cd11937676e66aca30dba909a807c2ea334250a068b5d93fc79da178ca6ff7c47c1f525da10d30d312a2c1c9b56d98e18651e74
languageName: node
linkType: hard
@@ -5386,6 +5397,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/semver@npm:^7.3.12":
+ version: 7.5.8
+ resolution: "@types/semver@npm:7.5.8"
+ checksum: 10/3496808818ddb36deabfe4974fd343a78101fa242c4690044ccdc3b95dcf8785b494f5d628f2f47f38a702f8db9c53c67f47d7818f2be1b79f2efb09692e1178
+ languageName: node
+ linkType: hard
+
"@types/semver@npm:^7.5.0":
version: 7.5.6
resolution: "@types/semver@npm:7.5.6"
@@ -5547,6 +5565,16 @@ __metadata:
languageName: node
linkType: hard
+"@typescript-eslint/scope-manager@npm:5.62.0":
+ version: 5.62.0
+ resolution: "@typescript-eslint/scope-manager@npm:5.62.0"
+ dependencies:
+ "@typescript-eslint/types": "npm:5.62.0"
+ "@typescript-eslint/visitor-keys": "npm:5.62.0"
+ checksum: 10/e827770baa202223bc0387e2fd24f630690809e460435b7dc9af336c77322290a770d62bd5284260fa881c86074d6a9fd6c97b07382520b115f6786b8ed499da
+ languageName: node
+ linkType: hard
+
"@typescript-eslint/scope-manager@npm:6.15.0":
version: 6.15.0
resolution: "@typescript-eslint/scope-manager@npm:6.15.0"
@@ -5574,6 +5602,13 @@ __metadata:
languageName: node
linkType: hard
+"@typescript-eslint/types@npm:5.62.0":
+ version: 5.62.0
+ resolution: "@typescript-eslint/types@npm:5.62.0"
+ checksum: 10/24e8443177be84823242d6729d56af2c4b47bfc664dd411a1d730506abf2150d6c31bdefbbc6d97c8f91043e3a50e0c698239dcb145b79bb6b0c34469aaf6c45
+ languageName: node
+ linkType: hard
+
"@typescript-eslint/types@npm:6.15.0":
version: 6.15.0
resolution: "@typescript-eslint/types@npm:6.15.0"
@@ -5581,6 +5616,24 @@ __metadata:
languageName: node
linkType: hard
+"@typescript-eslint/typescript-estree@npm:5.62.0":
+ version: 5.62.0
+ resolution: "@typescript-eslint/typescript-estree@npm:5.62.0"
+ dependencies:
+ "@typescript-eslint/types": "npm:5.62.0"
+ "@typescript-eslint/visitor-keys": "npm:5.62.0"
+ debug: "npm:^4.3.4"
+ globby: "npm:^11.1.0"
+ is-glob: "npm:^4.0.3"
+ semver: "npm:^7.3.7"
+ tsutils: "npm:^3.21.0"
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ checksum: 10/06c975eb5f44b43bd19fadc2e1023c50cf87038fe4c0dd989d4331c67b3ff509b17fa60a3251896668ab4d7322bdc56162a9926971218d2e1a1874d2bef9a52e
+ languageName: node
+ linkType: hard
+
"@typescript-eslint/typescript-estree@npm:6.15.0":
version: 6.15.0
resolution: "@typescript-eslint/typescript-estree@npm:6.15.0"
@@ -5616,6 +5669,34 @@ __metadata:
languageName: node
linkType: hard
+"@typescript-eslint/utils@npm:^5.58.0":
+ version: 5.62.0
+ resolution: "@typescript-eslint/utils@npm:5.62.0"
+ dependencies:
+ "@eslint-community/eslint-utils": "npm:^4.2.0"
+ "@types/json-schema": "npm:^7.0.9"
+ "@types/semver": "npm:^7.3.12"
+ "@typescript-eslint/scope-manager": "npm:5.62.0"
+ "@typescript-eslint/types": "npm:5.62.0"
+ "@typescript-eslint/typescript-estree": "npm:5.62.0"
+ eslint-scope: "npm:^5.1.1"
+ semver: "npm:^7.3.7"
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+ checksum: 10/15ef13e43998a082b15f85db979f8d3ceb1f9ce4467b8016c267b1738d5e7cdb12aa90faf4b4e6dd6486c236cf9d33c463200465cf25ff997dbc0f12358550a1
+ languageName: node
+ linkType: hard
+
+"@typescript-eslint/visitor-keys@npm:5.62.0":
+ version: 5.62.0
+ resolution: "@typescript-eslint/visitor-keys@npm:5.62.0"
+ dependencies:
+ "@typescript-eslint/types": "npm:5.62.0"
+ eslint-visitor-keys: "npm:^3.3.0"
+ checksum: 10/dc613ab7569df9bbe0b2ca677635eb91839dfb2ca2c6fa47870a5da4f160db0b436f7ec0764362e756d4164e9445d49d5eb1ff0b87f4c058946ae9d8c92eb388
+ languageName: node
+ linkType: hard
+
"@typescript-eslint/visitor-keys@npm:6.15.0":
version: 6.15.0
resolution: "@typescript-eslint/visitor-keys@npm:6.15.0"
@@ -9010,6 +9091,22 @@ __metadata:
languageName: node
linkType: hard
+"eslint-plugin-jest-dom@npm:^5.4.0":
+ version: 5.4.0
+ resolution: "eslint-plugin-jest-dom@npm:5.4.0"
+ dependencies:
+ "@babel/runtime": "npm:^7.16.3"
+ requireindex: "npm:^1.2.0"
+ peerDependencies:
+ "@testing-library/dom": ^8.0.0 || ^9.0.0 || ^10.0.0
+ eslint: ^6.8.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
+ peerDependenciesMeta:
+ "@testing-library/dom":
+ optional: true
+ checksum: 10/b8b0b0249d066658a75723892bc6f52d6bcf03ff0a69fc5020548c49f740613a8f3acce647f8f04b292606d2bd0ab3372a695aa3d90b4efb19e71870bbddf637
+ languageName: node
+ linkType: hard
+
"eslint-plugin-react-hooks@npm:^4.6.0":
version: 4.6.0
resolution: "eslint-plugin-react-hooks@npm:4.6.0"
@@ -9019,7 +9116,18 @@ __metadata:
languageName: node
linkType: hard
-"eslint-scope@npm:5.1.1":
+"eslint-plugin-testing-library@npm:^6.2.2":
+ version: 6.2.2
+ resolution: "eslint-plugin-testing-library@npm:6.2.2"
+ dependencies:
+ "@typescript-eslint/utils": "npm:^5.58.0"
+ peerDependencies:
+ eslint: ^7.5.0 || ^8.0.0
+ checksum: 10/61947d0b81de1565c8627ec2d1e6636a8b6613cfe554a4671d011b3e88dfd77b498ce83b15bcf0a2df5570c44ad1d46d54058ed488f4e515d764196cbc6d65cf
+ languageName: node
+ linkType: hard
+
+"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1":
version: 5.1.1
resolution: "eslint-scope@npm:5.1.1"
dependencies:
@@ -13179,11 +13287,11 @@ __metadata:
linkType: hard
"openmrs@npm:next":
- version: 5.6.1-pre.1966
- resolution: "openmrs@npm:5.6.1-pre.1966"
+ version: 5.6.1-pre.2029
+ resolution: "openmrs@npm:5.6.1-pre.2029"
dependencies:
- "@openmrs/esm-app-shell": "npm:5.6.1-pre.1966"
- "@openmrs/webpack-config": "npm:5.6.1-pre.1966"
+ "@openmrs/esm-app-shell": "npm:5.6.1-pre.2029"
+ "@openmrs/webpack-config": "npm:5.6.1-pre.2029"
"@pnpm/npm-conf": "npm:^2.1.0"
"@swc/core": "npm:^1.3.58"
autoprefixer: "npm:^10.4.2"
@@ -13215,7 +13323,7 @@ __metadata:
yargs: "npm:^17.6.2"
bin:
openmrs: ./dist/cli.js
- checksum: 10/c6a7653b2f7ff8695fb782c1fddf19138348ed6102f960bb3975f32745095fd72eca9ebd3e9b09e2ab92f75266109e3fc8cf4f020f2965a2a6a4c4c114437a13
+ checksum: 10/c20d90cc585565ee242c21b7193e5721743d21b44159c827df556c3c04ff163ac7c509ef098f0a0478e4ed0479ab6aace97172e65f56dabf821e222dbda2b0a1
languageName: node
linkType: hard
@@ -14835,6 +14943,13 @@ __metadata:
languageName: node
linkType: hard
+"requireindex@npm:^1.2.0":
+ version: 1.2.0
+ resolution: "requireindex@npm:1.2.0"
+ checksum: 10/266d1cb31f6cbc4b6cf2e898f5bbc45581f7919bcf61bba5c45d0adb69b722b9ff5a13727be3350cde4520d7cd37f39df45d58a29854baaa4552cd6b05ae4a1a
+ languageName: node
+ linkType: hard
+
"requires-port@npm:^1.0.0":
version: 1.0.0
resolution: "requires-port@npm:1.0.0"
@@ -15273,6 +15388,15 @@ __metadata:
languageName: node
linkType: hard
+"semver@npm:^7.3.7":
+ version: 7.6.2
+ resolution: "semver@npm:7.6.2"
+ bin:
+ semver: bin/semver.js
+ checksum: 10/296b17d027f57a87ef645e9c725bff4865a38dfc9caf29b26aa084b85820972fbe7372caea1ba6857162fa990702c6d9c1d82297cecb72d56c78ab29070d2ca2
+ languageName: node
+ linkType: hard
+
"send@npm:0.18.0":
version: 0.18.0
resolution: "send@npm:0.18.0"
@@ -16408,7 +16532,7 @@ __metadata:
languageName: node
linkType: hard
-"tslib@npm:^1.10.0, tslib@npm:^1.9.0":
+"tslib@npm:^1.10.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0":
version: 1.14.1
resolution: "tslib@npm:1.14.1"
checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb
@@ -16422,6 +16546,17 @@ __metadata:
languageName: node
linkType: hard
+"tsutils@npm:^3.21.0":
+ version: 3.21.0
+ resolution: "tsutils@npm:3.21.0"
+ dependencies:
+ tslib: "npm:^1.8.1"
+ peerDependencies:
+ typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+ checksum: 10/ea036bec1dd024e309939ffd49fda7a351c0e87a1b8eb049570dd119d447250e2c56e0e6c00554e8205760e7417793fdebff752a46e573fbe07d4f375502a5b2
+ languageName: node
+ linkType: hard
+
"turbo-darwin-64@npm:2.0.6":
version: 2.0.6
resolution: "turbo-darwin-64@npm:2.0.6"