diff --git a/.eslintrc b/.eslintrc index 49fb9c21fe..3cf458e060 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/exhaustive-deps": "warn", "react-hooks/rules-of-hooks": "error", diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml index 01a9db3b48..a32bba3037 100644 --- a/.github/workflows/bundle-size.yml +++ b/.github/workflows/bundle-size.yml @@ -23,7 +23,7 @@ jobs: uses: felixmosh/turborepo-gh-artifacts@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - server-token: ${{ env.TURBO_TOKEN }} + server-token: '' - name: Compute bundle size report uses: preactjs/compressed-size-action@v2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79bdc3221d..19981e2487 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: uses: felixmosh/turborepo-gh-artifacts@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - server-token: ${{ env.TURBO_TOKEN }} + server-token: '' - name: Run lint, type checks and tests run: yarn verify @@ -81,7 +81,7 @@ jobs: uses: felixmosh/turborepo-gh-artifacts@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - server-token: ${{ env.TURBO_TOKEN }} + server-token: '' - name: Version run: yarn workspaces foreach --all --topological --exclude @openmrs/esm-patient-chart version "$(node -e "console.log(require('semver').inc(require('./package.json').version, 'patch'))")-pre.${{ github.run_number }}" @@ -151,7 +151,7 @@ jobs: uses: felixmosh/turborepo-gh-artifacts@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - server-token: ${{ env.TURBO_TOKEN }} + server-token: '' - name: Build run: yarn turbo run build --color --concurrency=5 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index f0b2035641..ebc14224e3 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -47,7 +47,7 @@ jobs: uses: felixmosh/turborepo-gh-artifacts@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - server-token: ${{ env.TURBO_TOKEN }} + server-token: '' - name: Build apps run: yarn turbo run build --color --concurrency=5 @@ -75,14 +75,14 @@ jobs: overwrite: true - name: Capture Server Logs - if: '!cancelled()' + if: always() uses: jwalton/gh-docker-logs@v2 with: dest: './logs' - name: Upload Logs as Artifact uses: actions/upload-artifact@v4 - if: '!cancelled()' + if: always() with: name: server-logs path: './logs' diff --git a/.prettierignore b/.prettierignore index f215f78948..67c4b323d9 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,7 +1,5 @@ dist/ node_modules/ -**/*.css -**/*.scss **/*.md **/*.json packages/esm-form-entry-app/src/app/loader/loader.component.html diff --git a/README.md b/README.md index 8d4f01e558..a7c9fd9c00 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ To run end-to-end tests, run: yarn test-e2e ``` -Read the [e2e testing guide](https://o3-docs.openmrs.org/docs/frontend-modules/testing#end-to-end-testing-with-playwright) to learn more about End-to-End tests in this project. +Read the [e2e testing guide](https://o3-docs.openmrs.org/docs/frontend-modules/end-to-end-testing) to learn more about End-to-End tests in this project. ### Updating Playwright diff --git a/__mocks__/mockDeceasedPatient.ts b/__mocks__/mockDeceasedPatient.ts index d9b6eb3efa..3b4363b9de 100644 --- a/__mocks__/mockDeceasedPatient.ts +++ b/__mocks__/mockDeceasedPatient.ts @@ -19,7 +19,7 @@ export const mockDeceasedPatient = { value: '100732HE', }, { - id: '1f0ad7a1-430f-4397-b571-59ea654a52db', + id: 'ceda35a8-a445-455d-9928-ac088692190a', use: 'usual', system: 'OpenMRS ID', value: '100GEJ', diff --git a/__mocks__/patient-flags.mock.ts b/__mocks__/patient-flags.mock.ts index 97e85e3d92..6e2883e739 100644 --- a/__mocks__/patient-flags.mock.ts +++ b/__mocks__/patient-flags.mock.ts @@ -1,20 +1,47 @@ export const mockPatientFlags = [ { - uuid: '8ca6c08f-66d9-4a18-a233-4f658b1755bf', - flag: { display: 'Needs Follow Up' }, + auditInfo: { dateCreated: '2024-03-01T00:00:00.000Z' }, + flag: { display: 'Needs Follow Up', uuid: '98ca6c08f-66d9-4a18-a233-4f658b1755bf' }, message: 'Patient needs to be followed up', - tags: [{ display: 'flag tag - risk' }, { display: 'flag type - Social' }], + patient: { + uuid: '8ca6c08f-66d9-4a18-a233-4f658b1755bf', + display: 'John Doe', + }, + tags: [ + { display: 'flag tag - risk', uuid: 'some-uuid' }, + { display: 'flag type - Social', uuid: 'some-other-uuid' }, + ], + uuid: '8ca6c08f-66d9-4a18-a233-4f658b1755bf', + voided: false, }, { - uuid: '5fs6c08f-66d9-4a18-a233-5f658b1755bf', - flag: { display: 'Unknown Diagnosis' }, + auditInfo: { dateCreated: '2024-03-01T00:00:00.000Z' }, + flag: { display: 'Unknown Diagnosis', uuid: 'a4a4c08f-66d9-4a18-a233-5f658b1755bf' }, message: 'Diagnosis for the patient is unknown', - tags: [{ display: 'flag tag - info' }, { display: 'flag type - Clinical' }], + patient: { + uuid: '8ca6c08f-66d9-4a18-a233-4f658b1755bf', + display: 'John Doe', + }, + tags: [ + { display: 'flag tag - info', uuid: 'another-uuid' }, + { display: 'flag type - Clinical', uuid: 'yet-another-uuid' }, + ], + uuid: '5fs6c08f-66d9-4a18-a233-5f658b1755bf', + voided: false, }, { - uuid: '4da4c08f-66d9-4a18-a233-5f658b1755bf', - flag: { display: 'Future Appointment' }, + auditInfo: { dateCreated: '2024-03-01T00:00:00.000Z' }, + flag: { display: 'Future Appointment', uuid: 'cc6c08f-66d9-4a18-a233-5f658b1755bf' }, message: 'Patient has a future appointment scheduled', - tags: [{ display: 'flag tag - info' }, { display: 'flag type - Clinical' }], + patient: { + uuid: '8ca6c08f-66d9-4a18-a233-4f658b1755bf', + display: 'John Doe', + }, + tags: [ + { display: 'flag tag - info', uuid: 'one-more-uuid' }, + { display: 'flag type - Clinical', uuid: 'yet-one-more-uuid' }, + ], + uuid: '4da4c08f-66d9-4a18-a233-5f658b1755bf', + voided: false, }, ]; diff --git a/__mocks__/programs.mock.ts b/__mocks__/programs.mock.ts index 636df1c07a..1fb5702e5c 100644 --- a/__mocks__/programs.mock.ts +++ b/__mocks__/programs.mock.ts @@ -50,6 +50,10 @@ export const mockEnrolledProgramsResponse = [ uuid: '64f950e6-1b07-4ac0-8e7e-f3e148f3463f', name: 'HIV Care and Treatment', allWorkflows: [], + concept: { + uuid: '70724784-438a-490e-a581-68b7d1f8f47f', + display: 'Human immunodeficiency virus (HIV) disease', + }, }, display: 'HIV Care and Treatment', location: { @@ -61,6 +65,58 @@ export const mockEnrolledProgramsResponse = [ }, ]; +export const mockEnrolledInAllProgramsResponse = [ + { + uuid: '8ba6c08f-66d9-4a18-a233-5f658b1755bf', + program: { + uuid: '64f950e6-1b07-4ac0-8e7e-f3e148f3463f', + name: 'HIV Care and Treatment', + allWorkflows: [], + concept: { + uuid: '70724784-438a-490e-a581-68b7d1f8f47f', + display: 'Human immunodeficiency virus (HIV) disease', + }, + }, + display: 'HIV Care and Treatment', + location: { + uuid: 'aff27d58-a15c-49a6-9beb-d30dcfc0c66e', + display: 'Amani Hospital', + }, + dateEnrolled: '2020-01-16T00:00:00.000+0000', + dateCompleted: null, + }, + { + uuid: '700b7914-9dc9-4569-8fe3-6db6c80af4c5', + program: { + uuid: '11b129ca-a5e7-4025-84bf-b92a173e20de', + name: 'Oncology Screening and Diagnosis', + allWorkflows: [], + }, + display: 'Oncology Screening and Diagnosis', + location: { + uuid: 'aff27d58-a15c-49a6-9beb-d30dcfc0c66e', + display: 'Amani Hospital', + }, + dateEnrolled: '2021-03-16T00:00:00.000+0000', + dateCompleted: null, + }, + { + uuid: '874e5326-faa0-4d4b-a891-9a0e3a16f30f', + program: { + uuid: 'b2f65a51-2f87-4faa-a8c6-327a0c1d2e17', + name: 'HIV Differentiated Care', + allWorkflows: [], + }, + display: 'HIV Differentiated Care', + location: { + uuid: 'aff27d58-a15c-49a6-9beb-d30dcfc0c66e', + display: 'Amani Hospital', + }, + dateEnrolled: '2021-02-16T00:00:00.000+0000', + dateCompleted: null, + }, +]; + export const mockCareProgramsResponse = [ { uuid: '64f950e6-1b07-4ac0-8e7e-f3e148f3463f', diff --git a/__mocks__/session.mock.ts b/__mocks__/session.mock.ts index c5786b172e..6d53aa4d1c 100644 --- a/__mocks__/session.mock.ts +++ b/__mocks__/session.mock.ts @@ -60,6 +60,7 @@ export const mockSessionDataResponse = { address13: null, address14: null, address15: null, + links: [], }, user: { uuid: '45ce6c2e-dd5a-11e6-9d9c-0242ac150002', @@ -77,31 +78,40 @@ export const mockSessionDataResponse = { { uuid: '62431c71-5244-11ea-a771-0242ac160002', display: 'Manage Appointment Services', + links: [], }, { uuid: '6206682c-5244-11ea-a771-0242ac160002', display: 'Manage Appointments', + links: [], }, { uuid: '6280ff58-5244-11ea-a771-0242ac160002', display: 'Manage Appointment Specialities', + links: [], }, ], roles: [ { uuid: '8d94f852-c2cc-11de-8d13-0010c6dffd0f', display: 'System Developer', + links: [], }, { uuid: '62c195c5-5244-11ea-a771-0242ac160002', display: 'Bahmni Role', + links: [], }, { uuid: '8d94f280-c2cc-11de-8d13-0010c6dffd0f', display: 'Provider', + links: [], }, ], retired: false, + locale: 'en_GB', + allowedLocales: ['en_GB'], }, + sessionId: 'D4F7D4D4-6A6D-4D4D-8D4D-4D4D4D4D4D4D', }, }; diff --git a/__mocks__/visits.mock.ts b/__mocks__/visits.mock.ts index 9ccf374130..ebb27b508d 100644 --- a/__mocks__/visits.mock.ts +++ b/__mocks__/visits.mock.ts @@ -51,7 +51,7 @@ export const mockCurrentVisit = { display: 'Facility Visit', }, attributes: [], - startDatetime: new Date('2021-03-16T08:16:00.000+0000'), + startDatetime: new Date('2021-03-16T08:16:00.000+0000').toISOString(), stopDatetime: null, location: { uuid: '6351fcf4-e311-4a19-90f9-35667d99a8af', diff --git a/__mocks__/vitals-and-biometrics.mock.ts b/__mocks__/vitals-and-biometrics.mock.ts index 681b64deff..64bb0b1398 100644 --- a/__mocks__/vitals-and-biometrics.mock.ts +++ b/__mocks__/vitals-and-biometrics.mock.ts @@ -1,5 +1,3 @@ -import { type PatientVitalsAndBiometrics } from '../packages/esm-patient-vitals-app/src/common'; - export const mockBiometricsResponse = { resourceType: 'Bundle', id: '0b0f8529-3e6e-41d9-8007-0ef639fb893b', @@ -5987,12 +5985,12 @@ export const mockFhirVitalsResponse = { }; export const mockBiometricsConfig = { - biometrics: { bmiUnit: 'kg / m²' }, concepts: { heightUuid: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', weightUuid: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }, + biometrics: { bmiUnit: 'kg / m²' }, }; export const mockVitalsConfig = { - biometrics: { bmiUnit: 'kg / m²' }, + biometrics: { bmiUnit: 'kg / m²', heightUnit: 'm', weightUnit: 'kg' }, concepts: { diastolicBloodPressureUuid: '5086AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', generalPatientNoteUuid: '165095AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', @@ -6007,8 +6005,12 @@ export const mockVitalsConfig = { }, vitals: { encounterTypeUuid: '67a71486-1a54-468f-ac3e-7091a9a79584', - formUuid: 'a000cb34-9ec1-4344-a1c8-f692232f6edd', + formUuid: '9f26aad4-244a-46ca-be49-1196df1a8c9a', useFormEngine: false, + logo: null, + showPrintButton: false, + formName: 'Vitals', + useMuacColors: false, }, }; @@ -6059,26 +6061,6 @@ export const formattedVitals = [ }, ]; -export const formattedVitalsAndBiometrics: Array = [ - { - id: '0', - uuid: 'fd481c9f-f81f-4aaf-b00b-747900af9935', - date: '2024-04-21T09:31:27.000Z', - height: 12, - weight: 12, - bmi: 833.3, - bloodPressureRenderInterpretation: 'normal', - systolic: 1, - diastolic: 2, - pulse: 12, - respiratoryRate: 12, - spo2: 12, - temperature: 31, - muac: 12, - generalPatientNote: 'notes', - }, -]; - export const mockVitalsConceptMetadata = mockVitalsSignsConcepts.data.results[0].setMembers; export const mockConceptUnits = new Map( diff --git a/e2e/specs/clinical-forms.spec.ts b/e2e/specs/clinical-forms.spec.ts index 71348d6f1f..a8941ff10c 100644 --- a/e2e/specs/clinical-forms.spec.ts +++ b/e2e/specs/clinical-forms.spec.ts @@ -51,7 +51,7 @@ test('Fill a clinical form', async ({ page }) => { }); await test.step('When I fill the `Subjective findings` question', async () => { - await page.getByLabel(/subjective Findings/i).fill(subjectiveFindings); + await page.getByLabel(/subjective findings/i).fill(subjectiveFindings); }); await test.step('And I fill the `Objective findings` question', async () => { @@ -66,13 +66,31 @@ test('Fill a clinical form', async ({ page }) => { await page.getByLabel(/plan/i).fill(plan); }); + await test.step('And I click the `Order basket` button on the siderail', async () => { + await page.getByRole('button', { name: /order basket/i, exact: true }).click(); + }); + + await test.step('And I click the `Add +` button to order drugs', async () => { + await page.getByRole('button', { name: /add/i }).nth(1).click(); + }); + + await test.step('And I click the `Clinical forms` button on the siderail', async () => { + await page.getByLabel(/clinical forms/i, { exact: true }).click(); + }); + + await test.step('Then I should see retained inputs in `Soap note template` form', async () => { + await expect(page.getByText(subjectiveFindings)).toBeVisible(); + await expect(page.getByText(objectiveFindings)).toBeVisible(); + await expect(page.getByText(assessment)).toBeVisible(); + await expect(page.getByText(plan)).toBeVisible(); + }); + await test.step('And I click on the `Save and close` button', async () => { await page.getByRole('button', { name: /save/i }).click(); }); await test.step('Then I should see a success notification', async () => { - await expect(page.getByText(/record created/i, { exact: true })).toBeVisible(); - await expect(page.getByText(/a new encounter was created/i, { exact: true })).toBeVisible(); + await expect(page.getByText(/form submitted successfully/i)).toBeVisible(); }); await test.step('And if I navigate to the visits dashboard', async () => { @@ -100,6 +118,160 @@ test('Fill a clinical form', async ({ page }) => { }); }); +test('Fill a form with a browser slightly ahead of time', async ({ page }) => { + const chartPage = new ChartPage(page); + const visitsPage = new VisitsPage(page); + await page.clock.fastForward('01:00'); // Advances the time by 1 minute in the testing environment. + + await test.step('When I visit the chart summary page', async () => { + await chartPage.goTo(patient.uuid); + }); + + await test.step('And I click the `Clinical forms` button on the siderail', async () => { + await page.getByLabel(/clinical forms/i, { exact: true }).click(); + }); + + await test.step('Then I should see `Laboratory Test Results` listed in the clinical forms workspace', async () => { + await expect(page.getByRole('cell', { name: /laboratory test results/i, exact: true })).toBeVisible(); + }); + + await test.step('When I click the `Laboratory Test Results` link to launch the form', async () => { + await page.getByText(/laboratory test results/i).click(); + }); + + await test.step('Then I should see the `Laboratory Test Results` form launch in the workspace', async () => { + await expect(page.getByText(/laboratory test results/i)).toBeVisible(); + }); + + await test.step('When I fill the `White Blood Cells (WBC)` result as `5000', async () => { + await page.locator('#ManualInputWhiteBloodCells').fill('5500'); + }); + + await test.step('And I fill the `Neutrophils` result as `35`', async () => { + await page.locator('#ManualEntryNeutrophilsMicroscopic').fill('35'); + }); + + await test.step('And I fill the `Hematocrit` result as `18`', async () => { + await page.locator('#ManualEntryHematocrit').fill('18'); + }); + + await test.step('And I click on the `Save` button', async () => { + await page.getByRole('button', { name: /save/i }).click(); + }); + + await test.step('Then I should see a success notification', async () => { + await expect(page.getByText(/form submitted successfully/i)).toBeVisible(); + }); + + await test.step('And I should not see any error messages', async () => { + await expect(page.getByText('error')).not.toBeVisible(); + }); +}); + +test('Form state is retained when moving between forms in the workspace', async ({ page }) => { + const chartPage = new ChartPage(page); + + await test.step('When I visit the chart summary page', async () => { + await chartPage.goTo(patient.uuid); + }); + + await test.step('And I click the `Clinical forms` button on the siderail', async () => { + await page.getByLabel(/clinical forms/i, { exact: true }).click(); + }); + + await test.step('Then I should see `Soap note template` listed in the clinical forms workspace', async () => { + await expect(page.getByRole('cell', { name: /soap note template/i, exact: true })).toBeVisible(); + }); + + await test.step('When I click the `Soap note template` link to launch the form', async () => { + await page.getByText(/soap note template/i).click(); + }); + + await test.step('Then I should see the `Soap note template` form launch in the workspace', async () => { + await expect(page.getByText(/soap note template/i)).toBeVisible(); + }); + + await test.step('When I fill the `Subjective findings` and `Objective findings` questions', async () => { + await page.getByLabel(/subjective Findings/i).fill(subjectiveFindings); + await page.getByLabel(/objective findings/i).fill(objectiveFindings); + }); + + await test.step('And I click the `Order basket` button on the siderail', async () => { + await page.getByRole('button', { name: /order basket/i, exact: true }).click(); + }); + + await test.step('And I click the `Add +` button to order drugs', async () => { + await page.getByRole('button', { name: /add/i }).nth(1).click(); + }); + + await test.step('And I click the `Clinical forms` button on the siderail', async () => { + await page.getByLabel(/clinical forms/i, { exact: true }).click(); + }); + + await test.step('Then I should see retained inputs in `Soap note template` form', async () => { + await page.locator('#SOAPSubjectiveFindings').waitFor(); + await expect(page.getByText(subjectiveFindings)).toBeVisible(); + await expect(page.getByText(objectiveFindings)).toBeVisible(); + }); +}); + +test('Form state is retained when minimizing a form in the workspace', async ({ page }) => { + const chartPage = new ChartPage(page); + + await test.step('When I visit the chart summary page', async () => { + await chartPage.goTo(patient.uuid); + }); + + await test.step('And I click the `Clinical forms` button on the siderail', async () => { + await page.getByLabel(/clinical forms/i, { exact: true }).click(); + }); + + await test.step('Then I should see the `Laboratory Test Results` form listed in the clinical forms workspace', async () => { + await expect(page.getByRole('cell', { name: /laboratory test results/i, exact: true })).toBeVisible(); + }); + + await test.step('When I click the `Laboratory Test Results` link to launch the form', async () => { + await page.getByText(/laboratory test results/i).click(); + }); + + await test.step('Then I should see the `Laboratory Test Results` form launch in the workspace', async () => { + await expect(page.getByText(/laboratory test results/i)).toBeVisible(); + }); + + await test.step('And I maximize the form', async () => { + await page.getByRole('button', { name: /maximize/i }).click(); + }); + + await test.step('And I fill in values for the `White Blood Cells (WBC)`, `Platelets`, and `Neutrophils` questions', async () => { + await page.locator('#ManualInputWhiteBloodCells').waitFor(); + await page.getByRole('spinbutton', { name: /white blood cells/i }).fill('5000'); + await page.getByRole('spinbutton', { name: /platelets/i }).fill('180000'); + await page.getByRole('spinbutton', { name: /neutrophils/i }).fill('35'); + }); + + await test.step('Then I minimize the form in the workspace', async () => { + await page.getByRole('button', { name: /minimize/i }).click(); + }); + + await test.step('And then I maximize the form in the workspace', async () => { + await page.getByRole('button', { name: /maximize/i }).click(); + }); + + await test.step('And I should see the original form state retained', async () => { + await expect(page.getByLabel(/white blood cells/i)).toHaveValue('5000'); + await expect(page.getByLabel(/platelets/i)).toHaveValue('180000'); + await expect(page.getByLabel(/neutrophils/i)).toHaveValue('35'); + }); + + await test.step('When I click on the `Save` button', async () => { + await page.getByRole('button', { name: /save/i }).click(); + }); + + await test.step('Then I should see a success notification', async () => { + await expect(page.getByText(/form submitted successfully/i)).toBeVisible(); + }); +}); + test.afterEach(async ({ api }) => { await endVisit(api, visit.uuid); await deletePatient(api, patient.uuid); diff --git a/e2e/specs/drug-orders.spec.ts b/e2e/specs/drug-orders.spec.ts index 146a426a82..299d72d3e7 100644 --- a/e2e/specs/drug-orders.spec.ts +++ b/e2e/specs/drug-orders.spec.ts @@ -72,6 +72,16 @@ test('Record, edit and discontinue a drug order', async ({ page }) => { await form.getByLabel(/^duration$/i).fill('3'); }); + await test.step('And I set the quantity to dispense to 3', async () => { + await form.getByLabel(/^quantity to dispense$/i).clear(); + await form.getByLabel(/^quantity to dispense$/i).fill('3'); + }); + + await test.step('And I set the prescription refills to 1', async () => { + await form.getByLabel(/^prescription refills$/i).clear(); + await form.getByLabel(/^prescription refills$/i).fill('1'); + }); + await test.step('And I set the indication to `Headache`', async () => { await form.getByLabel(/indication/i).clear(); await form.getByLabel(/indication/i).fill('Headache'); diff --git a/e2e/specs/edit-existing-visit.spec.ts b/e2e/specs/edit-existing-visit.spec.ts new file mode 100644 index 0000000000..84ec2c7f8b --- /dev/null +++ b/e2e/specs/edit-existing-visit.spec.ts @@ -0,0 +1,70 @@ +import { expect } from '@playwright/test'; +import { type Visit } from '@openmrs/esm-framework'; +import { type Patient, generateRandomPatient, deletePatient, startVisit } from '../commands'; +import { test } from '../core'; +import { ChartPage, VisitsPage } from '../pages'; + +let patient: Patient; +let visit: Visit; + +test.beforeEach(async ({ api }) => { + patient = await generateRandomPatient(api); + visit = await startVisit(api, patient.uuid); +}); + +test('Edit an existing visit', async ({ page }) => { + const chartPage = new ChartPage(page); + const visitsPage = new VisitsPage(page); + + await test.step('When I visit the Visits summary page', async () => { + await visitsPage.goTo(patient.uuid); + await expect(visitsPage.page.getByRole('button', { name: /edit visit details/i })).toBeVisible(); + }); + + await test.step('And I click on the `Edit visit details` button on an active visit', async () => { + await visitsPage.page.getByRole('button', { name: /edit visit details/i }).click(); + }); + + await test.step('Then I should see the `Edit Visit` form launch in the workspace', async () => { + await expect(chartPage.page.getByText(/visit start date and time/i)).toBeVisible(); + await expect(chartPage.page.getByPlaceholder(/dd\/mm\/yyyy/i)).toBeVisible(); + await expect(chartPage.page.getByPlaceholder(/hh\:mm/i)).toBeVisible(); + await expect(chartPage.page.getByRole('combobox', { name: /select a location/i })).toBeVisible(); + await expect(chartPage.page.getByRole('combobox', { name: /select a location/i })).toHaveValue('Outpatient Clinic'); + await expect(chartPage.page.getByText(/visit type/i)).toBeVisible(); + await expect(chartPage.page.getByLabel(/facility visit/i)).toBeChecked(); + await expect(chartPage.page.getByRole('search', { name: /search for a visit type/i })).toBeVisible(); + await expect(chartPage.page.getByLabel(/facility visit/i)).toBeVisible(); + await expect(chartPage.page.getByLabel(/home visit/i)).toBeVisible(); + await expect(chartPage.page.getByLabel(/opd visit/i)).toBeVisible(); + await expect(chartPage.page.getByLabel(/offline visit/i)).toBeVisible(); + await expect(chartPage.page.getByLabel(/group session/i)).toBeVisible(); + await expect(chartPage.page.getByRole('button', { name: /discard/i })).toBeVisible(); + await expect(chartPage.page.locator('form').getByRole('button', { name: /update visit/i })).toBeVisible(); + }); + + await test.step('And when I change the visit details and submit the form', async () => { + await chartPage.page.getByRole('button', { name: /clear selected item/i }).click(); + await chartPage.page.getByRole('combobox', { name: /select a location/i }).click(); + await chartPage.page.getByRole('option', { name: /inpatient ward/i }).click(); + await chartPage.page.getByText(/home visit/i).click(); + await expect(chartPage.page.getByLabel(/home visit/i)).toBeChecked(); + await chartPage.page.getByRole('button', { name: /update visit/i }).click(); + }); + + await test.step('Then I should see a success notification', async () => { + await expect(chartPage.page.getByText(/visit details updated/i)).toBeVisible(); + await expect(chartPage.page.getByText(/home visit updated successfully/i)).toBeVisible(); + }); + + await test.step('And I should see the updated visit details', async () => { + await expect(chartPage.page.getByRole('button', { name: /active visit/i })).toBeVisible(); + await chartPage.page.getByLabel(/active visit/i).click(); + await expect(chartPage.page.getByRole('tooltip')).toContainText(/home visit/i); + await expect(chartPage.page.getByRole('tooltip')).toContainText(/started: today/i); + }); +}); + +test.afterEach(async ({ api }) => { + await deletePatient(api, patient.uuid); +}); diff --git a/e2e/specs/lab-orders.spec.ts b/e2e/specs/lab-orders.spec.ts index 466ed5327e..de8487d758 100644 --- a/e2e/specs/lab-orders.spec.ts +++ b/e2e/specs/lab-orders.spec.ts @@ -14,6 +14,7 @@ test.beforeEach(async ({ api }) => { test('Record a lab order', async ({ page }) => { const ordersPage = new OrdersPage(page); + const orderBasket = page.locator('[data-extension-slot-name="order-basket-slot"]'); await test.step('When I visit the orders page', async () => { await ordersPage.goTo(patient.uuid); @@ -24,7 +25,7 @@ test('Record a lab order', async ({ page }) => { }); await test.step('And I click the `Add +` button on the Lab orders tile', async () => { - await page.getByRole('button', { name: /add/i }).nth(1).click(); + await orderBasket.getByRole('button', { name: /add/i }).nth(1).click(); }); await test.step('Then I type `Blood urea nitrogen` into the search bar', async () => { @@ -62,7 +63,7 @@ test('Record a lab order', async ({ page }) => { }); await test.step('Then I should see the newly added lab order in the list', async () => { - await expect(page.getByLabel('testorders').getByRole('cell', { name: /blood urea nitrogen/i })).toBeVisible(); + await expect(page.getByRole('cell', { name: /blood urea nitrogen/i })).toBeVisible(); }); }); diff --git a/e2e/specs/program-enrollment.spec.ts b/e2e/specs/program-enrollment.spec.ts index 4234d8e937..5ccfc602e7 100644 --- a/e2e/specs/program-enrollment.spec.ts +++ b/e2e/specs/program-enrollment.spec.ts @@ -26,7 +26,7 @@ test('Add and edit a program enrollment', async ({ page }) => { await expect(page.getByText('Record program enrollment', { exact: true })).toBeVisible(); }); - await test.step('When I select the program named `Hiv Care and Treatment`', async () => { + await test.step('When I select the program named `HIV Care and Treatment`', async () => { await page.locator('#program').selectOption('64f950e6-1b07-4ac0-8e7e-f3e148f3463f'); }); diff --git a/e2e/specs/results-viewer.spec.ts b/e2e/specs/results-viewer.spec.ts index f914e2275f..cf69581e32 100644 --- a/e2e/specs/results-viewer.spec.ts +++ b/e2e/specs/results-viewer.spec.ts @@ -257,29 +257,30 @@ test('Record and edit test results', async ({ page }) => { }); await test.step('Then I should see a success notification', async () => { - await expect(page.getByText(/record created/i, { exact: true })).toBeVisible(); - await expect(page.getByText(/a new encounter was created/i, { exact: true })).toBeVisible(); + await expect(page.getByText(/form submitted successfully/i)).toBeVisible(); }); await test.step('When I go to the results viewer page', async () => { await resultsViewerPage.goTo(patient.uuid); }); - await test.step('And I click on the `Panel` tab', async () => { - await page.getByRole('tab', { name: /panel/i }).click(); + await test.step('And I click on the `Individual tests` tab', async () => { + await page.getByRole('tab', { name: /individual tests/i }).click(); }); await test.step('Then I should see the newly entered test results reflect in the results viewer', async () => { - for (const { resultsPageReference, value } of completeBloodCountData) { - await test.step(resultsPageReference, async () => { - const row = page.locator(`tr:has-text("${resultsPageReference}")`); - const valueCell = row.locator('td:nth-child(2)'); - await expect(valueCell).toContainText(value); - }); - } + await test.step('Then I should see the newly entered test results reflect in the results viewer', async () => { + for (const { resultsPageReference, value } of completeBloodCountData) { + await test.step(resultsPageReference, async () => { + const row = page.locator(`tr:has-text("${resultsPageReference}"):has(td:has-text("${value}"))`).first(); + const valueCell = row.locator('td:nth-child(2)'); + await expect(valueCell).toContainText(value); + }); + } + }); for (const { resultsPageReference, value } of chemistryResultsData) { await test.step(resultsPageReference, async () => { - const row = page.locator(`tr:has-text("${resultsPageReference}")`); + const row = page.locator(`tr:has-text("${resultsPageReference}"):has(td:has-text("${value}"))`).first(); const valueCell = row.locator('td:nth-child(2)'); await expect(valueCell).toContainText(value); }); @@ -332,29 +333,28 @@ test('Record and edit test results', async ({ page }) => { }); await test.step('Then I should see a success notification', async () => { - await expect(page.getByText(/record updated/i, { exact: true })).toBeVisible(); - await expect(page.getByText(/the patient encounter was updated/i, { exact: true })).toBeVisible(); + await expect(page.getByText(/form submitted successfully/i)).toBeVisible(); }); await test.step('When I revisit the `Results Viewer` page', async () => { await resultsViewerPage.goTo(patient.uuid); }); - await test.step('And I click on the `Panel` tab', async () => { - await page.getByRole('tab', { name: /panel/i }).click(); + await test.step('And I click on the `Individual tests` tab', async () => { + await page.getByRole('tab', { name: /individual tests/i }).click(); }); await test.step('Then I should see the updated results reflect in the results viewer', async () => { for (const { resultsPageReference, updatedValue } of completeBloodCountData) { await test.step(resultsPageReference, async () => { - const row = page.locator(`tr:has-text("${resultsPageReference}")`); + const row = page.locator(`tr:has-text("${resultsPageReference}"):has(td:has-text("${updatedValue}"))`).first(); const valueCell = row.locator('td:nth-child(2)'); await expect(valueCell).toContainText(updatedValue); }); } for (const { resultsPageReference, updatedValue } of chemistryResultsData) { await test.step(resultsPageReference, async () => { - const row = page.locator(`tr:has-text("${resultsPageReference}")`); + const row = page.locator(`tr:has-text("${resultsPageReference}"):has(td:has-text("${updatedValue}"))`).first(); const valueCell = row.locator('td:nth-child(2)'); await expect(valueCell).toContainText(updatedValue); }); diff --git a/e2e/specs/visit.spec.ts b/e2e/specs/start-and-end-visit.spec.ts similarity index 100% rename from e2e/specs/visit.spec.ts rename to e2e/specs/start-and-end-visit.spec.ts diff --git a/e2e/support/bamboo/playwright.Dockerfile b/e2e/support/bamboo/playwright.Dockerfile index 889594307f..ba4a7e8cb7 100644 --- a/e2e/support/bamboo/playwright.Dockerfile +++ b/e2e/support/bamboo/playwright.Dockerfile @@ -1,5 +1,5 @@ # The image version should match the Playwright version specified in the package.json file -FROM mcr.microsoft.com/playwright:v1.44.0-jammy +FROM mcr.microsoft.com/playwright:v1.45.3-jammy ARG USER_ID ARG GROUP_ID diff --git a/jest.config.js b/jest.config.js index 0d2ef7d519..1a6f4287e8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,6 +3,7 @@ const path = require('path'); module.exports = { + clearMocks: true, transform: { '^.+\\.(j|t)sx?$': '@swc/jest', }, diff --git a/package.json b/package.json index f86de1f3c9..8754f71b2b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@openmrs/esm-patient-chart", "private": true, - "version": "8.0.1", + "version": "8.1.0", "workspaces": [ "packages/*" ], @@ -9,7 +9,7 @@ "start": "openmrs develop --sources packages/esm-patient-chart-app/", "ci:publish": "yarn workspaces foreach --all --topological --exclude @openmrs/esm-patient-chart npm publish --access public --tag latest", "ci:prepublish": "yarn workspaces foreach --all --topological --exclude @openmrs/esm-patient-chart npm publish --access public --tag next", - "ci:bump-form-engine-lib": "yarn up @openmrs/openmrs-form-engine-lib@next", + "ci:bump-form-engine-lib": "yarn up @openmrs/esm-form-engine-lib@next", "release": "yarn workspaces foreach --all --topological version", "verify": "turbo run lint typescript test --color --concurrency=5", "postinstall": "husky install", @@ -24,7 +24,7 @@ }, "devDependencies": { "@openmrs/esm-framework": "next", - "@playwright/test": "1.44.0", + "@playwright/test": "1.45.3", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.89", "@swc/jest": "^0.2.29", @@ -47,8 +47,10 @@ "dayjs": "^1.11.10", "dotenv": "^16.3.1", "eslint": "^8.50.0", + "eslint-plugin-jest-dom": "^5.4.0", "eslint-plugin-playwright": "^0.16.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", @@ -68,7 +70,7 @@ "sass": "^1.54.3", "swc-loader": "^0.2.3", "swr": "^2.2.4", - "turbo": "^2.0.6", + "turbo": "^2.0.12", "typescript": "^4.0.3", "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.15.1" diff --git a/packages/esm-form-engine-app/package.json b/packages/esm-form-engine-app/package.json index 914aced227..b395ec90f8 100644 --- a/packages/esm-form-engine-app/package.json +++ b/packages/esm-form-engine-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-form-engine-app", - "version": "8.0.1", + "version": "8.1.0", "license": "MPL-2.0", "description": "Wrapper ESM for the O3 React Form Engine", "browser": "dist/openmrs-esm-form-engine-app.js", @@ -38,7 +38,7 @@ }, "dependencies": { "@carbon/react": "^1.12.0", - "@openmrs/openmrs-form-engine-lib": "next", + "@openmrs/esm-form-engine-lib": "next", "lodash-es": "^4.17.21", "react-error-boundary": "^4.0.13" }, diff --git a/packages/esm-form-engine-app/src/form-collapse-toggle/form-collapse-toggle.scss b/packages/esm-form-engine-app/src/form-collapse-toggle/form-collapse-toggle.scss index 4378671b17..ce84c6d47c 100644 --- a/packages/esm-form-engine-app/src/form-collapse-toggle/form-collapse-toggle.scss +++ b/packages/esm-form-engine-app/src/form-collapse-toggle/form-collapse-toggle.scss @@ -1,8 +1,10 @@ +@use '@carbon/layout'; + .toggleContainer { display: flex; align-items: center; height: var(--workspace-header-height); - margin-right: 0.5rem; + margin-right: layout.$spacing-03; } :global(.omrs-breakpoint-lt-desktop) { diff --git a/packages/esm-form-engine-app/src/form-renderer/form-error.scss b/packages/esm-form-engine-app/src/form-renderer/form-error.scss index 216be70ce9..ca29f663f7 100644 --- a/packages/esm-form-engine-app/src/form-renderer/form-error.scss +++ b/packages/esm-form-engine-app/src/form-renderer/form-error.scss @@ -1,16 +1,16 @@ -@use '@carbon/styles/scss/spacing'; +@use '@carbon/layout'; @use '@carbon/colors'; -@use '@carbon/styles/scss/type'; +@use '@carbon/type'; .errorContainer { display: flex; justify-content: center; - margin-top: spacing.$spacing-05; + margin-top: layout.$spacing-05; } .formErrorCard { background-color: colors.$gray-10; - padding: spacing.$spacing-05; + padding: layout.$spacing-05; min-width: 23rem; display: flex; align-items: center; @@ -18,28 +18,27 @@ } .errorTitle { - @include type.type-style("heading-compact-02"); - margin-bottom: spacing.$spacing-04; + @include type.type-style('heading-compact-02'); + margin-bottom: layout.$spacing-04; } .errorMessage { - @include type.type-style("body-long-01"); - margin-bottom: spacing.$spacing-05; + @include type.type-style('body-long-01'); + margin-bottom: layout.$spacing-05; color: colors.$gray-70; display: flex; align-items: center; - &>button { - padding: 0 0 0 spacing.$spacing-01 !important; + & > button { + padding-left: layout.$spacing-01 !important; } } - .separator { @include type.type-style('body-01'); color: colors.$gray-90; width: 12rem; - margin: spacing.$spacing-03 0; + margin: layout.$spacing-03 0; overflow: hidden; text-align: center; color: colors.$gray-70; @@ -48,7 +47,7 @@ .separator::before, .separator::after { background-color: colors.$gray-40; - content: ""; + content: ''; display: inline-block; height: 1px; position: relative; @@ -57,18 +56,18 @@ } .separator::before { - right: 1rem; + right: layout.$spacing-05; margin-left: -50%; } .separator::after { - left: 1rem; + left: layout.$spacing-05; margin-right: -50%; } .list { - margin-left: spacing.$spacing-02; - color:colors.$blue-60; + margin-left: layout.$spacing-02; + color: colors.$blue-60; text-decoration: underline; cursor: pointer; -} \ No newline at end of file +} diff --git a/packages/esm-form-engine-app/src/form-renderer/form-error.test.tsx b/packages/esm-form-engine-app/src/form-renderer/form-error.test.tsx index 9f4a24bb54..c49a1df181 100644 --- a/packages/esm-form-engine-app/src/form-renderer/form-error.test.tsx +++ b/packages/esm-form-engine-app/src/form-renderer/form-error.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { launchPatientWorkspace } from '@openmrs/esm-patient-common-lib'; import FormError from './form-error.component'; -const mocklaunchPatientWorkspace = launchPatientWorkspace as jest.Mock; +const mocklaunchPatientWorkspace = jest.mocked(launchPatientWorkspace); jest.mock('@openmrs/esm-patient-common-lib', () => ({ launchPatientWorkspace: jest.fn(), diff --git a/packages/esm-form-engine-app/src/form-renderer/form-renderer.component.tsx b/packages/esm-form-engine-app/src/form-renderer/form-renderer.component.tsx index 2e347e4237..907472f674 100644 --- a/packages/esm-form-engine-app/src/form-renderer/form-renderer.component.tsx +++ b/packages/esm-form-engine-app/src/form-renderer/form-renderer.component.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { InlineLoading } from '@carbon/react'; -import { FormEngine } from '@openmrs/openmrs-form-engine-lib'; +import { FormEngine } from '@openmrs/esm-form-engine-lib'; import { showModal, type Visit } from '@openmrs/esm-framework'; import { launchPatientWorkspace } from '@openmrs/esm-patient-common-lib'; import FormError from './form-error.component'; @@ -29,11 +29,12 @@ const FormRenderer: React.FC = ({ }) => { const { t } = useTranslation(); const { schema, error, isLoading } = useFormSchema(formUuid); + const openClinicalFormsWorkspaceOnFormClose = additionalProps?.openClinicalFormsWorkspaceOnFormClose ?? true; const handleCloseForm = useCallback(() => { closeWorkspace(); - !encounterUuid && launchPatientWorkspace('clinical-forms-workspace'); - }, [closeWorkspace, encounterUuid]); + !encounterUuid && openClinicalFormsWorkspaceOnFormClose && launchPatientWorkspace('clinical-forms-workspace'); + }, [closeWorkspace, encounterUuid, openClinicalFormsWorkspaceOnFormClose]); const handleConfirmQuestionDeletion = useCallback(() => { return new Promise((resolve, reject) => { diff --git a/packages/esm-form-engine-app/src/form-renderer/form-renderer.scss b/packages/esm-form-engine-app/src/form-renderer/form-renderer.scss index b07adbe05b..96a2424eac 100644 --- a/packages/esm-form-engine-app/src/form-renderer/form-renderer.scss +++ b/packages/esm-form-engine-app/src/form-renderer/form-renderer.scss @@ -1,7 +1,3 @@ -@use '@carbon/colors'; -@use '@carbon/styles/scss/spacing'; -@use '@carbon/styles/scss/type'; - .loaderContainer { height: 100vh; } diff --git a/packages/esm-form-engine-app/src/form-renderer/form-renderer.test.tsx b/packages/esm-form-engine-app/src/form-renderer/form-renderer.test.tsx index 74d043c3c8..9b031c2b6e 100644 --- a/packages/esm-form-engine-app/src/form-renderer/form-renderer.test.tsx +++ b/packages/esm-form-engine-app/src/form-renderer/form-renderer.test.tsx @@ -3,9 +3,9 @@ import { render, screen } from '@testing-library/react'; import FormRenderer from './form-renderer.component'; import useFormSchema from '../hooks/useFormSchema'; -const mockUseFormSchema = useFormSchema as jest.Mock; +const mockUseFormSchema = jest.mocked(useFormSchema); -jest.mock('@openmrs/openmrs-form-engine-lib', () => ({ +jest.mock('@openmrs/esm-form-engine-lib', () => ({ FormEngine: jest .fn() .mockImplementation(() => React.createElement('div', { 'data-testid': 'openmrs form' }, 'FORM ENGINE LIB')), @@ -23,6 +23,7 @@ describe('FormRenderer', () => { closeWorkspace: jest.fn(), closeWorkspaceWithSavedChanges: jest.fn(), promptBeforeClosing: jest.fn(), + setTitle: jest.fn(), }; test('renders FormError component when there is an error', () => { @@ -42,7 +43,9 @@ describe('FormRenderer', () => { }); test('renders a form preview from the engine when a schema is available', async () => { - mockUseFormSchema.mockReturnValue({ schema: { id: 'test-schema' }, isLoading: false, error: null }); + mockUseFormSchema.mockReturnValue({ schema: { uuid: 'test-schema' }, isLoading: false, error: null } as ReturnType< + typeof useFormSchema + >); render(); await expect(screen.getByText(/form engine lib/i)).toBeInTheDocument(); diff --git a/packages/esm-form-engine-app/src/hooks/useFormSchema.tsx b/packages/esm-form-engine-app/src/hooks/useFormSchema.tsx index 31f1ce204a..16c378f1f3 100644 --- a/packages/esm-form-engine-app/src/hooks/useFormSchema.tsx +++ b/packages/esm-form-engine-app/src/hooks/useFormSchema.tsx @@ -1,7 +1,7 @@ import useSWR from 'swr'; import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; -import { type FormSchema } from '@openmrs/openmrs-form-engine-lib'; +import { type FormSchema } from '@openmrs/esm-form-engine-lib'; /** * Custom hook to fetch form schema based on its form UUID. diff --git a/packages/esm-form-engine-app/src/types/index.ts b/packages/esm-form-engine-app/src/types/index.ts index 9cd43716bf..c3bb70c27b 100644 --- a/packages/esm-form-engine-app/src/types/index.ts +++ b/packages/esm-form-engine-app/src/types/index.ts @@ -1,4 +1,4 @@ -import { type OpenmrsFormResource } from '@openmrs/openmrs-form-engine-lib'; +import { type OpenmrsFormResource } from '@openmrs/esm-form-engine-lib'; import { type OpenmrsResource } from '@openmrs/esm-framework'; export interface Form { diff --git a/packages/esm-form-engine-app/webpack.config.js b/packages/esm-form-engine-app/webpack.config.js index df41c9c7a3..dac35d9012 100644 --- a/packages/esm-form-engine-app/webpack.config.js +++ b/packages/esm-form-engine-app/webpack.config.js @@ -5,7 +5,7 @@ config.scriptRuleConfig.exclude = /(node_modules(?![\/\\]@(?:openmrs|ohri)))/; config.overrides.resolve = { extensions: ['.tsx', '.ts', '.jsx', '.js', '.scss'], alias: { - '@openmrs/openmrs-form-engine-lib': '@openmrs/openmrs-form-engine-lib/src/index', + '@openmrs/esm-form-engine-lib': '@openmrs/esm-form-engine-lib/src/index', }, }; module.exports = config; diff --git a/packages/esm-form-entry-app/angular.json b/packages/esm-form-entry-app/angular.json index cfada769e8..21bbe87cbe 100644 --- a/packages/esm-form-entry-app/angular.json +++ b/packages/esm-form-entry-app/angular.json @@ -28,13 +28,13 @@ "options": { "allowedCommonJsDependencies": [ "core-js", + "dayjs", "dompurify", "html2canvas", "lodash", "moment", "raf", - "rgbcolor", - "zone.js" + "rgbcolor" ], "preserveSymlinks": true, "outputPath": "dist", diff --git a/packages/esm-form-entry-app/package.json b/packages/esm-form-entry-app/package.json index 919eb8ffcb..1de26b2534 100644 --- a/packages/esm-form-entry-app/package.json +++ b/packages/esm-form-entry-app/package.json @@ -1,8 +1,8 @@ { "name": "@openmrs/esm-form-entry-app", - "version": "8.0.1", + "version": "8.1.0", "license": "MPL-2.0", - "description": "Form engine entry capabilities for the OpenMRS SPA", + "description": "Angular form engine for O3", "browser": "dist/openmrs-esm-form-entry-app.js", "main": "src/index.ts", "source": true, @@ -35,25 +35,25 @@ "url": "https://github.com/openmrs/openmrs-esm-patient-chart/issues" }, "dependencies": { - "@angular-extensions/elements": "^16.0.0", - "@angular/animations": "^16.2.12", - "@angular/cdk": "^16.2.12", - "@angular/common": "^16.2.12", - "@angular/compiler": "^16.2.12", - "@angular/compiler-cli": "^16.2.12", - "@angular/core": "^16.2.12", - "@angular/forms": "^16.2.12", - "@angular/platform-browser": "^16.2.12", - "@angular/platform-browser-dynamic": "^16.2.12", - "@angular/router": "^16.2.12", + "@angular-extensions/elements": "^17.1.1", + "@angular/animations": "^17.3.12", + "@angular/cdk": "^17.3.10", + "@angular/common": "^17.3.12", + "@angular/compiler": "^17.3.12", + "@angular/compiler-cli": "^17.3.12", + "@angular/core": "^17.3.12", + "@angular/forms": "^17.3.12", + "@angular/platform-browser": "^17.3.12", + "@angular/platform-browser-dynamic": "^17.3.12", + "@angular/router": "^17.3.12", "@carbon/styles": "~1.14.0", - "@ng-select/ng-select": "^11.2.0", + "@ng-select/ng-select": "^12.0.7", "@ngx-translate/core": "^15.0.0", "@openmrs/ngx-file-uploader": "next", "@openmrs/ngx-formentry": "next", "jspdf": "^2.5.1", "moment": "^2.29.4", - "ngx-bootstrap": "^11.0.1", + "ngx-bootstrap": "^12.0.0", "ngx-webcam": "^0.4.1", "reflect-metadata": "^0.1.13", "single-spa-angular": "^9.0.1", @@ -62,24 +62,22 @@ "tree-model": "^1.0.7", "tslib": "^2.4.1", "uuid": "^8.3.2", - "zone.js": "~0.13.3" + "zone.js": "~0.14.8" }, "peerDependencies": { "@openmrs/esm-framework": "5.x", "single-spa": "6.x" }, "devDependencies": { - "@angular-devkit/build-angular": "^16.2.11", - "@angular-eslint/builder": "^16.3.1", - "@angular-eslint/eslint-plugin": "^16.3.1", - "@angular-eslint/eslint-plugin-template": "^16.3.1", - "@angular-eslint/schematics": "^16.3.1", - "@angular-eslint/template-parser": "^16.3.1", - "@angular/cli": "^16.2.11", - "@angular/compiler-cli": "^16.2.12", - "@angular/language-service": "^16.2.12", - "@angular/localize": "^16.2.12", - "@openmrs/esm-framework": "next", + "@angular-devkit/build-angular": "^17.3.8", + "@angular-eslint/builder": "^17.5.2", + "@angular-eslint/eslint-plugin": "^17.5.2", + "@angular-eslint/eslint-plugin-template": "^17.5.2", + "@angular-eslint/schematics": "^17.5.2", + "@angular-eslint/template-parser": "^17.5.2", + "@angular/cli": "^17.3.8", + "@angular/language-service": "^17.3.12", + "@angular/localize": "^17.3.12", "@types/jasmine": "~3.6.11", "@types/jasminewd2": "~2.0.3", "@types/webpack-env": "^1.18.2", @@ -95,7 +93,7 @@ "karma-coverage-istanbul-reporter": "~3.0.3", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "^2.1.0", - "ngx-build-plus": "^16.0.0", + "ngx-build-plus": "^17.0.0", "openmrs": "next", "protractor": "~7.0.0", "rxjs": "^7.8.0", diff --git a/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.scss b/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.scss index f97dd5d624..610d18a7bb 100644 --- a/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.scss +++ b/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.scss @@ -1,7 +1,7 @@ -@use '@carbon/styles/scss/spacing'; @use '@carbon/colors'; -@use '@carbon/styles/scss/type'; -@import '@openmrs/esm-styleguide/src/vars'; +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .error-tile { width: 60%; @@ -10,7 +10,7 @@ flex-flow: column wrap; align-items: center; justify-content: center; - padding: 1.5rem 0 1.75rem; + padding: layout.$spacing-06 0 1.75rem; } /* Tablet */ @@ -24,7 +24,7 @@ } .helperText { - margin-top: 0.5rem; + margin-top: layout.$spacing-03; @include type.type-style('body-01'); color: colors.$gray-90; } @@ -38,9 +38,10 @@ text-align: center; } -.separator::before, .separator::after { +.separator::before, +.separator::after { background-color: colors.$gray-40; - content: ""; + content: ''; display: inline-block; height: 1px; position: relative; @@ -49,18 +50,18 @@ } .separator::before { - right: 1rem; + right: layout.$spacing-05; margin-left: -50%; } .separator::after { - left: 1rem; + left: layout.$spacing-05; margin-right: -50%; } .title-icon-size { - height: 0.8rem; - width: 0.8rem; + height: layout.$spacing-04; + width: layout.$spacing-04; } .no-errors { @@ -71,7 +72,7 @@ margin-top: -1px; width: 55%; height: 80%; - padding: 4px; + padding: layout.$spacing-02; overflow-y: scroll; overflow-x: hidden; } @@ -88,8 +89,8 @@ .error-content { border-top: 1px solid colors.$gray-20; - margin: -4px; - padding: 4px; + margin: -(layout.$spacing-02); + padding: layout.$spacing-02; } .error-title .closed { @@ -98,8 +99,7 @@ } .no-padding { - margin-left: -2rem; - margin-right: -2rem; + margin: 0 (-(layout.$spacing-07)); } .center { @@ -116,11 +116,11 @@ } .button-set { - margin: 0 -2rem; + margin: 0 (-(layout.$spacing-07)); } .button-set button { - height: 4rem; + height: layout.$spacing-10; align-items: baseline; min-width: 50%; } @@ -128,21 +128,21 @@ /* Desktop */ :host-context(.omrs-breakpoint-gt-tablet) .form-container { height: 100%; - margin: 0 2rem; + margin: 0 layout.$spacing-07; } :host-context(.omrs-breakpoint-gt-tablet) .button-set { - padding: 0rem; + padding: 0; } /* Tablet */ :host-context(.omrs-breakpoint-lt-desktop) .form-container { height: 100%; - margin: 0 2rem; + margin: 0 layout.$spacing-07; } :host-context(.omrs-breakpoint-lt-desktop) .button-set { - padding: 1.5rem 1rem; + padding: layout.$spacing-06 layout.$spacing-05; background-color: $ui-02; } @@ -172,14 +172,14 @@ flex-direction: column; width: 100%; row-gap: 0.625rem; - margin-top: 1rem; - padding-top: 1rem; + margin-top: layout.$spacing-05; + padding-top: layout.$spacing-05; margin-left: 0.625rem; - @include type.type-style("body-short-01"); - border-top: 1px solid #a8a8a8; + @include type.type-style('body-short-01'); + border-top: 1px solid colors.$gray-40; & button { width: 100%; - padding: spacing.$spacing-04 spacing.$spacing-05; + padding: layout.$spacing-04 layout.$spacing-05; } } diff --git a/packages/esm-form-entry-app/src/app/form-data-source/form-data-source.service.ts b/packages/esm-form-entry-app/src/app/form-data-source/form-data-source.service.ts index db168f8ee6..defff67c5e 100644 --- a/packages/esm-form-entry-app/src/app/form-data-source/form-data-source.service.ts +++ b/packages/esm-form-entry-app/src/app/form-data-source/form-data-source.service.ts @@ -341,17 +341,15 @@ export class FormDataSourceService { } } public getPatientObject(patient): PatientModel { - let model: PatientModel = { - sex: this.getGender(patient?.gender), - birthdate: new Date(patient?.birthDate), - age: this.calculateAge(patient?.birthDate), - identifiers: [], - gendercreatconstant: patient?.gender === 'female' ? 0.85 : patient?.gender === 'male' ? 1 : undefined, + return { + ...patient, + patientUuid: patient.id, + sex: this.getGender(patient.gender), + birthdate: patient.birthDate ? new Date(patient.birthDate) : undefined, + age: this.calculateAge(patient.birthDate), + gendercreatconstant: patient.gender === 'female' ? 0.85 : patient.gender === 'male' ? 1 : undefined, + identifiers: this.mapFHIRPatientIdentifiersToOpenMRSIdentifiers(patient), }; - - model.identifiers = this.mapFHIRPatientIdentifiersToOpenMRSIdentifiers(patient); - - return model; } private calculateAge(birthday) { diff --git a/packages/esm-form-entry-app/src/app/loader/loader.component.scss b/packages/esm-form-entry-app/src/app/loader/loader.component.scss index d15e8496c5..de6e27e546 100644 --- a/packages/esm-form-entry-app/src/app/loader/loader.component.scss +++ b/packages/esm-form-entry-app/src/app/loader/loader.component.scss @@ -1,4 +1,6 @@ +@use '@carbon/layout'; + .spinner { - min-height: 1rem; + min-height: layout.$spacing-05; width: fit-content; } diff --git a/packages/esm-form-entry-app/src/app/types/index.ts b/packages/esm-form-entry-app/src/app/types/index.ts index b0685a3fe2..3ce24440b0 100644 --- a/packages/esm-form-entry-app/src/app/types/index.ts +++ b/packages/esm-form-entry-app/src/app/types/index.ts @@ -454,6 +454,8 @@ export interface PatientModel { age: number; gendercreatconstant?: number; identifiers: Array; + patientUuid: string; + [key: string]: any; } export interface IdentifierPayload { diff --git a/packages/esm-form-entry-app/tsconfig.json b/packages/esm-form-entry-app/tsconfig.json index efed6eda48..d908f91fff 100644 --- a/packages/esm-form-entry-app/tsconfig.json +++ b/packages/esm-form-entry-app/tsconfig.json @@ -18,8 +18,9 @@ "webpack-env" ], "lib": [ + "dom", "es2015", - "dom" + "es2022", ], "skipLibCheck": true, "useDefineForClassFields": false, diff --git a/packages/esm-generic-patient-widgets-app/package.json b/packages/esm-generic-patient-widgets-app/package.json index 3f4838bcee..8edc620fcc 100644 --- a/packages/esm-generic-patient-widgets-app/package.json +++ b/packages/esm-generic-patient-widgets-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-generic-patient-widgets-app", - "version": "8.0.1", + "version": "8.1.0", "license": "MPL-2.0", "description": "Generic widgets for the patient chart", "browser": "dist/openmrs-esm-generic-patient-widgets-app.js", diff --git a/packages/esm-generic-patient-widgets-app/src/obs-graph/obs-graph.scss b/packages/esm-generic-patient-widgets-app/src/obs-graph/obs-graph.scss index 3c2c5a5ab6..c9f0a9a47c 100644 --- a/packages/esm-generic-patient-widgets-app/src/obs-graph/obs-graph.scss +++ b/packages/esm-generic-patient-widgets-app/src/obs-graph/obs-graph.scss @@ -1,62 +1,62 @@ -@use '@carbon/styles/scss/spacing'; -@use '@carbon/styles/scss/type'; -@import '@openmrs/esm-styleguide/src/vars'; +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .label01 { - @include type.type-style("label-01"); + @include type.type-style('label-01'); } .bodyShort01 { - @include type.type-style("body-compact-01"); + @include type.type-style('body-compact-01'); } .graphContainer { display: flex; - margin: 0rem spacing.$spacing-05; + margin: 0 layout.$spacing-05; flex-direction: row; justify-content: space-between; } .conceptPickerTabs { flex-grow: 1; - margin: spacing.$spacing-03 0; - padding-right: spacing.$spacing-05; + margin: layout.$spacing-03 0; + padding-right: layout.$spacing-05; } .lineChartContainer { flex-grow: 4; - padding: 0 spacing.$spacing-05 spacing.$spacing-09; + padding: 0 layout.$spacing-05 layout.$spacing-09; :global(.cds--cc--layout-row) { height: 0; } :global(.layout-child) { - margin-top: spacing.$spacing-03; + margin-top: layout.$spacing-03; } } .conceptLabel { @extend .label01; - margin-bottom: spacing.$spacing-05; + margin-bottom: layout.$spacing-05; display: inline-block; } .verticalTabs { - margin: 1rem 0; + margin: layout.$spacing-05 0; scroll-behavior: smooth; > ul { flex-direction: column !important; } - :global(.cds--tabs--scrollable .cds--tabs--scrollable__nav-item+.cds--tabs--scrollable__nav-item) { - margin-left: 0rem; + :global(.cds--tabs--scrollable .cds--tabs--scrollable__nav-item + .cds--tabs--scrollable__nav-item) { + margin-left: 0; } :global(.cds--tabs--scrollable .cds--tabs--scrollable__nav-link) { border-bottom: 0 !important; - border-left: 2px solid $color-gray-30; + border-left: layout.$spacing-01 solid $color-gray-30; } } @@ -64,21 +64,22 @@ outline: 0; outline-offset: 0; - &:active, &:focus { - outline: 2px solid var(--brand-03) !important; + &:active, + &:focus { + outline: layout.$spacing-01 solid var(--brand-03) !important; } - &[aria-selected="true"] { + &[aria-selected='true'] { border-left: 3px solid var(--brand-03); border-bottom: none; font-weight: 600; - margin-left: 0rem !important; + margin-left: 0 !important; } - &[aria-selected="false"] { + &[aria-selected='false'] { border-bottom: none; - border-left: 2px solid $ui-03; - margin-left: 0rem !important; + border-left: layout.$spacing-01 solid $ui-03; + margin-left: 0 !important; } } diff --git a/packages/esm-generic-patient-widgets-app/src/obs-switchable/obs-switchable.scss b/packages/esm-generic-patient-widgets-app/src/obs-switchable/obs-switchable.scss index 9d867d8d08..097f55411e 100644 --- a/packages/esm-generic-patient-widgets-app/src/obs-switchable/obs-switchable.scss +++ b/packages/esm-generic-patient-widgets-app/src/obs-switchable/obs-switchable.scss @@ -1,5 +1,6 @@ -@use '@carbon/styles/scss/spacing'; -@import '@openmrs/esm-styleguide/src/vars'; +@use '@carbon/colors'; +@use '@carbon/layout'; +@use '@openmrs/esm-styleguide/src/vars' as *; .widgetContainer { background-color: $ui-background; @@ -10,16 +11,16 @@ display: flex; justify-content: space-between; align-items: center; - padding: spacing.$spacing-04 0 spacing.$spacing-04 spacing.$spacing-05; + padding: layout.$spacing-04 0 layout.$spacing-04 layout.$spacing-05; } .toggleButtons { width: fit-content; - margin: 0 spacing.$spacing-03; + margin: 0 layout.$spacing-03; } .toggle { - padding: 0.5rem; + padding: layout.$spacing-03; color: $interactive-01; min-height: 0px; @@ -33,8 +34,8 @@ &:global(.cds--btn--tertiary:active) { border-color: $interactive-01; - background-color: #edf5ff; - color: #edf5ff; + background-color: colors.$blue-10; + color: colors.$blue-10; } &:global(.cds--btn--tertiary:focus) { @@ -48,17 +49,17 @@ } &:global(.cds--btn--ghost) { - border-color: #a6c8ff; + border-color: colors.$blue-30; } &:global(.cds--btn--ghost:hover) { - color: #000; + color: black; background-color: $ui-01; } } .toggle:first-of-type { - border-radius: spacing.$spacing-02 0 0 spacing.$spacing-02; + border-radius: layout.$spacing-02 0 0 layout.$spacing-02; &:global(.cds--btn--ghost) { border-right: 0; @@ -66,10 +67,10 @@ } .toggle:last-of-type { - border-radius: 0 spacing.$spacing-02 spacing.$spacing-02 0; + border-radius: 0 layout.$spacing-02 layout.$spacing-02 0; &:global(.cds--btn--ghost) { - border-left: 0rem; + border-left: 0; } } @@ -91,4 +92,3 @@ flex: 1 1 0%; justify-content: end; } - diff --git a/packages/esm-generic-patient-widgets-app/src/obs-table/obs-table.scss b/packages/esm-generic-patient-widgets-app/src/obs-table/obs-table.scss index eeda861563..156d88bbf4 100644 --- a/packages/esm-generic-patient-widgets-app/src/obs-table/obs-table.scss +++ b/packages/esm-generic-patient-widgets-app/src/obs-table/obs-table.scss @@ -1,8 +1,8 @@ -.customRow tbody{ +.customRow tbody { white-space: nowrap; } -.tableHeader{ - width: max-content; - max-width: 300px; -} \ No newline at end of file +.tableHeader { + width: max-content; + max-width: 300px; +} diff --git a/packages/esm-generic-patient-widgets-app/src/routes.json b/packages/esm-generic-patient-widgets-app/src/routes.json index 63e6dd571f..1f00c261a8 100644 --- a/packages/esm-generic-patient-widgets-app/src/routes.json +++ b/packages/esm-generic-patient-widgets-app/src/routes.json @@ -10,7 +10,7 @@ "online": true, "offline": true, "meta": { - "columnSpan": 4 + "fullWidth": false } } ] diff --git a/packages/esm-patient-allergies-app/package.json b/packages/esm-patient-allergies-app/package.json index 0c97525f11..a1f2079479 100644 --- a/packages/esm-patient-allergies-app/package.json +++ b/packages/esm-patient-allergies-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-allergies-app", - "version": "8.0.1", + "version": "8.1.0", "license": "MPL-2.0", "description": "Patient allergies microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-allergies-app.js", diff --git a/packages/esm-patient-allergies-app/src/allergies/allergies-detailed-summary.component.tsx b/packages/esm-patient-allergies-app/src/allergies/allergies-detailed-summary.component.tsx index fd1638e360..b98d075138 100644 --- a/packages/esm-patient-allergies-app/src/allergies/allergies-detailed-summary.component.tsx +++ b/packages/esm-patient-allergies-app/src/allergies/allergies-detailed-summary.component.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { @@ -17,11 +17,10 @@ import { import { Add } from '@carbon/react/icons'; import { formatDate, parseDate, useLayoutType } from '@openmrs/esm-framework'; import { CardHeader, EmptyState, ErrorState, launchPatientWorkspace } from '@openmrs/esm-patient-common-lib'; -import { useAllergies } from './allergy-intolerance.resource'; import { patientAllergiesFormWorkspace } from '../constants'; -import styles from './allergies-detailed-summary.scss'; -import { ReactionSeverity } from '../types'; +import { useAllergies } from './allergy-intolerance.resource'; import { AllergiesActionMenu } from './allergies-action-menu.component'; +import styles from './allergies-detailed-summary.scss'; interface AllergiesDetailedSummaryProps { patient: fhir.Patient; @@ -31,12 +30,12 @@ const AllergiesDetailedSummary: React.FC = ({ pat const { t } = useTranslation(); const displayText = t('allergyIntolerances', 'allergy intolerances'); const headerTitle = t('allergies', 'Allergies'); - const { allergies, isError, isLoading, isValidating } = useAllergies(patient.id); + const { allergies, error, isLoading, isValidating } = useAllergies(patient.id); const layout = useLayoutType(); const isTablet = layout === 'tablet'; const isDesktop = layout === 'small-desktop' || layout === 'large-desktop'; - const launchAllergiesForm = React.useCallback(() => launchPatientWorkspace(patientAllergiesFormWorkspace), []); + const launchAllergiesForm = useCallback(() => launchPatientWorkspace(patientAllergiesFormWorkspace), []); const tableHeaders = [ { key: 'display', header: t('allergen', 'Allergen') }, @@ -51,27 +50,10 @@ const AllergiesDetailedSummary: React.FC = ({ pat }, ]; - const tableRows = React.useMemo(() => { + const tableRows = useMemo(() => { return allergies?.map((allergy) => ({ ...allergy, - reactionSeverity: { - content: ( - - {allergy.reactionSeverity === ReactionSeverity.SEVERE && ( - - - - )} - - {allergy.reactionSeverity} - - - ), - }, + reactionSeverity: allergy.reactionSeverity?.toUpperCase() ?? '--', lastUpdated: allergy.lastUpdated ? formatDate(parseDate(allergy.lastUpdated), { time: false }) : '--', reaction: allergy.reactionManifestations?.join(', '), note: allergy?.note ?? '--', @@ -79,7 +61,7 @@ const AllergiesDetailedSummary: React.FC = ({ pat }, [allergies]); if (isLoading) return ; - if (isError) return ; + if (error) return ; if (allergies?.length) { return (
@@ -102,7 +84,7 @@ const AllergiesDetailedSummary: React.FC = ({ pat {headers.map((header) => ( { +describe('AllergiesDetailedSummary', () => { it('renders an empty state view if allergy data is unavailable', async () => { mockOpenmrsFetch.mockReturnValueOnce({ data: { entry: [] } }); - renderAllergiesDetailedSummary(); - + renderWithSwr(); await waitForLoadingToFinish(); expect(screen.queryByRole('table')).not.toBeInTheDocument(); @@ -34,8 +29,7 @@ describe('AllergiesDetailedSummary: ', () => { }, }; mockOpenmrsFetch.mockRejectedValueOnce(error); - renderAllergiesDetailedSummary(); - + renderWithSwr(); await waitForLoadingToFinish(); expect(screen.queryByRole('table')).not.toBeInTheDocument(); @@ -50,8 +44,7 @@ describe('AllergiesDetailedSummary: ', () => { it("renders a detailed summary of the patient's allergic reactions and their manifestations", async () => { mockOpenmrsFetch.mockReturnValueOnce({ data: mockFhirAllergyIntoleranceResponse }); - renderAllergiesDetailedSummary(); - + renderWithSwr(); await waitForLoadingToFinish(); expect(screen.getByRole('heading', { name: /allergies/i })).toBeInTheDocument(); @@ -76,8 +69,7 @@ describe('AllergiesDetailedSummary: ', () => { it("renders non-coded allergen name and non-coded allergic reaction name in the detailed summary of the patient's allergies", async () => { mockOpenmrsFetch.mockReturnValueOnce({ data: mockFhirAllergyIntoleranceResponse }); - renderAllergiesDetailedSummary(); - + renderWithSwr(); await waitForLoadingToFinish(); expect(screen.getByRole('heading', { name: /allergies/i })).toBeInTheDocument(); @@ -86,7 +78,3 @@ describe('AllergiesDetailedSummary: ', () => { expect(screen.getByRole('row', { name: new RegExp(expectedNonCodedAllergy) })).toBeInTheDocument(); }); }); - -function renderAllergiesDetailedSummary() { - renderWithSwr(); -} diff --git a/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form-tab.scss b/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form-tab.scss index cc16bc4e8f..c50945e9eb 100644 --- a/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form-tab.scss +++ b/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form-tab.scss @@ -1,17 +1,15 @@ -@import '../../root.scss'; - .radioButtonWrapper { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - flex-direction: row; - min-height: 18rem; - width: 30rem; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + flex-direction: row; + min-height: 18rem; + width: 30rem; } -.radioButtonWrapper div { - width: 14rem; - display: flex; - height: spacing.$spacing-07; - margin: 0rem; -} \ No newline at end of file +.radioButtonWrapper div { + width: 14rem; + display: flex; + height: layout.$spacing-07; + margin: 0; +} diff --git a/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.scss b/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.scss index 4d0b7d465a..df35749e90 100644 --- a/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.scss +++ b/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.scss @@ -1,7 +1,5 @@ -@use '@carbon/styles/scss/spacing'; -@use '@carbon/styles/scss/type'; -@import '@openmrs/esm-styleguide/src/vars'; -@import '../../root.scss'; +@use '@carbon/layout'; +@use '@openmrs/esm-styleguide/src/vars' as *; .header { border-bottom: 0.0625rem solid $grey-2; @@ -11,7 +9,7 @@ .content { display: grid; grid-template-columns: 50% 10% 1fr; - margin: 0.5rem 1rem; + margin: layout.$spacing-03 layout.$spacing-05; } .wrapperContainer fieldset { @@ -25,7 +23,7 @@ } .radio { - margin: 1rem 0rem; + margin: layout.$spacing-05 0; label { justify-content: flex-start; @@ -34,12 +32,12 @@ .checkbox { &:not(:first-child) { - margin: 0.3rem 0rem; + margin: 0.3rem 0; } } .input { - margin: 0rem 1rem 1rem; + margin: 0 layout.$spacing-05 layout.$spacing-05; } .checkboxContainer { @@ -48,7 +46,7 @@ } .button { - height: 4rem; + height: layout.$spacing-10; display: flex; align-content: flex-start; align-items: baseline; @@ -56,12 +54,12 @@ } .desktopButtons { - margin-top: 2rem; + margin-top: layout.$spacing-07; } .tabletButtons { - margin-top: 2rem; - padding: 1.5rem 1rem; + margin-top: layout.$spacing-07; + padding: layout.$spacing-06 layout.$spacing-05; background-color: $ui-02; } @@ -71,7 +69,7 @@ } .formContent { - margin: 1rem; + margin: layout.$spacing-05; } .form { diff --git a/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.test.tsx b/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.test.tsx index 7dbd2f70fb..3f701b0f7e 100644 --- a/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.test.tsx +++ b/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { screen, render, within } from '@testing-library/react'; -import { type FetchResponse, showSnackbar } from '@openmrs/esm-framework'; +import { type FetchResponse, getDefaultsFromConfigSchema, showSnackbar, useConfig } from '@openmrs/esm-framework'; import { mockAllergens, mockAllergicReactions } from '__mocks__'; import { mockPatient } from 'tools'; import { @@ -11,27 +11,25 @@ import { useAllergicReactions, updatePatientAllergy, } from './allergy-form.resource'; -import AllergyForm from './allergy-form.workspace'; -import { AllergenType, ReactionSeverity } from '../../types'; import { mockAllergy } from '__mocks__'; +import { type AllergiesConfigObject, configSchema } from '../../config-schema'; +import { AllergenType } from '../../types'; +import AllergyForm from './allergy-form.workspace'; -const mockSaveAllergy = saveAllergy as jest.Mock>; -const mockUseAllergens = useAllergens as jest.Mock; -const mockUseAllergicReactions = useAllergicReactions as jest.Mock; -const mockShowSnackbar = showSnackbar as jest.Mock; -const mockUpdatePatientAllergy = updatePatientAllergy as jest.Mock; - -jest.mock('./allergy-form.resource', () => { - const originalModule = jest.requireActual('./allergy-form.resource'); - - return { - ...originalModule, - saveAllergy: jest.fn().mockResolvedValue({ data: {}, status: 201, statusText: 'Created' }), - updatePatientAllergy: jest.fn().mockResolvedValue({ data: {}, status: 200, statusText: 'Updated' }), - useAllergens: jest.fn(), - useAllergicReactions: jest.fn(), - }; -}); +const mockSaveAllergy = jest.mocked(saveAllergy); +const mockShowSnackbar = jest.mocked(showSnackbar); +const mockUpdatePatientAllergy = jest.mocked(updatePatientAllergy); +const mockUseAllergens = jest.mocked(useAllergens); +const mockUseAllergicReactions = jest.mocked(useAllergicReactions); +const mockUseConfig = jest.mocked(useConfig); + +jest.mock('./allergy-form.resource', () => ({ + ...jest.requireActual('./allergy-form.resource'), + saveAllergy: jest.fn().mockResolvedValue({ data: {}, status: 201, statusText: 'Created' }), + updatePatientAllergy: jest.fn().mockResolvedValue({ data: {}, status: 200, statusText: 'Updated' }), + useAllergens: jest.fn(), + useAllergicReactions: jest.fn(), +})); const mockConcepts = { drugAllergenUuid: '162552AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', @@ -44,32 +42,27 @@ const mockConcepts = { otherConceptUuid: '5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', }; -mockUseAllergens.mockReturnValue({ - isLoading: false, - allergens: mockAllergens, -}); -mockUseAllergicReactions.mockReturnValue({ - isLoading: false, - allergicReactions: mockAllergicReactions, -}); - -jest.mock('@openmrs/esm-framework', () => { - const originalModule = jest.requireActual('@openmrs/esm-framework'); - - return { - ...originalModule, - openmrsFetch: jest.fn(), - useConfig: jest.fn().mockImplementation(() => ({ +describe('AllergyForm', () => { + beforeEach(() => { + mockUseAllergens.mockReturnValue({ + isLoading: false, + allergens: mockAllergens, + }); + mockUseAllergicReactions.mockReturnValue({ + isLoading: false, + allergicReactions: mockAllergicReactions, + }); + mockUseConfig.mockReturnValue({ + ...getDefaultsFromConfigSchema(configSchema), concepts: mockConcepts, - })), - }; -}); + }); + }); -describe('AllergyForm ', () => { it('renders the allergy form with all the expected fields and values', async () => { - renderAllergyForm(); const user = userEvent.setup(); + renderAllergyForm(); + const allergensContainer = screen.getByTestId('allergens-container'); const allergenInput = screen.queryByPlaceholderText(/select the allergen/i); @@ -94,9 +87,10 @@ describe('AllergyForm ', () => { }); it('enable the save button when all required fields are filled', async () => { - renderAllergyForm(); const user = userEvent.setup(); + renderAllergyForm(); + const allergen = mockAllergens[0]; const reaction = mockAllergicReactions[0]; @@ -126,10 +120,10 @@ describe('AllergyForm ', () => { }); it('calls the saveAllergy function with the correct payload', async () => { - mockSaveAllergy.mockClear(); + const user = userEvent.setup(); + renderAllergyForm(); - const user = userEvent.setup(); const allergenInput = screen.getByPlaceholderText(/select the allergen/i); const allergen = mockAllergens[0]; @@ -159,9 +153,10 @@ describe('AllergyForm ', () => { }); it('displays a custom input and a warning message when select other allergen', async () => { + const user = userEvent.setup(); + renderAllergyForm(); - const user = userEvent.setup(); const allergenInput = screen.getByPlaceholderText(/select the allergen/i); const allergensContainer = screen.getByTestId('allergens-container'); @@ -178,10 +173,10 @@ describe('AllergyForm ', () => { }); it('calls the saveAllergy function with the correct payload when select other allergen', async () => { - mockSaveAllergy.mockClear(); + const user = userEvent.setup(); + renderAllergyForm(); - const user = userEvent.setup(); const allergenInput = screen.getByPlaceholderText(/select the allergen/i); const allergensContainer = screen.getByTestId('allergens-container'); const customAllergen = 'some other allergen'; @@ -215,12 +210,10 @@ describe('AllergyForm ', () => { }); it('renders a success notification after successful submission', async () => { - mockSaveAllergy.mockClear(); - mockShowSnackbar.mockClear(); + const user = userEvent.setup(); renderAllergyForm(); - const user = userEvent.setup(); const allergenInput = screen.getByPlaceholderText(/select the allergen/i); const allergen = mockAllergens[0]; @@ -244,8 +237,8 @@ describe('AllergyForm ', () => { }); it('renders an error snackbar upon an invalid submission', async () => { - mockSaveAllergy.mockClear(); - mockShowSnackbar.mockClear(); + const user = userEvent.setup(); + mockSaveAllergy.mockRejectedValue({ message: 'Internal Server Error', response: { @@ -256,7 +249,6 @@ describe('AllergyForm ', () => { renderAllergyForm(); - const user = userEvent.setup(); const allergenInput = screen.getByPlaceholderText(/select the allergen/i); const allergen = mockAllergens[0]; @@ -278,11 +270,12 @@ describe('AllergyForm ', () => { kind: 'error', }); }); - it('Edit Allergy should call the saveAllergy function with updated payload', async () => { - mockSaveAllergy.mockClear(); - renderEditAllergyForm(); + it('Edit Allergy should call the saveAllergy function with updated payload', async () => { const user = userEvent.setup(); + + renderAllergyForm({ allergy: mockAllergy, formContext: 'editing' }); + const allergenInput = screen.getByPlaceholderText(/select the allergen/i); const commentInput = screen.getByLabelText(/Date of onset and comments/i); @@ -319,8 +312,8 @@ describe('AllergyForm ', () => { }); }); -function renderAllergyForm() { - const testProps = { +function renderAllergyForm(props = {}) { + const defaultProps = { closeWorkspace: () => {}, closeWorkspaceWithSavedChanges: () => {}, promptBeforeClosing: () => {}, @@ -328,21 +321,8 @@ function renderAllergyForm() { formContext: 'creating' as 'creating' | 'editing', patient: mockPatient, patientUuid: mockPatient.id, + setTitle: jest.fn(), }; - render(); -} - -function renderEditAllergyForm() { - const testProps = { - closeWorkspace: () => {}, - closeWorkspaceWithSavedChanges: () => {}, - promptBeforeClosing: () => {}, - allergy: mockAllergy, - formContext: 'editing' as 'creating' | 'editing', - patient: mockPatient, - patientUuid: mockPatient.id, - }; - - render(); + render(); } diff --git a/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.workspace.tsx b/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.workspace.tsx index dbbf51fd13..0225c9ccfe 100644 --- a/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.workspace.tsx +++ b/packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.workspace.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import classNames from 'classnames'; -import { type TFunction, useTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import { Button, ButtonSet, @@ -72,8 +72,7 @@ interface AllergyFormProps extends DefaultPatientWorkspaceProps { } function AllergyForm(props: AllergyFormProps) { - const { closeWorkspace, patientUuid, allergy, formContext, promptBeforeClosing, closeWorkspaceWithSavedChanges } = - props; + const { closeWorkspace, patientUuid, allergy, formContext, promptBeforeClosing } = props; const { t } = useTranslation(); const { concepts } = useConfig(); const isTablet = useLayoutType() === 'tablet'; @@ -333,7 +332,7 @@ function AllergyForm(props: AllergyFormProps) { )}
-
+
{isLoading ? ( ) : ( diff --git a/packages/esm-patient-allergies-app/src/allergies/allergies-overview.component.tsx b/packages/esm-patient-allergies-app/src/allergies/allergies-overview.component.tsx index c5bbfc430a..e2bdc6cf48 100644 --- a/packages/esm-patient-allergies-app/src/allergies/allergies-overview.component.tsx +++ b/packages/esm-patient-allergies-app/src/allergies/allergies-overview.component.tsx @@ -42,7 +42,7 @@ const AllergiesOverview: React.FC = ({ patient }) => { const isTablet = layout === 'tablet'; const isDesktop = layout === 'small-desktop' || layout === 'large-desktop'; - const { allergies, isError, isLoading, isValidating } = useAllergies(patient.id); + const { allergies, error, isLoading, isValidating } = useAllergies(patient.id); const { results: paginatedAllergies, goTo, currentPage } = usePagination(allergies ?? [], allergiesCount); const tableHeaders = [ @@ -68,7 +68,7 @@ const AllergiesOverview: React.FC = ({ patient }) => { const launchAllergiesForm = React.useCallback(() => launchPatientWorkspace(patientAllergiesFormWorkspace), []); if (isLoading) return ; - if (isError) return ; + if (error) return ; if (allergies?.length) { return (
@@ -91,7 +91,7 @@ const AllergiesOverview: React.FC = ({ patient }) => { {headers.map((header) => ( { - const originalModule = jest.requireActual('@openmrs/esm-framework'); - - return { - ...originalModule, - attach: jest.fn(), - }; -}); - -describe('AllergiesOverview: ', () => { +describe('AllergiesOverview', () => { it('renders an empty state view if allergy data is unavailable', async () => { mockOpenmrsFetch.mockReturnValueOnce({ data: [] }); - renderAllergiesOverview(); + renderWithSwr(); await waitForLoadingToFinish(); @@ -43,7 +29,7 @@ describe('AllergiesOverview: ', () => { }, }; mockOpenmrsFetch.mockRejectedValueOnce(error); - renderAllergiesOverview(); + renderWithSwr(); await waitForLoadingToFinish(); @@ -60,7 +46,7 @@ describe('AllergiesOverview: ', () => { it("renders an overview of the patient's allergic reactions and their manifestations", async () => { mockOpenmrsFetch.mockReturnValueOnce({ data: mockFhirAllergyIntoleranceResponse }); - renderAllergiesOverview(); + renderWithSwr(); await waitForLoadingToFinish(); @@ -86,7 +72,3 @@ describe('AllergiesOverview: ', () => { expect(screen.getByRole('button', { name: /next page/i })).toBeInTheDocument(); }); }); - -function renderAllergiesOverview() { - renderWithSwr(); -} diff --git a/packages/esm-patient-allergies-app/src/allergies/allergies-tile.scss b/packages/esm-patient-allergies-app/src/allergies/allergies-tile.scss index 43e4c23617..521595c875 100644 --- a/packages/esm-patient-allergies-app/src/allergies/allergies-tile.scss +++ b/packages/esm-patient-allergies-app/src/allergies/allergies-tile.scss @@ -1,5 +1,5 @@ -@use '@carbon/styles/scss/type'; -@import '@openmrs/esm-styleguide/src/vars'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .label { @include type.type-style('label-01'); diff --git a/packages/esm-patient-allergies-app/src/allergies/allergies-tile.test.tsx b/packages/esm-patient-allergies-app/src/allergies/allergies-tile.test.tsx index b9eec0c266..eff079583d 100644 --- a/packages/esm-patient-allergies-app/src/allergies/allergies-tile.test.tsx +++ b/packages/esm-patient-allergies-app/src/allergies/allergies-tile.test.tsx @@ -5,16 +5,12 @@ import { mockPatient, renderWithSwr, waitForLoadingToFinish } from 'tools'; import { mockFhirAllergyIntoleranceResponse } from '__mocks__'; import AllergiesTile from './allergies-tile.component'; -const testProps = { - patientUuid: mockPatient.id, -}; - const mockOpenmrsFetch = openmrsFetch as jest.Mock; describe('AllergiesTile', () => { it('renders an empty state when allergy data is not available', async () => { mockOpenmrsFetch.mockReturnValueOnce({ data: [] }); - renderAllergiesTile(); + renderWithSwr(); await waitForLoadingToFinish(); @@ -24,7 +20,7 @@ describe('AllergiesTile', () => { it("renders a summary of the patient's allergy data when available", async () => { mockOpenmrsFetch.mockReturnValueOnce({ data: mockFhirAllergyIntoleranceResponse }); - renderAllergiesTile(); + renderWithSwr(); await waitForLoadingToFinish(); @@ -32,7 +28,3 @@ describe('AllergiesTile', () => { expect(screen.getByText(/ACE inhibitors, Fish, Penicillins, Morphine, Aspirin/i)).toBeInTheDocument(); }); }); - -function renderAllergiesTile() { - renderWithSwr(); -} diff --git a/packages/esm-patient-allergies-app/src/allergies/allergy-intolerance.resource.ts b/packages/esm-patient-allergies-app/src/allergies/allergy-intolerance.resource.ts index 0141bb6026..319a9c2461 100644 --- a/packages/esm-patient-allergies-app/src/allergies/allergy-intolerance.resource.ts +++ b/packages/esm-patient-allergies-app/src/allergies/allergy-intolerance.resource.ts @@ -21,7 +21,7 @@ export type Allergy = { type UseAllergies = { allergies: Array; - isError: Error | null; + error: Error | null; isLoading: boolean; isValidating: boolean; mutate: () => void; @@ -45,7 +45,7 @@ export function useAllergies(patientUuid: string): UseAllergies { return { allergies: data ? formattedAllergies : null, - isError: error, + error: error, isLoading, isValidating, mutate, diff --git a/packages/esm-patient-allergies-app/src/config-schema.ts b/packages/esm-patient-allergies-app/src/config-schema.ts index 50e5bbc309..f93f6cb6b5 100644 --- a/packages/esm-patient-allergies-app/src/config-schema.ts +++ b/packages/esm-patient-allergies-app/src/config-schema.ts @@ -12,6 +12,7 @@ export interface AllergiesConfigObject { otherConceptUuid: string; }; } + export const configSchema = { concepts: { drugAllergenUuid: { diff --git a/packages/esm-patient-allergies-app/src/dashboard.meta.ts b/packages/esm-patient-allergies-app/src/dashboard.meta.ts index 13387b5b22..32182e03bc 100644 --- a/packages/esm-patient-allergies-app/src/dashboard.meta.ts +++ b/packages/esm-patient-allergies-app/src/dashboard.meta.ts @@ -1,6 +1,5 @@ export const dashboardMeta = { slot: 'patient-chart-allergies-dashboard-slot', - columns: 1, path: 'Allergies', title: 'Allergies', }; diff --git a/packages/esm-patient-allergies-app/src/root.scss b/packages/esm-patient-allergies-app/src/root.scss deleted file mode 100644 index 56e17a9b8d..0000000000 --- a/packages/esm-patient-allergies-app/src/root.scss +++ /dev/null @@ -1,47 +0,0 @@ -@use '@carbon/styles/scss/spacing'; -@use '@carbon/styles/scss/type'; -@import '@openmrs/esm-styleguide/src/vars'; - -.productiveHeading01 { - @include type.type-style("heading-compact-01"); -} - -.productiveHeading02 { - @include type.type-style("heading-compact-02"); -} - -.productiveHeading03 { - @include type.type-style("heading-03"); -} - -.productiveHeading04 { - @include type.type-style("heading-04"); -} - -.productiveHeading05 { - @include type.type-style("heading-05"); -} - -.productiveHeading06 { - @include type.type-style("heading-06"); -} - -.bodyShort01 { - @include type.type-style("body-compact-01"); -} - -.bodyShort02 { - @include type.type-style("body-compact-02"); -} - -.bodyLong01 { - @include type.type-style("body-01"); -} - -.bodyLong02 { - @include type.type-style("body-02"); -} - -.label01 { - @include type.type-style("label-01"); -} diff --git a/packages/esm-patient-allergies-app/src/routes.json b/packages/esm-patient-allergies-app/src/routes.json index ee9ad6dfb0..bfc3172078 100644 --- a/packages/esm-patient-allergies-app/src/routes.json +++ b/packages/esm-patient-allergies-app/src/routes.json @@ -18,10 +18,7 @@ "component": "allergiesDetailedSummary", "slot": "patient-chart-allergies-dashboard-slot", "online": true, - "offline": true, - "meta": { - "columnSpan": 4 - } + "offline": true }, { "name": "allergies-summary-dashboard", @@ -31,7 +28,6 @@ "offline": true, "order": 6, "meta": { - "columns": 1, "slot": "patient-chart-allergies-dashboard-slot", "path": "Allergies" } diff --git a/packages/esm-patient-attachments-app/package.json b/packages/esm-patient-attachments-app/package.json index 4b05a69833..7f3d8f5234 100644 --- a/packages/esm-patient-attachments-app/package.json +++ b/packages/esm-patient-attachments-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-attachments-app", - "version": "8.0.1", + "version": "8.1.0", "license": "MPL-2.0", "description": "Patient attachments microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-attachments-app.js", @@ -38,7 +38,7 @@ }, "dependencies": { "@carbon/react": "^1.12.0", - "@openmrs/esm-patient-common-lib": "^8.0.1", + "@openmrs/esm-patient-common-lib": "^8.1.0", "lodash-es": "^4.17.21", "react-grid-gallery": "^0.5.6", "react-html5-camera-photo": "^1.5.11" diff --git a/packages/esm-patient-attachments-app/src/attachments/attachment-preview.component.tsx b/packages/esm-patient-attachments-app/src/attachments/attachment-preview.component.tsx index ac06021188..58739f686e 100644 --- a/packages/esm-patient-attachments-app/src/attachments/attachment-preview.component.tsx +++ b/packages/esm-patient-attachments-app/src/attachments/attachment-preview.component.tsx @@ -71,7 +71,7 @@ const AttachmentPreview: React.FC = ({

{attachmentToPreview.filename}

{attachmentToPreview?.description ? ( -

{attachmentToPreview.description}

+

{attachmentToPreview.description}

) : null}
diff --git a/packages/esm-patient-attachments-app/src/attachments/attachment-preview.scss b/packages/esm-patient-attachments-app/src/attachments/attachment-preview.scss index d450993803..b722bf3ba0 100644 --- a/packages/esm-patient-attachments-app/src/attachments/attachment-preview.scss +++ b/packages/esm-patient-attachments-app/src/attachments/attachment-preview.scss @@ -1,13 +1,14 @@ -@use '@carbon/styles/scss/spacing'; -@use '~@carbon/styles/scss/colors'; -@import "../root.scss"; +@use '@carbon/colors'; +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .previewContainer { display: grid; grid-template-columns: 1fr 16rem; position: fixed; width: 100vw; - top: 3rem; + top: layout.$spacing-09; bottom: 0; left: 0; z-index: 5001; @@ -22,7 +23,7 @@ .rightPanel { background-color: colors.$gray-80; - padding: spacing.$spacing-05; + padding: layout.$spacing-05; color: $ui-01; } @@ -51,7 +52,7 @@ .overflowMenu { &:hover { - background-color: hsla(0,0%,55%,.12) !important; + background-color: hsla(0, 0%, 55%, 0.12) !important; } svg { @@ -60,7 +61,7 @@ } .imageDescription { - margin-top: spacing.$spacing-03; + margin-top: layout.$spacing-03; } .menuItem { @@ -68,7 +69,7 @@ } .title { - @extend .productiveHeading02; + @include type.type-style('heading-compact-02'); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; diff --git a/packages/esm-patient-attachments-app/src/attachments/attachment-thumbnail.scss b/packages/esm-patient-attachments-app/src/attachments/attachment-thumbnail.scss index 07d8c66315..7c9383adc8 100644 --- a/packages/esm-patient-attachments-app/src/attachments/attachment-thumbnail.scss +++ b/packages/esm-patient-attachments-app/src/attachments/attachment-thumbnail.scss @@ -1,4 +1,5 @@ -@import "../root.scss"; +@use '@carbon/layout'; +@use '@openmrs/esm-styleguide/src/vars' as vars; .caption { position: absolute; @@ -25,7 +26,7 @@ .pdfThumbnail { cursor: pointer; - background-color: $color-gray-30; + background-color: vars.$color-gray-30; display: flex; justify-content: center; align-items: center; @@ -52,8 +53,8 @@ .infoIcon { visibility: hidden; position: absolute; - right: 4px; - top: 4px; + right: layout.$spacing-02; + top: layout.$spacing-02; cursor: pointer; } @@ -65,7 +66,7 @@ visibility: hidden; width: 170px; background-color: black; - color: #fff; + color: white; text-align: center; border-radius: 6px; padding: 5px 0; diff --git a/packages/esm-patient-attachments-app/src/attachments/attachments-grid-overview.scss b/packages/esm-patient-attachments-app/src/attachments/attachments-grid-overview.scss index 4172011e64..8c89b83d69 100644 --- a/packages/esm-patient-attachments-app/src/attachments/attachments-grid-overview.scss +++ b/packages/esm-patient-attachments-app/src/attachments/attachments-grid-overview.scss @@ -1,17 +1,16 @@ -@use '@carbon/styles/scss/spacing'; -@use '~@carbon/styles/scss/colors'; -@use '@carbon/styles/scss/type'; -@import '../root.scss'; +@use '@carbon/colors'; +@use '@carbon/layout'; +@use '@carbon/type'; + +.attachmentThumbnailSkeleton { + width: 100%; +} .galleryContainer { display: grid; grid-template-columns: repeat(3, 1fr); - grid-gap: spacing.$spacing-05; - padding: spacing.$spacing-05; -} - -.attachmentThumbnailSkeleton { - width: 100%; + grid-gap: layout.$spacing-05; + padding: layout.$spacing-05; } .galleryContainer img { @@ -19,13 +18,13 @@ } .muted { - color: $color-gray-70; - @include type.type-style("label-01"); - margin: 0.25rem; + color: colors.$gray-70; + @include type.type-style('label-01'); + margin: layout.$spacing-02; } .title { - @extend .bodyLong01; + @include type.type-style('body-long-01'); color: colors.$gray-100; - margin: 0.25rem; + margin: layout.$spacing-02; } diff --git a/packages/esm-patient-attachments-app/src/attachments/attachments-overview.scss b/packages/esm-patient-attachments-app/src/attachments/attachments-overview.scss index 2ef0330f64..9287939900 100644 --- a/packages/esm-patient-attachments-app/src/attachments/attachments-overview.scss +++ b/packages/esm-patient-attachments-app/src/attachments/attachments-overview.scss @@ -1,9 +1,9 @@ @use '@carbon/colors'; -@use '@carbon/styles/scss/spacing'; -@import "../root.scss"; +@use '@carbon/layout'; +@use '@openmrs/esm-styleguide/src/vars' as vars; .overview { - background-color: $ui-02; + background-color: vars.$ui-02; } .attachmentHeaderActionItems { @@ -15,16 +15,16 @@ max-width: max-content; :global(.cds--content-switcher__label) { - height: spacing.$spacing-05; + height: layout.$spacing-05; } } } .divider { width: 1px; - height: spacing.$spacing-05; + height: layout.$spacing-05; color: colors.$gray-20; - margin: 0 spacing.$spacing-05; + margin: 0 layout.$spacing-05; } .validatingDataIcon { @@ -33,5 +33,3 @@ flex: 1 1 0%; justify-content: center; } - - diff --git a/packages/esm-patient-attachments-app/src/attachments/attachments-overview.test.tsx b/packages/esm-patient-attachments-app/src/attachments/attachments-overview.test.tsx index bad7f90db0..d9112f7de8 100644 --- a/packages/esm-patient-attachments-app/src/attachments/attachments-overview.test.tsx +++ b/packages/esm-patient-attachments-app/src/attachments/attachments-overview.test.tsx @@ -3,10 +3,10 @@ import { render, screen } from '@testing-library/react'; import AttachmentsOverview from './attachments-overview.component'; import { useAttachments } from '@openmrs/esm-framework'; -const mockedUseAttachments = jest.mocked(useAttachments); +const mockUseAttachments = jest.mocked(useAttachments); it('renders a loading skeleton when attachments are loading', () => { - mockedUseAttachments.mockReturnValue({ + mockUseAttachments.mockReturnValue({ data: [], error: null, isLoading: true, @@ -14,14 +14,14 @@ it('renders a loading skeleton when attachments are loading', () => { mutate: jest.fn(), }); - renderAttachmentsOverview(); + render(); expect(screen.getByRole('progressbar')).toBeInTheDocument(); expect(screen.queryByRole('table')).not.toBeInTheDocument(); }); it('renders an empty state if attachments are not available', () => { - mockedUseAttachments.mockReturnValue({ + mockUseAttachments.mockReturnValue({ data: [], error: null, isLoading: false, @@ -29,12 +29,8 @@ it('renders an empty state if attachments are not available', () => { mutate: jest.fn(), }); - renderAttachmentsOverview(); + render(); expect(screen.getByText(/There are no attachments to display for this patient/i)).toBeInTheDocument(); expect(screen.getByRole('button', { name: /record attachments/i })).toBeInTheDocument(); }); - -function renderAttachmentsOverview() { - render(); -} diff --git a/packages/esm-patient-attachments-app/src/attachments/attachments-table-overview.scss b/packages/esm-patient-attachments-app/src/attachments/attachments-table-overview.scss index 090f7affd4..ac8d5dfa41 100644 --- a/packages/esm-patient-attachments-app/src/attachments/attachments-table-overview.scss +++ b/packages/esm-patient-attachments-app/src/attachments/attachments-table-overview.scss @@ -1,5 +1,3 @@ -@import "../root.scss"; - .attachmentTable > .cds--data-table-header { display: none; } diff --git a/packages/esm-patient-attachments-app/src/attachments/delete-attachment.modal.tsx b/packages/esm-patient-attachments-app/src/attachments/delete-attachment.modal.tsx index 244a5e59c1..bacf522924 100644 --- a/packages/esm-patient-attachments-app/src/attachments/delete-attachment.modal.tsx +++ b/packages/esm-patient-attachments-app/src/attachments/delete-attachment.modal.tsx @@ -19,11 +19,11 @@ const DeleteAttachmentConfirmation: React.FC return ( <> - + {t('delete', 'Delete')} {attachment.bytesContentFamily.toLowerCase()}? -

+

{t( 'deleteAttachmentConfirmationText', `Are you sure you want to delete this {{attachmentType}}? This action can't be undone.`, diff --git a/packages/esm-patient-attachments-app/src/attachments/delete-attachment.scss b/packages/esm-patient-attachments-app/src/attachments/delete-attachment.scss index bf06cb4a78..73840a89d8 100644 --- a/packages/esm-patient-attachments-app/src/attachments/delete-attachment.scss +++ b/packages/esm-patient-attachments-app/src/attachments/delete-attachment.scss @@ -1,2 +1,9 @@ -@use '@carbon/styles/scss/spacing'; -@import "../root.scss"; +@use '@carbon/type'; + +.modalHeader { + @include type.type-style('heading-compact-02'); +} + +.bodyText { + @include type.type-style('body-long-01'); +} diff --git a/packages/esm-patient-attachments-app/src/camera-media-uploader/camera-media-uploader.scss b/packages/esm-patient-attachments-app/src/camera-media-uploader/camera-media-uploader.scss index d1bcfd9de9..14b66e74ec 100644 --- a/packages/esm-patient-attachments-app/src/camera-media-uploader/camera-media-uploader.scss +++ b/packages/esm-patient-attachments-app/src/camera-media-uploader/camera-media-uploader.scss @@ -1,6 +1,5 @@ -@use '@carbon/styles/scss/spacing'; -@import '@openmrs/esm-styleguide/src/vars'; -@import '../root.scss'; +@use '@carbon/layout'; +@use '@openmrs/esm-styleguide/src/vars' as *; .cameraSection { width: 100%; @@ -8,7 +7,8 @@ } .cameraSection :global(.react-html5-camera-photo) { - & > img, & > video { + & > img, + & > video { max-width: 100%; max-height: 100%; } @@ -26,7 +26,6 @@ justify-content: center; align-items: center; } - } .modalBody { @@ -34,14 +33,14 @@ margin: 0; } -@media (max-width: $breakpoint-tablet-max){ +@media (max-width: $breakpoint-tablet-max) { .frameContent { width: 65%; } } .tabList { - border-bottom: 2px solid $ui-03; + border-bottom: layout.$spacing-01 solid $ui-03; div { position: absolute; diff --git a/packages/esm-patient-attachments-app/src/camera-media-uploader/capture-photo.scss b/packages/esm-patient-attachments-app/src/camera-media-uploader/capture-photo.scss index af476b5bc5..cd634cf5b5 100644 --- a/packages/esm-patient-attachments-app/src/camera-media-uploader/capture-photo.scss +++ b/packages/esm-patient-attachments-app/src/camera-media-uploader/capture-photo.scss @@ -1,29 +1,31 @@ +@use '@carbon/layout'; + .container { display: flex; align-items: center; } .buttonCssReset { - max-width: 4rem; - padding: 0; - margin: 0; - border: none; - background: none; - cursor: pointer; + max-width: layout.$spacing-10; + padding: 0; + margin: 0; + border: none; + background: none; + cursor: pointer; } .placeholderIconContainer { - width: 4rem; - height: 4rem; - background-color: #D8D8D8; - display: flex; - align-items: center; - justify-content: center; + width: layout.$spacing-10; + height: layout.$spacing-10; + background-color: #d8d8d8; + display: flex; + align-items: center; + justify-content: center; } .actionButton { flex: 1; - margin-left: 1rem; + margin-left: layout.$spacing-05; } .preview { diff --git a/packages/esm-patient-attachments-app/src/camera-media-uploader/file-review.component.tsx b/packages/esm-patient-attachments-app/src/camera-media-uploader/file-review.component.tsx index 33ab9c9b16..ccc2f5828f 100644 --- a/packages/esm-patient-attachments-app/src/camera-media-uploader/file-review.component.tsx +++ b/packages/esm-patient-attachments-app/src/camera-media-uploader/file-review.component.tsx @@ -1,4 +1,4 @@ -import React, { type SyntheticEvent, useCallback, useEffect, useState, useContext } from 'react'; +import React, { type SyntheticEvent, useCallback, useState, useContext } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Form, ModalBody, ModalFooter, ModalHeader, Stack, TextArea, TextInput } from '@carbon/react'; import { DocumentPdf, DocumentUnknown } from '@carbon/react/icons'; @@ -43,7 +43,7 @@ const FileReviewContainer: React.FC = ({ onCompletion return (

- + {t('addAttachment_title', 'Add Attachment')}{' '} {filesToUpload.length > 1 && `(${currentFile} of ${filesToUpload.length})`} diff --git a/packages/esm-patient-attachments-app/src/camera-media-uploader/file-review.scss b/packages/esm-patient-attachments-app/src/camera-media-uploader/file-review.scss index 9ab7501333..9f5d9df3fd 100644 --- a/packages/esm-patient-attachments-app/src/camera-media-uploader/file-review.scss +++ b/packages/esm-patient-attachments-app/src/camera-media-uploader/file-review.scss @@ -1,15 +1,20 @@ -@use '@carbon/styles/scss/spacing'; -@import '../root.scss'; +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as vars; .filePreviewContainer { - background-color: $ui-02; + background-color: vars.$ui-02; +} + +.modalHeader { + @include type.type-style('heading-compact-02'); } .overview { display: grid; grid-template-columns: 1fr 1fr; - img { + img { max-width: 100%; max-height: 20rem; display: block; @@ -18,16 +23,16 @@ } .imageDetails { - margin-left: 1rem; + margin-left: layout.$spacing-05; } } .captionFrame { - margin-bottom: .3rem; + margin-bottom: 0.3rem; } .buttonSetOverrides { - margin: spacing.$spacing-05 auto 0; + margin: layout.$spacing-05 auto 0; display: grid; grid-template-columns: 1fr 1fr; @@ -37,7 +42,7 @@ } .filePlaceholder { - background-color: $color-gray-30; + background-color: vars.$color-gray-30; display: flex; justify-content: center; align-items: center; @@ -46,7 +51,7 @@ } .filePlaceholder::after { - content: ""; + content: ''; display: block; padding-bottom: 100%; -} \ No newline at end of file +} diff --git a/packages/esm-patient-attachments-app/src/camera-media-uploader/media-uploader.scss b/packages/esm-patient-attachments-app/src/camera-media-uploader/media-uploader.scss index c779198953..210488e407 100644 --- a/packages/esm-patient-attachments-app/src/camera-media-uploader/media-uploader.scss +++ b/packages/esm-patient-attachments-app/src/camera-media-uploader/media-uploader.scss @@ -1,11 +1,12 @@ -@import '../root.scss'; +@use '@carbon/layout'; +@use '@openmrs/esm-styleguide/src/vars' as vars; .errorContainer { - margin: 1rem 0; + margin: layout.$spacing-05 0; } .uploadFile { - color: $ui-02; + color: vars.$ui-02; width: 100%; height: 406px; diff --git a/packages/esm-patient-attachments-app/src/camera-media-uploader/upload-status.component.tsx b/packages/esm-patient-attachments-app/src/camera-media-uploader/upload-status.component.tsx index fe2233190d..4c3a2d1626 100644 --- a/packages/esm-patient-attachments-app/src/camera-media-uploader/upload-status.component.tsx +++ b/packages/esm-patient-attachments-app/src/camera-media-uploader/upload-status.component.tsx @@ -61,7 +61,7 @@ const UploadStatusComponent: React.FC = () => {
diff --git a/packages/esm-patient-attachments-app/src/camera-media-uploader/upload-status.scss b/packages/esm-patient-attachments-app/src/camera-media-uploader/upload-status.scss index 63278c6258..4954a22296 100644 --- a/packages/esm-patient-attachments-app/src/camera-media-uploader/upload-status.scss +++ b/packages/esm-patient-attachments-app/src/camera-media-uploader/upload-status.scss @@ -1,9 +1,13 @@ -@use '@carbon/styles/scss/spacing'; -@import "../root.scss"; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as vars; + +.modalHeader { + @include type.type-style('heading-compact-02'); +} .cameraSection { width: inherit; - background-color: $ui-02; + background-color: vars.$ui-02; } .uploadingFilesSection { @@ -13,14 +17,14 @@ align-items: center; height: 25rem; overflow-y: auto; - border: 1px dashed $ui-04; + border: 1px dashed vars.$ui-04; } .buttonSet { width: 100%; display: grid; grid-template-columns: 1fr 1fr; - button{ + button { max-width: unset !important; } } diff --git a/packages/esm-patient-attachments-app/src/dashboard.meta.ts b/packages/esm-patient-attachments-app/src/dashboard.meta.ts index ea0505453d..eeab51079b 100644 --- a/packages/esm-patient-attachments-app/src/dashboard.meta.ts +++ b/packages/esm-patient-attachments-app/src/dashboard.meta.ts @@ -1,6 +1,5 @@ export const dashboardMeta = { slot: 'patient-chart-attachments-dashboard-slot', - columns: 1, path: 'Attachments', title: 'Attachments', }; diff --git a/packages/esm-patient-attachments-app/src/root.scss b/packages/esm-patient-attachments-app/src/root.scss deleted file mode 100644 index 939d76b96f..0000000000 --- a/packages/esm-patient-attachments-app/src/root.scss +++ /dev/null @@ -1,23 +0,0 @@ -@use '@carbon/styles/scss/spacing'; -@use '@carbon/styles/scss/type'; -@import '@openmrs/esm-styleguide/src/vars'; - -.productiveHeading02 { - @include type.type-style("heading-compact-02"); -} - -.productiveHeading03 { - @include type.type-style("heading-03"); -} - -.bodyLong01 { - @include type.type-style("body-long-01"); -} - -.bodyShort01 { - @include type.type-style("body-compact-01"); -} - -.text02 { - color: $text-02; -} diff --git a/packages/esm-patient-attachments-app/src/routes.json b/packages/esm-patient-attachments-app/src/routes.json index 3cf5629faf..1de27d117a 100644 --- a/packages/esm-patient-attachments-app/src/routes.json +++ b/packages/esm-patient-attachments-app/src/routes.json @@ -15,8 +15,7 @@ "slot": "patient-chart-dashboard-slot", "meta": { "slot": "patient-chart-attachments-dashboard-slot", - "path": "Attachments", - "columns": 1 + "path": "Attachments" }, "order": 9 }, @@ -35,4 +34,4 @@ } ], "pages": [] -} \ No newline at end of file +} diff --git a/packages/esm-patient-banner-app/package.json b/packages/esm-patient-banner-app/package.json index 989e2aad50..23afeee6c4 100644 --- a/packages/esm-patient-banner-app/package.json +++ b/packages/esm-patient-banner-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-banner-app", - "version": "8.0.1", + "version": "8.1.0", "license": "MPL-2.0", "description": "Patient banner microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-banner-app.js", @@ -38,7 +38,7 @@ }, "dependencies": { "@carbon/react": "^1.12.0", - "@openmrs/esm-patient-common-lib": "^8.0.1", + "@openmrs/esm-patient-common-lib": "^8.1.0", "lodash-es": "^4.17.21" }, "peerDependencies": { diff --git a/packages/esm-patient-banner-app/src/banner-tags/deceased-patient-tag.extension.tsx b/packages/esm-patient-banner-app/src/banner-tags/deceased-patient-tag.extension.tsx index 4b53c1156d..42a774c14e 100644 --- a/packages/esm-patient-banner-app/src/banner-tags/deceased-patient-tag.extension.tsx +++ b/packages/esm-patient-banner-app/src/banner-tags/deceased-patient-tag.extension.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { DefinitionTooltip, Tag } from '@carbon/react'; import { formatDatetime, parseDate } from '@openmrs/esm-framework'; -import { useCauseOfDeath } from './useCauseOfDeath'; +import { useCauseOfDeath } from '../hooks/useCauseOfDeath'; import styles from './deceased-patient-tag.scss'; interface DeceasedPatientBannerTagProps { diff --git a/packages/esm-patient-banner-app/src/banner-tags/deceased-patient-tag.scss b/packages/esm-patient-banner-app/src/banner-tags/deceased-patient-tag.scss index beeac217e3..2d68822db3 100644 --- a/packages/esm-patient-banner-app/src/banner-tags/deceased-patient-tag.scss +++ b/packages/esm-patient-banner-app/src/banner-tags/deceased-patient-tag.scss @@ -1,7 +1,8 @@ @use '@carbon/colors'; +@use '@carbon/layout'; .tooltipPadding { - padding: 0.25rem; + padding: layout.$spacing-02; } .tooltipSmallText { diff --git a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-modal.test.tsx b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-modal.test.tsx index a5ae2a24f3..9092801ae8 100644 --- a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-modal.test.tsx +++ b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker-modal.test.tsx @@ -2,11 +2,14 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; import { useReactToPrint } from 'react-to-print'; +import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework'; +import { configSchema, type ConfigObject } from '../config-schema'; import { mockPatient } from 'tools'; import PrintIdentifierSticker from './print-identifier-sticker.modal'; -const mockedCloseModal = jest.fn(); -const mockedUseReactToPrint = jest.mocked(useReactToPrint); +const mockCloseModal = jest.fn(); +const mockUseReactToPrint = jest.mocked(useReactToPrint); +const mockUseConfig = jest.mocked(useConfig); jest.mock('react-to-print', () => { const originalModule = jest.requireActual('react-to-print'); @@ -17,20 +20,14 @@ jest.mock('react-to-print', () => { }; }); -jest.mock('@openmrs/esm-framework', () => { - const originalModule = jest.requireActual('@openmrs/esm-framework'); - - return { - ...originalModule, - useConfig: jest.fn().mockImplementation(() => ({ - printIdentifierStickerFields: ['name', 'identifier', 'age', 'dateOfBirth', 'gender'], - })), - }; +mockUseConfig.mockReturnValue({ + ...getDefaultsFromConfigSchema(configSchema), + printIdentifierStickerFields: ['name', 'identifier', 'age', 'dateOfBirth', 'gender'], }); describe('PrintIdentifierSticker', () => { test('renders the component', () => { - renderPrintIdentifierSticker(); + render(); expect(screen.getByText(/Print Identifier Sticker/i)).toBeInTheDocument(); expect(screen.getByText('John Wilson')).toBeInTheDocument(); @@ -41,22 +38,22 @@ describe('PrintIdentifierSticker', () => { test('calls closeModal when cancel button is clicked', async () => { const user = userEvent.setup(); - renderPrintIdentifierSticker(); + render(); const cancelButton = screen.getByRole('button', { name: /Cancel/i }); expect(cancelButton).toBeInTheDocument(); await user.click(cancelButton); - expect(mockedCloseModal).toHaveBeenCalled(); + expect(mockCloseModal).toHaveBeenCalled(); }); test('calls the print function when print button is clicked', async () => { const handlePrint = jest.fn(); - mockedUseReactToPrint.mockReturnValue(handlePrint); + mockUseReactToPrint.mockReturnValue(handlePrint); const user = userEvent.setup(); - renderPrintIdentifierSticker(); + render(); const printButton = screen.getByRole('button', { name: /Print/i }); expect(printButton).toBeInTheDocument(); @@ -65,6 +62,3 @@ describe('PrintIdentifierSticker', () => { expect(handlePrint).toHaveBeenCalled(); }); }); -function renderPrintIdentifierSticker() { - render(); -} diff --git a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.modal.tsx b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.modal.tsx index 0f7c33461b..47dac29857 100644 --- a/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.modal.tsx +++ b/packages/esm-patient-banner-app/src/banner-tags/print-identifier-sticker.modal.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation, type TFunction } from 'react-i18next'; import { useReactToPrint } from 'react-to-print'; import { Button, InlineLoading, ModalBody, ModalFooter, ModalHeader } from '@carbon/react'; -import { age, getPatientName, showSnackbar, useConfig } from '@openmrs/esm-framework'; +import { age, getPatientName, showSnackbar, useConfig, getCoreTranslation } from '@openmrs/esm-framework'; import { type ConfigObject } from '../config-schema'; import styles from './print-identifier-sticker.scss'; @@ -48,13 +48,13 @@ const PrintIdentifierSticker: React.FC = ({ closeMo const getGender = (gender: string): string => { switch (gender) { case 'male': - return t('male', 'Male'); + return getCoreTranslation('male', 'Male'); case 'female': - return t('female', 'Female'); + return getCoreTranslation('female', 'Female'); case 'other': - return t('other', 'Other'); + return getCoreTranslation('other', 'Other'); case 'unknown': - return t('unknown', 'Unknown'); + return getCoreTranslation('unknown', 'Unknown'); default: return gender; } @@ -75,7 +75,7 @@ const PrintIdentifierSticker: React.FC = ({ closeMo name: patient ? getPatientName(patient) : '', photo: patient.photo, }; - }, [excludePatientIdentifierCodeTypes?.uuids, patient, t]); + }, [excludePatientIdentifierCodeTypes?.uuids, patient]); const handleBeforeGetContent = useCallback( () => @@ -94,21 +94,20 @@ const PrintIdentifierSticker: React.FC = ({ closeMo closeModal(); }, [closeModal]); - const handlePrintError = useCallback( - (errorLocation, error) => { - onBeforeGetContentResolve.current = null; + const handlePrintError = useCallback((errorLocation, error) => { + onBeforeGetContentResolve.current = null; - showSnackbar({ - isLowContrast: false, - kind: 'error', - title: t('printError', 'Print error'), - subtitle: t('printErrorExplainer', 'An error occurred in "{{errorLocation}}": ', { errorLocation }) + error, - }); + showSnackbar({ + isLowContrast: false, + kind: 'error', + title: getCoreTranslation('printError', 'Print error'), + subtitle: + getCoreTranslation('printErrorExplainer', 'An error occurred in "{{errorLocation}}": ', { errorLocation }) + + error, + }); - setIsPrinting(false); - }, - [t], - ); + setIsPrinting(false); + }, []); const handlePrint = useReactToPrint({ content: () => contentToPrintRef.current, @@ -120,7 +119,10 @@ const PrintIdentifierSticker: React.FC = ({ closeMo return ( <> - +