@ampath/esm-dispensing-app 1.10.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +12 -0
- package/.eslintignore +2 -0
- package/.eslintrc +80 -0
- package/.husky/pre-commit +7 -0
- package/.husky/pre-push +6 -0
- package/.prettierignore +12 -0
- package/.tx/config +11 -0
- package/.yarn/versions/1c40b9b6.yml +0 -0
- package/.yarn/versions/ff162597.yml +0 -0
- package/LICENSE +401 -0
- package/README.md +124 -0
- package/__mocks__/react-i18next.js +51 -0
- package/dist/1043.js +1 -0
- package/dist/1043.js.map +1 -0
- package/dist/1119.js +1 -0
- package/dist/1197.js +1 -0
- package/dist/2146.js +1 -0
- package/dist/2177.js +2 -0
- package/dist/2177.js.LICENSE.txt +9 -0
- package/dist/2177.js.map +1 -0
- package/dist/2690.js +1 -0
- package/dist/2890.js +2 -0
- package/dist/2890.js.LICENSE.txt +14 -0
- package/dist/2890.js.map +1 -0
- package/dist/2898.js +1 -0
- package/dist/2898.js.map +1 -0
- package/dist/3041.js +1 -0
- package/dist/3041.js.map +1 -0
- package/dist/3099.js +1 -0
- package/dist/3184.js +2 -0
- package/dist/3184.js.LICENSE.txt +14 -0
- package/dist/3184.js.map +1 -0
- package/dist/3568.js +1 -0
- package/dist/3568.js.map +1 -0
- package/dist/3584.js +1 -0
- package/dist/4055.js +1 -0
- package/dist/4099.js +1 -0
- package/dist/4099.js.map +1 -0
- package/dist/4132.js +1 -0
- package/dist/4225.js +1 -0
- package/dist/4225.js.map +1 -0
- package/dist/4300.js +1 -0
- package/dist/4335.js +1 -0
- package/dist/4353.js +1 -0
- package/dist/4353.js.map +1 -0
- package/dist/439.js +1 -0
- package/dist/4618.js +1 -0
- package/dist/4652.js +1 -0
- package/dist/4944.js +1 -0
- package/dist/5173.js +1 -0
- package/dist/5241.js +1 -0
- package/dist/5422.js +1 -0
- package/dist/5422.js.map +1 -0
- package/dist/5442.js +1 -0
- package/dist/5661.js +1 -0
- package/dist/5897.js +1 -0
- package/dist/5897.js.map +1 -0
- package/dist/6022.js +1 -0
- package/dist/609.js +1 -0
- package/dist/609.js.map +1 -0
- package/dist/6468.js +1 -0
- package/dist/6540.js +2 -0
- package/dist/6540.js.LICENSE.txt +9 -0
- package/dist/6540.js.map +1 -0
- package/dist/6589.js +1 -0
- package/dist/6606.js +1 -0
- package/dist/6606.js.map +1 -0
- package/dist/6679.js +1 -0
- package/dist/6825.js +1 -0
- package/dist/6825.js.map +1 -0
- package/dist/6840.js +1 -0
- package/dist/6841.js +1 -0
- package/dist/6841.js.map +1 -0
- package/dist/6859.js +1 -0
- package/dist/7097.js +1 -0
- package/dist/7159.js +1 -0
- package/dist/723.js +1 -0
- package/dist/7240.js +1 -0
- package/dist/7240.js.map +1 -0
- package/dist/7255.js +1 -0
- package/dist/7255.js.map +1 -0
- package/dist/7617.js +1 -0
- package/dist/795.js +1 -0
- package/dist/8163.js +1 -0
- package/dist/8349.js +1 -0
- package/dist/8371.js +1 -0
- package/dist/8569.js +2 -0
- package/dist/8569.js.LICENSE.txt +41 -0
- package/dist/8569.js.map +1 -0
- package/dist/8600.js +1 -0
- package/dist/8600.js.map +1 -0
- package/dist/8618.js +1 -0
- package/dist/8885.js +1 -0
- package/dist/8885.js.map +1 -0
- package/dist/890.js +1 -0
- package/dist/9214.js +1 -0
- package/dist/9538.js +1 -0
- package/dist/9569.js +1 -0
- package/dist/961.js +2 -0
- package/dist/961.js.LICENSE.txt +19 -0
- package/dist/961.js.map +1 -0
- package/dist/963.js +1 -0
- package/dist/963.js.map +1 -0
- package/dist/986.js +1 -0
- package/dist/9879.js +1 -0
- package/dist/9895.js +1 -0
- package/dist/9900.js +1 -0
- package/dist/9913.js +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.LICENSE.txt +51 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-dispensing-app.js +1 -0
- package/dist/openmrs-esm-dispensing-app.js.buildmanifest.json +1645 -0
- package/dist/openmrs-esm-dispensing-app.js.map +1 -0
- package/dist/routes.json +1 -0
- package/e2e/README.md +119 -0
- package/e2e/commands/drug-order-operations.ts +43 -0
- package/e2e/commands/encounter-operations.ts +60 -0
- package/e2e/commands/index.ts +5 -0
- package/e2e/commands/patient-operations.ts +109 -0
- package/e2e/commands/provider-operations.ts +9 -0
- package/e2e/commands/types/index.ts +157 -0
- package/e2e/commands/visit-operations.ts +38 -0
- package/e2e/core/global-setup.ts +32 -0
- package/e2e/core/index.ts +1 -0
- package/e2e/core/test.ts +31 -0
- package/e2e/fixtures/api.ts +27 -0
- package/e2e/fixtures/index.ts +1 -0
- package/e2e/pages/dispensing-page.ts +9 -0
- package/e2e/pages/index.ts +1 -0
- package/e2e/specs/active-prescriptions.spec.ts +72 -0
- package/e2e/specs/close-prescription.spec.ts +71 -0
- package/e2e/specs/dispense-medication.spec.ts +71 -0
- package/e2e/specs/pause-prescription.spec.ts +72 -0
- package/e2e/support/github/Dockerfile +34 -0
- package/e2e/support/github/docker-compose.yml +24 -0
- package/e2e/support/github/run-e2e-docker-env.sh +42 -0
- package/e2e/types/index.ts +157 -0
- package/example.env +7 -0
- package/jest.config.js +24 -0
- package/package.json +110 -0
- package/playwright.config.ts +36 -0
- package/prettier.config.js +8 -0
- package/src/components/action-buttons.component.tsx +87 -0
- package/src/components/action-buttons.scss +16 -0
- package/src/components/action-buttons.test.tsx +217 -0
- package/src/components/medication-card.component.tsx +37 -0
- package/src/components/medication-card.scss +20 -0
- package/src/components/medication-card.test.tsx +36 -0
- package/src/components/medication-dispense-review.scss +108 -0
- package/src/components/medication-event.component.tsx +96 -0
- package/src/components/medication-event.scss +44 -0
- package/src/components/medication-event.test.tsx +212 -0
- package/src/components/prescription-actions/close-action-button.component.tsx +50 -0
- package/src/components/prescription-actions/dispense-action-button.component.tsx +57 -0
- package/src/components/prescription-actions/pause-action-button.component.tsx +49 -0
- package/src/conditions/conditions.component.tsx +118 -0
- package/src/conditions/conditions.resource.ts +100 -0
- package/src/conditions/conditions.scss +26 -0
- package/src/conditions/conditions.test.tsx +200 -0
- package/src/config-schema.ts +192 -0
- package/src/constants.ts +22 -0
- package/src/dashboard/dispensing-dashboard-link.component.tsx +36 -0
- package/src/dashboard/dispensing-dashboard.component.tsx +36 -0
- package/src/declarations.d.ts +2 -0
- package/src/diagnoses/diagnoses.component.tsx +111 -0
- package/src/diagnoses/diagnoses.resource.ts +30 -0
- package/src/diagnoses/diagnoses.scss +31 -0
- package/src/dispensing-link.component.tsx +9 -0
- package/src/dispensing-tiles/dispensing-tile.component.tsx +42 -0
- package/src/dispensing-tiles/dispensing-tile.scss +43 -0
- package/src/dispensing-tiles/dispensing-tiles.component.tsx +39 -0
- package/src/dispensing-tiles/dispensing-tiles.resource.tsx +30 -0
- package/src/dispensing-tiles/dispensing-tiles.scss +11 -0
- package/src/dispensing.component.tsx +31 -0
- package/src/dispensing.scss +5 -0
- package/src/dispensing.test.tsx +9 -0
- package/src/fill-prescription/fill-prescription-button.component.tsx +103 -0
- package/src/fill-prescription/fill-prescription-button.scss +8 -0
- package/src/fill-prescription/on-prescription-filled.modal.tsx +140 -0
- package/src/fill-prescription/on-prescription-filled.scss +7 -0
- package/src/forms/close-dispense-form.workspace.tsx +194 -0
- package/src/forms/dispense-form.workspace.test.tsx +334 -0
- package/src/forms/dispense-form.workspace.tsx +324 -0
- package/src/forms/forms.scss +152 -0
- package/src/forms/medication-dispense-review.component.tsx +649 -0
- package/src/forms/medication-dispense-review.test.tsx +158 -0
- package/src/forms/pause-dispense-form.workspace.tsx +196 -0
- package/src/forms/stock-dispense/stock-dispense.component.tsx +126 -0
- package/src/forms/stock-dispense/stock.resource.tsx +67 -0
- package/src/history/delete-confirm.modal.tsx +35 -0
- package/src/history/history-and-comments.component.tsx +338 -0
- package/src/history/history-and-comments.scss +54 -0
- package/src/index.ts +57 -0
- package/src/location/location.resource.test.tsx +108 -0
- package/src/location/location.resource.tsx +32 -0
- package/src/medication/medication.resource.test.tsx +156 -0
- package/src/medication/medication.resource.tsx +45 -0
- package/src/medication-dispense/medication-dispense.resource.test.tsx +243 -0
- package/src/medication-dispense/medication-dispense.resource.tsx +178 -0
- package/src/medication-request/medication-request.resource.test.tsx +1333 -0
- package/src/medication-request/medication-request.resource.tsx +257 -0
- package/src/patient/patient-info-cell.component.tsx +26 -0
- package/src/patient/patient.resources.ts +14 -0
- package/src/pharmacy-header/pharmacy-header.component.tsx +35 -0
- package/src/pharmacy-header/pharmacy-header.scss +55 -0
- package/src/pharmacy-header/pharmacy-illustration.component.tsx +30 -0
- package/src/prescriptions/patient-search-tab-panel.component.tsx +58 -0
- package/src/prescriptions/patient-search-tab-panel.scss +26 -0
- package/src/prescriptions/prescription-actions.component.tsx +24 -0
- package/src/prescriptions/prescription-actions.scss +14 -0
- package/src/prescriptions/prescription-details.component.tsx +152 -0
- package/src/prescriptions/prescription-details.scss +87 -0
- package/src/prescriptions/prescription-details.test.tsx +267 -0
- package/src/prescriptions/prescription-expanded.component.tsx +58 -0
- package/src/prescriptions/prescription-expanded.scss +56 -0
- package/src/prescriptions/prescription-tab-lists.component.tsx +70 -0
- package/src/prescriptions/prescription-tab-panel.component.tsx +83 -0
- package/src/prescriptions/prescriptions-table.component.tsx +189 -0
- package/src/prescriptions/prescriptions.scss +152 -0
- package/src/print-prescription/prescription-print-action.component.tsx +30 -0
- package/src/print-prescription/prescription-print-preview.modal.tsx +92 -0
- package/src/print-prescription/prescription-printout.component.tsx +154 -0
- package/src/print-prescription/print-prescription.scss +75 -0
- package/src/print-prescription/printable-prescriptions.component.tsx +57 -0
- package/src/routes.json +137 -0
- package/src/types.ts +530 -0
- package/src/utils.test.ts +2947 -0
- package/src/utils.ts +637 -0
- package/tools/i18next-parser.config.js +89 -0
- package/tools/setup-tests.ts +8 -0
- package/tools/update-openmrs-deps.mjs +42 -0
- package/translations/am.json +133 -0
- package/translations/ar.json +133 -0
- package/translations/ar_SY.json +133 -0
- package/translations/bn.json +133 -0
- package/translations/cs.json +133 -0
- package/translations/de.json +133 -0
- package/translations/en.json +133 -0
- package/translations/en_US.json +133 -0
- package/translations/es.json +133 -0
- package/translations/es_MX.json +133 -0
- package/translations/fr.json +133 -0
- package/translations/he.json +133 -0
- package/translations/hi.json +133 -0
- package/translations/hi_IN.json +133 -0
- package/translations/id.json +133 -0
- package/translations/it.json +133 -0
- package/translations/ka.json +133 -0
- package/translations/km.json +133 -0
- package/translations/ku.json +133 -0
- package/translations/ky.json +133 -0
- package/translations/lg.json +133 -0
- package/translations/ne.json +133 -0
- package/translations/pl.json +133 -0
- package/translations/pt.json +133 -0
- package/translations/pt_BR.json +133 -0
- package/translations/qu.json +133 -0
- package/translations/ro_RO.json +133 -0
- package/translations/ru_RU.json +133 -0
- package/translations/si.json +133 -0
- package/translations/sq.json +133 -0
- package/translations/sw.json +133 -0
- package/translations/sw_KE.json +133 -0
- package/translations/tr.json +133 -0
- package/translations/tr_TR.json +133 -0
- package/translations/uk.json +133 -0
- package/translations/uz.json +133 -0
- package/translations/uz@Latn.json +133 -0
- package/translations/uz_UZ.json +133 -0
- package/translations/vi.json +133 -0
- package/translations/zh.json +133 -0
- package/translations/zh_CN.json +133 -0
- package/translations/zh_TW.json +133 -0
- package/tsconfig.json +23 -0
- package/turbo.json +41 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
+
import { type APIRequestContext, type PlaywrightWorkerArgs, type WorkerFixture } from '@playwright/test';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A fixture which initializes an [`APIRequestContext`](https://playwright.dev/docs/api/class-apirequestcontext)
|
|
6
|
+
* that is bound to the configured OpenMRS API server. The context is automatically authenticated
|
|
7
|
+
* using the configured admin account.
|
|
8
|
+
*
|
|
9
|
+
* Use the request context like this:
|
|
10
|
+
* ```ts
|
|
11
|
+
* test('your test', async ({ api }) => {
|
|
12
|
+
* const res = await api.get('patient/1234');
|
|
13
|
+
* await expect(res.ok()).toBeTruthy();
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export const api: WorkerFixture<APIRequestContext, PlaywrightWorkerArgs> = async ({ playwright }, use) => {
|
|
18
|
+
const ctx = await playwright.request.newContext({
|
|
19
|
+
baseURL: `${process.env.E2E_BASE_URL}/ws/rest/v1/`,
|
|
20
|
+
httpCredentials: {
|
|
21
|
+
username: process.env.E2E_USER_ADMIN_USERNAME,
|
|
22
|
+
password: process.env.E2E_USER_ADMIN_PASSWORD,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
use(ctx);
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './api';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './dispensing-page';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import { type Order, type Visit } from '@openmrs/esm-framework';
|
|
3
|
+
import {
|
|
4
|
+
generateRandomDrugOrder,
|
|
5
|
+
deleteDrugOrder,
|
|
6
|
+
createEncounter,
|
|
7
|
+
deleteEncounter,
|
|
8
|
+
getProvider,
|
|
9
|
+
startVisit,
|
|
10
|
+
endVisit,
|
|
11
|
+
} from '../commands';
|
|
12
|
+
import { type Encounter, type Provider } from '../commands/types';
|
|
13
|
+
import { test } from '../core';
|
|
14
|
+
import { DispensingPage } from '../pages';
|
|
15
|
+
|
|
16
|
+
let visit: Visit;
|
|
17
|
+
let drugOrder: Order;
|
|
18
|
+
let encounter: Encounter;
|
|
19
|
+
let orderer: Provider;
|
|
20
|
+
|
|
21
|
+
test.beforeEach(async ({ api, patient }) => {
|
|
22
|
+
visit = await startVisit(api, patient.uuid);
|
|
23
|
+
orderer = await getProvider(api);
|
|
24
|
+
encounter = await createEncounter(api, patient.uuid, orderer.uuid, visit);
|
|
25
|
+
drugOrder = await generateRandomDrugOrder(api, patient.uuid, encounter, orderer.uuid);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('View active prescriptions', async ({ page, patient }) => {
|
|
29
|
+
const dispensingPage = new DispensingPage(page);
|
|
30
|
+
|
|
31
|
+
await test.step('When I navigate to the dispensing page', async () => {
|
|
32
|
+
await dispensingPage.goTo();
|
|
33
|
+
await expect(page).toHaveURL(`${process.env.E2E_BASE_URL}/spa/dispensing`);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await test.step('And I click on the Active prescriptions tab', async () => {
|
|
37
|
+
await page.getByRole('tab', { name: 'Active prescriptions' }).click();
|
|
38
|
+
await expect(page.getByRole('tab', { name: 'Active prescriptions' })).toHaveAttribute('aria-selected', 'true');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await test.step('Then the prescriptions table should display with correct column headers', async () => {
|
|
42
|
+
await expect(page.getByRole('columnheader', { name: 'Created' })).toBeVisible();
|
|
43
|
+
await expect(page.getByRole('columnheader', { name: 'Patient name' })).toBeVisible();
|
|
44
|
+
await expect(page.getByRole('columnheader', { name: 'Prescriber' })).toBeVisible();
|
|
45
|
+
await expect(page.getByRole('columnheader', { name: 'Drugs' })).toBeVisible();
|
|
46
|
+
await expect(page.getByRole('columnheader', { name: 'Last dispenser' })).toBeVisible();
|
|
47
|
+
await expect(page.getByRole('columnheader', { name: 'Status' })).toBeVisible();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await test.step('And the table should contain the test prescription', async () => {
|
|
51
|
+
const patientName = `${patient.person.display}`;
|
|
52
|
+
const patientRow = page.getByRole('row', { name: new RegExp(patientName) });
|
|
53
|
+
await expect(patientRow.getByRole('cell', { name: patientName })).toBeVisible();
|
|
54
|
+
await expect(patientRow.getByRole('cell', { name: 'Aspirin 81mg' })).toBeVisible();
|
|
55
|
+
await expect(patientRow.getByRole('cell', { name: 'Active' })).toBeVisible();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await test.step('And I should be able to expand the prescription row to see details', async () => {
|
|
59
|
+
await page
|
|
60
|
+
.getByRole('row', { name: /Expand current row/ })
|
|
61
|
+
.getByLabel('Expand current row')
|
|
62
|
+
.nth(0)
|
|
63
|
+
.click();
|
|
64
|
+
await expect(page.getByLabel('Prescription details', { exact: true })).toBeVisible();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test.afterEach(async ({ api }) => {
|
|
69
|
+
await deleteEncounter(api, encounter.uuid);
|
|
70
|
+
await deleteDrugOrder(api, drugOrder.uuid);
|
|
71
|
+
await endVisit(api, visit);
|
|
72
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import { type Order, type Visit } from '@openmrs/esm-framework';
|
|
3
|
+
import {
|
|
4
|
+
generateRandomDrugOrder,
|
|
5
|
+
deleteDrugOrder,
|
|
6
|
+
createEncounter,
|
|
7
|
+
deleteEncounter,
|
|
8
|
+
getProvider,
|
|
9
|
+
startVisit,
|
|
10
|
+
endVisit,
|
|
11
|
+
} from '../commands';
|
|
12
|
+
import { type Encounter, type Provider } from '../commands/types';
|
|
13
|
+
import { test } from '../core';
|
|
14
|
+
import { DispensingPage } from '../pages';
|
|
15
|
+
|
|
16
|
+
let visit: Visit;
|
|
17
|
+
let drugOrder: Order;
|
|
18
|
+
let encounter: Encounter;
|
|
19
|
+
let orderer: Provider;
|
|
20
|
+
|
|
21
|
+
test.beforeEach(async ({ api, patient }) => {
|
|
22
|
+
visit = await startVisit(api, patient.uuid);
|
|
23
|
+
orderer = await getProvider(api);
|
|
24
|
+
encounter = await createEncounter(api, patient.uuid, orderer.uuid, visit);
|
|
25
|
+
drugOrder = await generateRandomDrugOrder(api, patient.uuid, encounter, orderer.uuid);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('Close prescription', async ({ page, patient }) => {
|
|
29
|
+
const dispensingPage = new DispensingPage(page);
|
|
30
|
+
await test.step('When I navigate to the dispensing app', async () => {
|
|
31
|
+
await dispensingPage.goTo();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await test.step('And I click on the Active prescriptions tab', async () => {
|
|
35
|
+
await page.getByRole('tab', { name: 'Active prescriptions' }).click();
|
|
36
|
+
await expect(page.getByRole('tab', { name: 'Active prescriptions' })).toHaveAttribute('aria-selected', 'true');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await test.step('And I expand a table row in the prescriptions table corresponding to an active prescription', async () => {
|
|
40
|
+
const rowText = new RegExp(`Expand current row`);
|
|
41
|
+
await page.getByRole('row', { name: rowText }).getByLabel('Expand current row').nth(0).click();
|
|
42
|
+
await expect(page.getByLabel('Prescription details', { exact: true }).getByText('Aspirin 81mg')).toBeVisible();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await test.step('Then I click the Close button on the prescription tile', async () => {
|
|
46
|
+
await page.getByRole('button', { name: 'danger Close' }).click();
|
|
47
|
+
await expect(page.getByText('Close prescription')).toBeVisible();
|
|
48
|
+
await expect(page.getByText('Reason for close')).toBeVisible();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await test.step('And when I select Allergy as the reason for closing and submit the form', async () => {
|
|
52
|
+
await page.waitForLoadState('networkidle');
|
|
53
|
+
const openDropdownButton = page.getByRole('button', { name: 'Open', exact: true });
|
|
54
|
+
await openDropdownButton.scrollIntoViewIfNeeded();
|
|
55
|
+
await openDropdownButton.click();
|
|
56
|
+
await page.getByText('Allergy', { exact: true }).click();
|
|
57
|
+
const closeButton = page.locator('form').getByRole('button', { name: 'Close' });
|
|
58
|
+
await closeButton.scrollIntoViewIfNeeded();
|
|
59
|
+
await closeButton.click();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await test.step('Then I should see a success notification', async () => {
|
|
63
|
+
await expect(page.getByText(/medication dispense closed/i)).toBeVisible();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test.afterEach(async ({ api }) => {
|
|
68
|
+
await deleteEncounter(api, encounter.uuid);
|
|
69
|
+
await deleteDrugOrder(api, drugOrder.uuid);
|
|
70
|
+
await endVisit(api, visit);
|
|
71
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import { type Order, type Visit } from '@openmrs/esm-framework';
|
|
3
|
+
import {
|
|
4
|
+
generateRandomDrugOrder,
|
|
5
|
+
deleteDrugOrder,
|
|
6
|
+
createEncounter,
|
|
7
|
+
deleteEncounter,
|
|
8
|
+
getProvider,
|
|
9
|
+
startVisit,
|
|
10
|
+
endVisit,
|
|
11
|
+
} from '../commands';
|
|
12
|
+
import { type Encounter, type Provider } from '../commands/types';
|
|
13
|
+
import { test } from '../core';
|
|
14
|
+
import { DispensingPage } from '../pages';
|
|
15
|
+
|
|
16
|
+
let visit: Visit;
|
|
17
|
+
let drugOrder: Order;
|
|
18
|
+
let encounter: Encounter;
|
|
19
|
+
let orderer: Provider;
|
|
20
|
+
|
|
21
|
+
test.beforeEach(async ({ api, patient }) => {
|
|
22
|
+
visit = await startVisit(api, patient.uuid);
|
|
23
|
+
orderer = await getProvider(api);
|
|
24
|
+
encounter = await createEncounter(api, patient.uuid, orderer.uuid, visit);
|
|
25
|
+
drugOrder = await generateRandomDrugOrder(api, patient.uuid, encounter, orderer.uuid);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('Dispense prescription', async ({ page, patient }) => {
|
|
29
|
+
const dispensingPage = new DispensingPage(page);
|
|
30
|
+
await test.step('When I navigate to the dispensing app', async () => {
|
|
31
|
+
await dispensingPage.goTo();
|
|
32
|
+
await expect(page).toHaveURL(process.env.E2E_BASE_URL + `/spa/dispensing`);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await test.step('And I click on the Active prescriptions tab', async () => {
|
|
36
|
+
await page.getByRole('tab', { name: 'Active prescriptions' }).click();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await test.step('And I expand a table row in the Prescriptions table corresponding to an active prescription', async () => {
|
|
40
|
+
const rowText = new RegExp(`Expand current row`);
|
|
41
|
+
await page.getByRole('row', { name: rowText }).getByLabel('Expand current row').nth(0).click();
|
|
42
|
+
await expect(page.getByLabel('Prescription details', { exact: true }).getByText('Aspirin 81mg')).toBeVisible();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await test.step('Then I click the Dispense button to launch the Dispense prescription form', async () => {
|
|
46
|
+
await page.getByRole('button', { name: 'Dispense', exact: true }).click();
|
|
47
|
+
await expect(page.getByLabel('Workspace header').getByText('Dispense prescription')).toBeVisible();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await test.step('Then I submit the form by clicking the Dispense prescription button', async () => {
|
|
51
|
+
await page.waitForLoadState('networkidle');
|
|
52
|
+
const dispenseButton = page.getByRole('button', { name: 'Dispense prescription' });
|
|
53
|
+
await dispenseButton.scrollIntoViewIfNeeded();
|
|
54
|
+
await dispenseButton.click();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await test.step('Then I should see a success notification', async () => {
|
|
58
|
+
await expect(page.getByText(/medication successfully dispensed/i)).toBeVisible();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await test.step('And when I click the `History and comments` tab I should see the updated `Dispensed` status reflected', async () => {
|
|
62
|
+
await page.getByRole('tab', { name: 'History and comments' }).click();
|
|
63
|
+
await expect(page.getByText('Dispensed', { exact: true })).toBeVisible();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test.afterEach(async ({ api }) => {
|
|
68
|
+
await deleteEncounter(api, encounter.uuid);
|
|
69
|
+
await deleteDrugOrder(api, drugOrder.uuid);
|
|
70
|
+
await endVisit(api, visit);
|
|
71
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import { type Order, type Visit } from '@openmrs/esm-framework';
|
|
3
|
+
import {
|
|
4
|
+
generateRandomDrugOrder,
|
|
5
|
+
deleteDrugOrder,
|
|
6
|
+
createEncounter,
|
|
7
|
+
deleteEncounter,
|
|
8
|
+
getProvider,
|
|
9
|
+
startVisit,
|
|
10
|
+
endVisit,
|
|
11
|
+
} from '../commands';
|
|
12
|
+
import { type Encounter, type Provider } from '../commands/types';
|
|
13
|
+
import { test } from '../core';
|
|
14
|
+
import { DispensingPage } from '../pages';
|
|
15
|
+
|
|
16
|
+
let visit: Visit;
|
|
17
|
+
let drugOrder: Order;
|
|
18
|
+
let encounter: Encounter;
|
|
19
|
+
let orderer: Provider;
|
|
20
|
+
|
|
21
|
+
test.beforeEach(async ({ api, patient }) => {
|
|
22
|
+
visit = await startVisit(api, patient.uuid);
|
|
23
|
+
orderer = await getProvider(api);
|
|
24
|
+
encounter = await createEncounter(api, patient.uuid, orderer.uuid, visit);
|
|
25
|
+
drugOrder = await generateRandomDrugOrder(api, patient.uuid, encounter, orderer.uuid);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('Pause prescription', async ({ page, patient }) => {
|
|
29
|
+
const dispensingPage = new DispensingPage(page);
|
|
30
|
+
|
|
31
|
+
await test.step('When I navigate to the dispensing app', async () => {
|
|
32
|
+
await dispensingPage.goTo();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await test.step('And I click on the Active prescriptions tab', async () => {
|
|
36
|
+
await page.getByRole('tab', { name: 'Active prescriptions' }).click();
|
|
37
|
+
await expect(page.getByRole('tab', { name: 'Active prescriptions' })).toHaveAttribute('aria-selected', 'true');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await test.step('And I expand a table row in the prescriptions table corresponding to an active prescription', async () => {
|
|
41
|
+
const rowText = new RegExp(`Expand current row`);
|
|
42
|
+
await page.getByRole('row', { name: rowText }).getByLabel('Expand current row').nth(0).click();
|
|
43
|
+
await expect(page.getByLabel('Prescription details', { exact: true }).getByText('Aspirin 81mg')).toBeVisible();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await test.step('Then I click the Pause button on the prescription tile', async () => {
|
|
47
|
+
await page.getByRole('button', { name: 'Pause' }).click();
|
|
48
|
+
await expect(page.getByText(/pause prescription/i)).toBeVisible();
|
|
49
|
+
await expect(page.getByText(/reason for pause/i)).toBeVisible();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await test.step('And I select "Allergy" as the reason for pausing the prescription and then submit the form', async () => {
|
|
53
|
+
await page.waitForLoadState('networkidle');
|
|
54
|
+
const openDropdownButton = page.getByRole('button', { name: 'Open', exact: true });
|
|
55
|
+
await openDropdownButton.scrollIntoViewIfNeeded();
|
|
56
|
+
await openDropdownButton.click();
|
|
57
|
+
await page.getByText('Allergy', { exact: true }).click();
|
|
58
|
+
const pauseButton = page.locator('form').getByRole('button', { name: 'Pause' });
|
|
59
|
+
await pauseButton.scrollIntoViewIfNeeded();
|
|
60
|
+
await pauseButton.click();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await test.step('Then I should see a success notification', async () => {
|
|
64
|
+
await expect(page.getByText(/medication dispense paused./i)).toBeVisible();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test.afterEach(async ({ api }) => {
|
|
69
|
+
await deleteEncounter(api, encounter.uuid);
|
|
70
|
+
await deleteDrugOrder(api, drugOrder.uuid);
|
|
71
|
+
await endVisit(api, visit);
|
|
72
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.3
|
|
2
|
+
FROM --platform=$BUILDPLATFORM node:22-alpine as dev
|
|
3
|
+
|
|
4
|
+
ARG APP_SHELL_VERSION=next
|
|
5
|
+
|
|
6
|
+
RUN mkdir -p /app
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
|
|
9
|
+
COPY . .
|
|
10
|
+
|
|
11
|
+
RUN npm_config_legacy_peer_deps=true npm install -g openmrs@${APP_SHELL_VERSION:-next}
|
|
12
|
+
ARG CACHE_BUST
|
|
13
|
+
RUN npm_config_legacy_peer_deps=true openmrs assemble --manifest --mode config --config spa-assemble-config.json --target ./spa
|
|
14
|
+
|
|
15
|
+
FROM --platform=$BUILDPLATFORM openmrs/openmrs-reference-application-3-frontend:nightly as frontend
|
|
16
|
+
FROM nginx:1.23-alpine
|
|
17
|
+
|
|
18
|
+
RUN apk update && \
|
|
19
|
+
apk upgrade && \
|
|
20
|
+
# add more utils for sponge to support our startup script
|
|
21
|
+
apk add --no-cache moreutils
|
|
22
|
+
|
|
23
|
+
# clear any default files installed by nginx
|
|
24
|
+
RUN rm -rf /usr/share/nginx/html/*
|
|
25
|
+
|
|
26
|
+
COPY --from=frontend /etc/nginx/nginx.conf /etc/nginx/nginx.conf
|
|
27
|
+
# this assumes that NOTHING in the framework is in a subdirectory
|
|
28
|
+
COPY --from=frontend /usr/share/nginx/html/* /usr/share/nginx/html/
|
|
29
|
+
COPY --from=frontend /usr/local/bin/startup.sh /usr/local/bin/startup.sh
|
|
30
|
+
RUN chmod +x /usr/local/bin/startup.sh
|
|
31
|
+
|
|
32
|
+
COPY --from=dev /app/spa/ /usr/share/nginx/html/
|
|
33
|
+
|
|
34
|
+
CMD ["/usr/local/bin/startup.sh"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# This docker compose file is used to create a backend environment for the e2e.yml workflow.
|
|
2
|
+
# The images are pre-filled with data so that the backend environment can be started within a short time.
|
|
3
|
+
version: "3.7"
|
|
4
|
+
|
|
5
|
+
services:
|
|
6
|
+
gateway:
|
|
7
|
+
image: openmrs/openmrs-reference-application-3-gateway:${TAG:-nightly}
|
|
8
|
+
ports:
|
|
9
|
+
- "8080:80"
|
|
10
|
+
|
|
11
|
+
frontend:
|
|
12
|
+
build:
|
|
13
|
+
context: .
|
|
14
|
+
environment:
|
|
15
|
+
SPA_PATH: /openmrs/spa
|
|
16
|
+
API_URL: /openmrs
|
|
17
|
+
|
|
18
|
+
backend:
|
|
19
|
+
image: openmrs/openmrs-reference-application-3-backend:nightly-with-data
|
|
20
|
+
depends_on:
|
|
21
|
+
- db
|
|
22
|
+
|
|
23
|
+
db:
|
|
24
|
+
image: openmrs/openmrs-reference-application-3-db:nightly-with-data
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bash -eu
|
|
2
|
+
|
|
3
|
+
# get the dir containing the script
|
|
4
|
+
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|
5
|
+
# create a temporary working directory
|
|
6
|
+
working_dir=$(mktemp -d "${TMPDIR:-/tmp/}openmrs-e2e-frontends.XXXXXXXXXX")
|
|
7
|
+
# cleanup temp directory on exit
|
|
8
|
+
trap 'rm -rf "$working_dir"' EXIT
|
|
9
|
+
# get the app name
|
|
10
|
+
app_name=$(jq -r '.name' "$script_dir/../../../package.json")
|
|
11
|
+
|
|
12
|
+
echo "Creating packed archive of the app for Docker build..."
|
|
13
|
+
# @openmrs/esm-whatever -> _openmrs_esm_whatever
|
|
14
|
+
packed_app_name=$(echo "$app_name" | tr '[:punct:]' '_');
|
|
15
|
+
# run yarn pack for our app and add it to the working directory
|
|
16
|
+
yarn pack -o "$working_dir/$packed_app_name.tgz" >/dev/null;
|
|
17
|
+
echo "Successfully created packed app archive: $packed_app_name.tgz"
|
|
18
|
+
|
|
19
|
+
echo "Creating dynamic spa-assemble-config.json with frontend modules..."
|
|
20
|
+
# dynamically assemble our list of frontend modules, prepending the primary navigation
|
|
21
|
+
# and patient banner apps; apps will all be in the /app directory of the Docker
|
|
22
|
+
# container
|
|
23
|
+
jq -n \
|
|
24
|
+
--arg app_name "$app_name" \
|
|
25
|
+
--arg app_file "/app/$packed_app_name.tgz" \
|
|
26
|
+
'{
|
|
27
|
+
"@openmrs/esm-primary-navigation-app": "next",
|
|
28
|
+
"@openmrs/esm-patient-banner-app": "next"
|
|
29
|
+
} + {
|
|
30
|
+
($app_name): $app_file
|
|
31
|
+
}' | jq '{"frontendModules": .}' > "$working_dir/spa-assemble-config.json"
|
|
32
|
+
echo "Successfully created spa-assemble-config.json with frontend modules"
|
|
33
|
+
|
|
34
|
+
echo "Copying Docker configuration files to working directory..."
|
|
35
|
+
cp "$script_dir/Dockerfile" "$working_dir/Dockerfile"
|
|
36
|
+
cp "$script_dir/docker-compose.yml" "$working_dir/docker-compose.yml"
|
|
37
|
+
|
|
38
|
+
cd "$working_dir"
|
|
39
|
+
echo "Building and starting Docker containers for e2e testing..."
|
|
40
|
+
# CACHE_BUST to ensure the assemble step is always run
|
|
41
|
+
docker compose build --build-arg CACHE_BUST=$(date +%s) frontend
|
|
42
|
+
docker compose up -d
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { type OpenmrsResource } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
export interface Encounter {
|
|
4
|
+
uuid: string;
|
|
5
|
+
encounterDateTime: string;
|
|
6
|
+
encounterProviders: Array<{
|
|
7
|
+
uuid: string;
|
|
8
|
+
display: string;
|
|
9
|
+
encounterRole: {
|
|
10
|
+
uuid: string;
|
|
11
|
+
display: string;
|
|
12
|
+
};
|
|
13
|
+
provider: {
|
|
14
|
+
uuid: string;
|
|
15
|
+
person: {
|
|
16
|
+
uuid: string;
|
|
17
|
+
display: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
21
|
+
encounterType: {
|
|
22
|
+
uuid: string;
|
|
23
|
+
display: string;
|
|
24
|
+
};
|
|
25
|
+
obs: Array<Observation>;
|
|
26
|
+
orders: Array<Order>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Observation {
|
|
30
|
+
uuid: string;
|
|
31
|
+
concept: {
|
|
32
|
+
uuid: string;
|
|
33
|
+
display: string;
|
|
34
|
+
conceptClass: {
|
|
35
|
+
uuid: string;
|
|
36
|
+
display: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
display: string;
|
|
40
|
+
groupMembers: null | Array<{
|
|
41
|
+
uuid: string;
|
|
42
|
+
concept: {
|
|
43
|
+
uuid: string;
|
|
44
|
+
display: string;
|
|
45
|
+
};
|
|
46
|
+
value: {
|
|
47
|
+
uuid: string;
|
|
48
|
+
display: string;
|
|
49
|
+
};
|
|
50
|
+
}>;
|
|
51
|
+
value: any;
|
|
52
|
+
obsDatetime: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface Order {
|
|
56
|
+
uuid: string;
|
|
57
|
+
dateActivated: string;
|
|
58
|
+
dateStopped?: Date | null;
|
|
59
|
+
dose: number;
|
|
60
|
+
dosingInstructions: string | null;
|
|
61
|
+
dosingType?: 'org.openmrs.FreeTextDosingInstructions' | 'org.openmrs.SimpleDosingInstructions';
|
|
62
|
+
doseUnits: {
|
|
63
|
+
uuid: string;
|
|
64
|
+
display: string;
|
|
65
|
+
};
|
|
66
|
+
drug: {
|
|
67
|
+
uuid: string;
|
|
68
|
+
name: string;
|
|
69
|
+
strength: string;
|
|
70
|
+
display: string;
|
|
71
|
+
};
|
|
72
|
+
duration: number;
|
|
73
|
+
durationUnits: {
|
|
74
|
+
uuid: string;
|
|
75
|
+
display: string;
|
|
76
|
+
};
|
|
77
|
+
frequency: {
|
|
78
|
+
uuid: string;
|
|
79
|
+
display: string;
|
|
80
|
+
};
|
|
81
|
+
numRefills: number;
|
|
82
|
+
orderNumber: string;
|
|
83
|
+
orderReason: string | null;
|
|
84
|
+
orderReasonNonCoded: string | null;
|
|
85
|
+
orderer: {
|
|
86
|
+
uuid: string;
|
|
87
|
+
person: {
|
|
88
|
+
uuid: string;
|
|
89
|
+
display: string;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
orderType: {
|
|
93
|
+
uuid: string;
|
|
94
|
+
display: string;
|
|
95
|
+
};
|
|
96
|
+
route: {
|
|
97
|
+
uuid: string;
|
|
98
|
+
display: string;
|
|
99
|
+
};
|
|
100
|
+
quantity: number;
|
|
101
|
+
quantityUnits: OpenmrsResource;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface Provider {
|
|
105
|
+
uuid: string;
|
|
106
|
+
display: string;
|
|
107
|
+
comments?: string;
|
|
108
|
+
response?: string;
|
|
109
|
+
person: OpenmrsResource;
|
|
110
|
+
name?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface Patient {
|
|
114
|
+
uuid: string;
|
|
115
|
+
identifiers: Identifier[];
|
|
116
|
+
display: string;
|
|
117
|
+
person: {
|
|
118
|
+
uuid: string;
|
|
119
|
+
display: string;
|
|
120
|
+
gender: string;
|
|
121
|
+
age: number;
|
|
122
|
+
birthdate: string;
|
|
123
|
+
birthdateEstimated: boolean;
|
|
124
|
+
dead: boolean;
|
|
125
|
+
deathDate?: any;
|
|
126
|
+
causeOfDeath?: any;
|
|
127
|
+
preferredAddress: {
|
|
128
|
+
address1: string;
|
|
129
|
+
cityVillage: string;
|
|
130
|
+
country: string;
|
|
131
|
+
postalCode: string;
|
|
132
|
+
stateProvince: string;
|
|
133
|
+
countyDistrict: string;
|
|
134
|
+
};
|
|
135
|
+
attributes: any[];
|
|
136
|
+
voided: boolean;
|
|
137
|
+
birthtime?: any;
|
|
138
|
+
deathdateEstimated: boolean;
|
|
139
|
+
resourceVersion: string;
|
|
140
|
+
};
|
|
141
|
+
attributes: { value: string; attributeType: { uuid: string; display: string } }[];
|
|
142
|
+
voided: boolean;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface Address {
|
|
146
|
+
preferred: boolean;
|
|
147
|
+
address1: string;
|
|
148
|
+
cityVillage: string;
|
|
149
|
+
country: string;
|
|
150
|
+
postalCode: string;
|
|
151
|
+
stateProvince: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface Identifier {
|
|
155
|
+
uuid: string;
|
|
156
|
+
display: string;
|
|
157
|
+
}
|
package/example.env
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# This is an example environment file for configuring dynamic values.
|
|
2
|
+
E2E_BASE_URL=http://localhost:8080/openmrs
|
|
3
|
+
E2E_USER_ADMIN_USERNAME=admin
|
|
4
|
+
E2E_USER_ADMIN_PASSWORD=Admin123
|
|
5
|
+
E2E_LOGIN_DEFAULT_LOCATION_UUID=44c3efb0-2583-4c80-a79e-1f756a03c0a1
|
|
6
|
+
# The above location UUID is for the "Outpatient Clinic" location in the reference application
|
|
7
|
+
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** @type {import('jest').Config} */
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
transform: {
|
|
5
|
+
'^.+\\.[jt]sx?$': ['@swc/jest'],
|
|
6
|
+
},
|
|
7
|
+
transformIgnorePatterns: ['/node_modules/(?!@openmrs|.+\\.pnp\\.[^\\/]+$)'],
|
|
8
|
+
moduleNameMapper: {
|
|
9
|
+
'@openmrs/esm-framework': '@openmrs/esm-framework/mock',
|
|
10
|
+
'\\.(s?css)$': 'identity-obj-proxy',
|
|
11
|
+
'^lodash-es/(.*)$': 'lodash/$1',
|
|
12
|
+
'^lodash-es$': 'lodash',
|
|
13
|
+
'^uuid$': '<rootDir>/node_modules/uuid/dist/index.js',
|
|
14
|
+
dexie: require.resolve('dexie'),
|
|
15
|
+
},
|
|
16
|
+
collectCoverageFrom: ['!**/node_modules/**', '!**/e2e/**'],
|
|
17
|
+
testPathIgnorePatterns: [
|
|
18
|
+
'/node_modules/',
|
|
19
|
+
'/e2e/', // Ignore the e2e directory containing Playwright tests
|
|
20
|
+
],
|
|
21
|
+
setupFilesAfterEnv: ['<rootDir>/tools/setup-tests.ts'],
|
|
22
|
+
testEnvironment: 'jsdom',
|
|
23
|
+
clearMocks: true,
|
|
24
|
+
};
|