Skip to content

Commit

Permalink
(refactor) Refactor reusable patient banner components (#1209)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
denniskigen authored Nov 20, 2024
1 parent 4286482 commit a0b090d
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 86 deletions.
42 changes: 0 additions & 42 deletions packages/framework/esm-framework/docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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`
Expand Down

This file was deleted.

3 changes: 3 additions & 0 deletions packages/framework/esm-react-utils/mock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<span className={styles.primaryIdentifier}>
{showIdentifierLabel && (
<Tag className={styles.tag} type="gray" title={type?.text}>
{type?.text && <span className={styles.label}>{type.text}: </span>}
<span className={styles.value}>{value}</span>
</Tag>
)}
<Tag className={styles.tag} type="gray">
{showIdentifierLabel && type?.text && <span className={styles.label}>{type.text}: </span>}
<span className={styles.value}>{value}</span>
</Tag>
</span>
);
}

function SecondaryIdentifier({ showIdentifierLabel, type, value }: IdentifierProps) {
function SecondaryIdentifier({ showIdentifierLabel, type, value }: IdentifiersProps) {
return (
<FormLabel className={styles.secondaryIdentifier} id={`patient-banner-identifier-${value}`}>
{showIdentifierLabel && <span className={styles.label}>{type?.text}: </span>}
Expand All @@ -38,15 +36,12 @@ function SecondaryIdentifier({ showIdentifierLabel, type, value }: IdentifierPro
);
}

export function PatientBannerPatientIdentifier({
identifier,
showIdentifierLabel,
}: PatientBannerPatientIdentifierProps) {
function PatientBannerPatientIdentifiers({ identifiers, showIdentifierLabel }: PatientBannerPatientIdentifiersProps) {
const { excludePatientIdentifierCodeTypes } = useConfig<StyleguideConfigObject>();
const { primaryIdentifierCode } = usePrimaryIdentifierCode();

const filteredIdentifiers =
identifier?.filter((identifier) => {
identifiers?.filter((identifier) => {
const code = identifier.type?.coding?.[0]?.code;
return code && !excludePatientIdentifierCodeTypes?.uuids.includes(code);
}) ?? [];
Expand All @@ -56,11 +51,11 @@ export function PatientBannerPatientIdentifier({
{filteredIdentifiers?.length
? filteredIdentifiers.map(({ value, type }, index) => (
<React.Fragment key={value}>
<span className={styles.identifierTag}>
<span className={styles.identifier}>
{type?.coding?.[0]?.code === primaryIdentifierCode ? (
<PrimaryIdentifier value={value} type={type} showIdentifierLabel={showIdentifierLabel} />
<PrimaryIdentifier showIdentifierLabel={showIdentifierLabel} type={type} value={value} />
) : (
<SecondaryIdentifier value={value} type={type} showIdentifierLabel={showIdentifierLabel} />
<SecondaryIdentifier showIdentifierLabel={showIdentifierLabel} type={type} value={value} />
)}
</span>
{index < filteredIdentifiers.length - 1 && <span className={styles.separator}>&middot;</span>}
Expand All @@ -71,4 +66,4 @@ export function PatientBannerPatientIdentifier({
);
}

export default PatientBannerPatientIdentifier;
export default PatientBannerPatientIdentifiers;
Original file line number Diff line number Diff line change
@@ -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(<PatientBannerPatientIdentifiers identifiers={mockIdentifiers} showIdentifierLabel />);

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(<PatientBannerPatientIdentifiers identifiers={mockIdentifiers} showIdentifierLabel={false} />);

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(<PatientBannerPatientIdentifiers identifiers={[]} showIdentifierLabel />);

expect(container).toBeEmptyDOMElement();
});

it('filters out excluded identifier types', () => {
mockUseConfig.mockReturnValue({
excludePatientIdentifierCodeTypes: { uuids: ['4281ec43-388b-4c25-8bb2-deaff0867b2c'] },
});

render(<PatientBannerPatientIdentifiers identifiers={mockIdentifiers} showIdentifierLabel />);

expect(screen.queryByText(/openmrs id/i)).toBeInTheDocument();
expect(screen.queryByText(/national id/i)).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -71,8 +71,10 @@ export function PatientBannerPatientInfo({ patient }: PatientBannerPatientInfoPr
<span className={styles.separator}>&middot;</span>
</>
)}
<PatientBannerPatientIdentifier identifier={patient.identifier} showIdentifierLabel={true} />
<PatientBannerPatientIdentifiers identifiers={patient.identifier} showIdentifierLabel />
</div>
</div>
);
}

export default PatientBannerPatientInfo;
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
align-items: center;
}

.identifierTag {
.identifier {
display: flex;
align-items: center;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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(<PatientBannerPatientInfo patient={mockPatient} />);

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();
});
});

0 comments on commit a0b090d

Please sign in to comment.