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"