From a0b090d99a210e18e74f91ee6abc6a46cea1dfbd Mon Sep 17 00:00:00 2001 From: Dennis Kigen Date: Wed, 20 Nov 2024 19:48:38 +0300 Subject: [PATCH] (refactor) Refactor reusable patient banner components (#1209) This PR refactors the reusable patient banner components as follows: ### PatientBannerPatientIdentifier - Renames the component export from `PatientBannerPatientIdentifier` to `PatientBannerPatientInfoIdentifiers` to better describe its purpose. The pluralized name is more representative of the fact that it renders a list of identifiers. - Renames the `identifiers` array prop from `identifier` to `identifiers` to better describe its purpose. - Fixes incorrect usage of the `showIdentifierLabel` prop. Per its description, it should toggle the display of the identifier label, not the entire identifier. - Removes the `title` prop from the Carbon `Tag` component which has since been deprecated. - Adds a test for the component. ### PatientBannerPatientInfo - Adds a default export. - Adds a test for the component. ### Misc - Adds a stub for `usePrimaryIdentifierCode` to the `esm-react-utils` mock. I'm not sure why we have a stub for the resource file, instead of just one for the function it exports. I'll look into this more closely. --- packages/framework/esm-framework/docs/API.md | 42 ---------- .../PatientBannerPatientInfoProps.md | 19 ----- packages/framework/esm-react-utils/mock.tsx | 3 + ...t-banner-patient-identifiers.component.tsx | 35 ++++----- ...atient-banner-patient-identifiers.test.tsx | 74 ++++++++++++++++++ .../patient-banner-patient-info.component.tsx | 10 ++- .../patient-banner-patient-info.module.scss | 2 +- .../patient-banner-patient-info.test.tsx | 77 +++++++++++++++++++ 8 files changed, 176 insertions(+), 86 deletions(-) delete mode 100644 packages/framework/esm-framework/docs/interfaces/PatientBannerPatientInfoProps.md create mode 100644 packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.test.tsx create mode 100644 packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.test.tsx diff --git a/packages/framework/esm-framework/docs/API.md b/packages/framework/esm-framework/docs/API.md index b1717c86f..0fa17bc32 100644 --- a/packages/framework/esm-framework/docs/API.md +++ b/packages/framework/esm-framework/docs/API.md @@ -214,8 +214,6 @@ - [CustomOverflowMenu](API.md#customoverflowmenu) - [PatientBannerActionsMenu](API.md#patientbanneractionsmenu) - [PatientBannerContactDetails](API.md#patientbannercontactdetails) -- [PatientBannerPatientIdentifier](API.md#patientbannerpatientidentifier) -- [PatientBannerPatientInfo](API.md#patientbannerpatientinfo) - [PatientBannerToggleContactDetailsButton](API.md#patientbannertogglecontactdetailsbutton) - [PatientPhoto](API.md#patientphoto) - [getFhirServerPaginationHandlers](API.md#getfhirserverpaginationhandlers) @@ -6887,46 +6885,6 @@ ___ ___ -### PatientBannerPatientIdentifier - -▸ **PatientBannerPatientIdentifier**(`__namedParameters`): `Element` - -#### Parameters - -| Name | Type | -| :------ | :------ | -| `__namedParameters` | `PatientBannerPatientIdentifierProps` | - -#### Returns - -`Element` - -#### Defined in - -[packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.component.tsx:41](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.component.tsx#L41) - -___ - -### PatientBannerPatientInfo - -▸ **PatientBannerPatientInfo**(`__namedParameters`): `Element` - -#### Parameters - -| Name | Type | -| :------ | :------ | -| `__namedParameters` | [`PatientBannerPatientInfoProps`](interfaces/PatientBannerPatientInfoProps.md) | - -#### Returns - -`Element` - -#### Defined in - -[packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.component.tsx:43](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.component.tsx#L43) - -___ - ### PatientBannerToggleContactDetailsButton ▸ **PatientBannerToggleContactDetailsButton**(`__namedParameters`): `Element` diff --git a/packages/framework/esm-framework/docs/interfaces/PatientBannerPatientInfoProps.md b/packages/framework/esm-framework/docs/interfaces/PatientBannerPatientInfoProps.md deleted file mode 100644 index e81b69cf2..000000000 --- a/packages/framework/esm-framework/docs/interfaces/PatientBannerPatientInfoProps.md +++ /dev/null @@ -1,19 +0,0 @@ -[@openmrs/esm-framework](../API.md) / PatientBannerPatientInfoProps - -# Interface: PatientBannerPatientInfoProps - -## Table of contents - -### UI Properties - -- [patient](PatientBannerPatientInfoProps.md#patient) - -## UI Properties - -### patient - -• **patient**: `Patient` - -#### Defined in - -[packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.component.tsx:12](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.component.tsx#L12) diff --git a/packages/framework/esm-react-utils/mock.tsx b/packages/framework/esm-react-utils/mock.tsx index e78b8e9a8..d0fcd97b3 100644 --- a/packages/framework/esm-react-utils/mock.tsx +++ b/packages/framework/esm-react-utils/mock.tsx @@ -140,4 +140,7 @@ export const useExtensionSlot = jest.fn(); export const useForceUpdate = jest.fn(); +// TODO: Remove this in favour of usePrimaryIdentifierCode below export const usePrimaryIdentifierResource = jest.fn(); + +export const usePrimaryIdentifierCode = jest.fn(); diff --git a/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.component.tsx b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.component.tsx index 5df8d67b0..ca73266dc 100644 --- a/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.component.tsx +++ b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.component.tsx @@ -5,31 +5,29 @@ import { useConfig, usePrimaryIdentifierCode } from '@openmrs/esm-react-utils'; import { type StyleguideConfigObject } from '../../config-schema'; import styles from './patient-banner-patient-info.module.scss'; -interface IdentifierProps { +interface IdentifiersProps { showIdentifierLabel: boolean; type: fhir.CodeableConcept | undefined; value: string | undefined; } -interface PatientBannerPatientIdentifierProps { - identifier: fhir.Identifier[] | undefined; +interface PatientBannerPatientIdentifiersProps { + identifiers: fhir.Identifier[] | undefined; showIdentifierLabel: boolean; } -function PrimaryIdentifier({ showIdentifierLabel, type, value }: IdentifierProps) { +function PrimaryIdentifier({ showIdentifierLabel, type, value }: IdentifiersProps) { return ( - {showIdentifierLabel && ( - - {type?.text && {type.text}: } - {value} - - )} + + {showIdentifierLabel && type?.text && {type.text}: } + {value} + ); } -function SecondaryIdentifier({ showIdentifierLabel, type, value }: IdentifierProps) { +function SecondaryIdentifier({ showIdentifierLabel, type, value }: IdentifiersProps) { return ( {showIdentifierLabel && {type?.text}: } @@ -38,15 +36,12 @@ function SecondaryIdentifier({ showIdentifierLabel, type, value }: IdentifierPro ); } -export function PatientBannerPatientIdentifier({ - identifier, - showIdentifierLabel, -}: PatientBannerPatientIdentifierProps) { +function PatientBannerPatientIdentifiers({ identifiers, showIdentifierLabel }: PatientBannerPatientIdentifiersProps) { const { excludePatientIdentifierCodeTypes } = useConfig(); const { primaryIdentifierCode } = usePrimaryIdentifierCode(); const filteredIdentifiers = - identifier?.filter((identifier) => { + identifiers?.filter((identifier) => { const code = identifier.type?.coding?.[0]?.code; return code && !excludePatientIdentifierCodeTypes?.uuids.includes(code); }) ?? []; @@ -56,11 +51,11 @@ export function PatientBannerPatientIdentifier({ {filteredIdentifiers?.length ? filteredIdentifiers.map(({ value, type }, index) => ( - + {type?.coding?.[0]?.code === primaryIdentifierCode ? ( - + ) : ( - + )} {index < filteredIdentifiers.length - 1 && ·} @@ -71,4 +66,4 @@ export function PatientBannerPatientIdentifier({ ); } -export default PatientBannerPatientIdentifier; +export default PatientBannerPatientIdentifiers; diff --git a/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.test.tsx b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.test.tsx new file mode 100644 index 000000000..87d2195f3 --- /dev/null +++ b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.test.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { useConfig, usePrimaryIdentifierCode } from '@openmrs/esm-react-utils'; +import PatientBannerPatientIdentifiers from './patient-banner-patient-identifiers.component'; + +const mockUsePrimaryIdentifierCode = jest.mocked(usePrimaryIdentifierCode); +const mockUseConfig = jest.mocked(useConfig); + +describe('PatientBannerPatientIdentifiers', () => { + const mockIdentifiers = [ + { + use: 'official', + type: { + coding: [{ code: '05a29f94-c0ed-11e2-94be-8c13b969e334' }], + text: 'OpenMRS ID', + }, + value: '100GEJ', + }, + { + use: 'official', + type: { + coding: [{ code: '4281ec43-388b-4c25-8bb2-deaff0867b2c' }], + text: 'National ID', + }, + value: '123456789', + }, + ]; + + beforeEach(() => { + mockUsePrimaryIdentifierCode.mockReturnValue({ + primaryIdentifierCode: '05a29f94-c0ed-11e2-94be-8c13b969e334', + isLoading: false, + error: undefined, + }); + mockUseConfig.mockReturnValue({ + excludePatientIdentifierCodeTypes: { uuids: [] }, + }); + }); + + it('renders the patient identifiers', async () => { + render(); + + expect(screen.getByText(/openmrs id/i)).toBeInTheDocument(); + expect(screen.getByText(/100gej/i)).toBeInTheDocument(); + expect(screen.getByText(/national id/i)).toBeInTheDocument(); + expect(screen.getByText(/123456789/i)).toBeInTheDocument(); + }); + + it('does not render identifier labels if showIdentifierLabel is false', () => { + render(); + + expect(screen.queryByText(/openmrs id/i)).not.toBeInTheDocument(); + expect(screen.getByText(/100gej/i)).toBeInTheDocument(); + expect(screen.queryByText(/national id/i)).not.toBeInTheDocument(); + expect(screen.getByText(/123456789/i)).toBeInTheDocument(); + }); + + it('renders nothing if identifiers are not provided', () => { + const { container } = render(); + + expect(container).toBeEmptyDOMElement(); + }); + + it('filters out excluded identifier types', () => { + mockUseConfig.mockReturnValue({ + excludePatientIdentifierCodeTypes: { uuids: ['4281ec43-388b-4c25-8bb2-deaff0867b2c'] }, + }); + + render(); + + expect(screen.queryByText(/openmrs id/i)).toBeInTheDocument(); + expect(screen.queryByText(/national id/i)).not.toBeInTheDocument(); + }); +}); diff --git a/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.component.tsx b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.component.tsx index f6a00661c..398a6fae4 100644 --- a/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.component.tsx +++ b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.component.tsx @@ -5,10 +5,10 @@ import { ExtensionSlot } from '@openmrs/esm-react-utils'; import { getCoreTranslation } from '@openmrs/esm-translations'; import { age, formatDate, parseDate } from '@openmrs/esm-utils'; import { GenderFemaleIcon, GenderMaleIcon, GenderOtherIcon, GenderUnknownIcon } from '../../icons'; -import PatientBannerPatientIdentifier from './patient-banner-patient-identifiers.component'; +import PatientBannerPatientIdentifiers from './patient-banner-patient-identifiers.component'; import styles from './patient-banner-patient-info.module.scss'; -export interface PatientBannerPatientInfoProps { +interface PatientBannerPatientInfoProps { patient: fhir.Patient; } @@ -40,7 +40,7 @@ const getGender = (gender: string): string => { return getCoreTranslation(key, gender); }; -export function PatientBannerPatientInfo({ patient }: PatientBannerPatientInfoProps) { +function PatientBannerPatientInfo({ patient }: PatientBannerPatientInfoProps) { const name = `${patient?.name?.[0]?.given?.join(' ')} ${patient?.name?.[0]?.family}`; const gender = patient?.gender && getGender(patient.gender); @@ -71,8 +71,10 @@ export function PatientBannerPatientInfo({ patient }: PatientBannerPatientInfoPr · )} - + ); } + +export default PatientBannerPatientInfo; diff --git a/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.module.scss b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.module.scss index 1e63b44b1..3464a57a0 100644 --- a/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.module.scss +++ b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.module.scss @@ -81,7 +81,7 @@ align-items: center; } -.identifierTag { +.identifier { display: flex; align-items: center; } diff --git a/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.test.tsx b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.test.tsx new file mode 100644 index 000000000..5aa518e1d --- /dev/null +++ b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.test.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { type i18n } from 'i18next'; +import { screen, render } from '@testing-library/react'; +import { usePrimaryIdentifierCode } from '@openmrs/esm-react-utils'; +import PatientBannerPatientInfo from './patient-banner-patient-info.component'; + +window.i18next = { language: 'en' } as i18n; +const mockUsePrimaryIdentifierCode = jest.mocked(usePrimaryIdentifierCode); + +const nameWithFormat = { + id: 'efdb246f-4142-4c12-a27a-9be60b9592e9', + family: 'Wilson', + given: ['John'], + text: 'Wilson, John', +}; + +const mockPatient = { + resourceType: 'Patient', + id: '8673ee4f-e2ab-4077-ba55-4980f408773e', + extension: [ + { + url: 'http://fhir-es.transcendinsights.com/stu3/StructureDefinition/resource-date-created', + valueDateTime: '2017-01-18T09:42:40+00:00', + }, + { + url: 'https://purl.org/elab/fhir/StructureDefinition/Creator-crew-version1', + valueString: 'daemon', + }, + ], + identifier: [ + { + use: 'official', + type: { + coding: [{ code: '05a29f94-c0ed-11e2-94be-8c13b969e334' }], + text: 'OpenMRS ID', + }, + value: '100GEJ', + }, + { + use: 'official', + type: { + coding: [{ code: '4281ec43-388b-4c25-8bb2-deaff0867b2c' }], + text: 'National ID', + }, + value: '123456789', + }, + ], + active: true, + name: [nameWithFormat], + gender: 'male', + birthDate: '1972-04-04', + deceasedBoolean: false, + address: [], +}; + +describe('PatientBannerPatientInfo', () => { + beforeEach(() => { + mockUsePrimaryIdentifierCode.mockReturnValue({ + primaryIdentifierCode: '05a29f94-c0ed-11e2-94be-8c13b969e334', + isLoading: false, + error: undefined, + }); + }); + + it("renders the patient's name, demographics, and identifier details in the banner", () => { + render(); + + expect(screen.getByText(/john wilson/i)).toBeInTheDocument(); + expect(screen.getByText(/male/i)).toBeInTheDocument(); + expect(screen.getByText(/52 yrs/i)).toBeInTheDocument(); + expect(screen.getByText(/04-Apr-1972/i)).toBeInTheDocument(); + expect(screen.getByText(/openmrs id/i)).toBeInTheDocument(); + expect(screen.getByText(/100gej/i)).toBeInTheDocument(); + expect(screen.getByText(/national id/i)).toBeInTheDocument(); + expect(screen.getByText(/123456789/i)).toBeInTheDocument(); + }); +});