Skip to content

Commit

Permalink
ISSUE-9: Add tests (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
reagan-meant authored Oct 9, 2024
1 parent fb4f027 commit d7ffa28
Show file tree
Hide file tree
Showing 21 changed files with 980 additions and 157 deletions.
9 changes: 6 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ module.exports = {
'!**/e2e/**',
],
transform: {
'^.+\\.tsx?$': ['@swc/jest'],
'^.+\\.(js|jsx|ts|tsx)$': ['@swc/jest'],

},
transformIgnorePatterns: ['/node_modules/(?!@openmrs)'],
transformIgnorePatterns: [
'/node_modules/(?!(@openmrs|uuid)/)',
],
moduleNameMapper: {
'@openmrs/esm-framework': '@openmrs/esm-framework/mock',
'@openmrs/esm-utils': '@openmrs/esm-framework/mock',
Expand All @@ -30,4 +33,4 @@ module.exports = {
testEnvironmentOptions: {
url: 'http://localhost/',
},
};
};
662 changes: 662 additions & 0 deletions src/__mocks__/test.util.ts

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions src/common/history.resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { restBaseUrl } from '@openmrs/esm-framework';
import { openmrsFetch } from '@openmrs/esm-framework';
import useSWR from 'swr';

const useIpsResource = (uuid: string) => {
export function useIpsResource(uuid: string) {
const url = `${restBaseUrl}/patient/${uuid}/patientsummary`;
const { data: history, error, isLoading } = useSWR<{ data: InternationalPatientSummary }, Error>(url, openmrsFetch);
return {
history,
error,
isLoading,
};
};
}

export function createIpsResource(uuid: string, abortController: AbortController) {
const url = `${restBaseUrl}/patient/${uuid}/patientsummary`;
Expand All @@ -26,5 +26,3 @@ export function createIpsResource(uuid: string, abortController: AbortController
},
});
}

export default useIpsResource;
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export const spaRoot = window['getOpenmrsSpaBase']();
export const historyBasePath = '/patient/:patientUuid/chart';
export const historyDashboardPath = `${historyBasePath}/:view/*`;
export const historyDashboardPath = `${historyBasePath}/:view/*`;
1 change: 0 additions & 1 deletion src/dashboard.meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ export const dashboardMeta = {
path: 'Patient History',
title: 'Patient History',
};

70 changes: 34 additions & 36 deletions src/history/history-detail-overview.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import ObservationTemplate from '../templates/observation-template.component';
import { Renew } from '@carbon/react/icons';
import styles from './history-detail-overview.scss';
import { useTranslation } from 'react-i18next';
import useIpsResource, { createIpsResource } from '../common/history.resource';
import { useIpsResource, createIpsResource } from '../common/history.resource';
import { Tile } from '@carbon/react';

interface HistoryDetailOverviewProps {
Expand All @@ -39,49 +39,43 @@ const HistoryDetailOverview: React.FC<HistoryDetailOverviewProps> = () => {
const [ips, setIps] = useState({
history: null,
isLoading: true,
error: "",
})
error: '',
});

useEffect(() => {
if (history != null) {
setIps((ps) => ({...ps, history: history, isLoading: isLoading, error: error?.message}));
setIps((ps) => ({ ...ps, history: history, isLoading: isLoading, error: error?.message }));
}
},[error, history, isLoading])

}, [error, history, isLoading]);

const handleSubmit = () => {
setIsSubmitting(true);
createIpsResource(
uuid,
abortController,
).then((response) => {
if (response.status === 200) {
setIps((ps) => ({...ps, history: history, isLoading: isLoading, error: error?.message}));
createIpsResource(uuid, abortController)
.then((response) => {
setIps((ps) => ({ ...ps, history: history, isLoading: isLoading, error: error?.message }));
setIsSubmitting(false);
showSnackbar({
isLowContrast: true,
kind: 'success',
title: t('ipsCreated', 'IPS'),
subtitle: t('ipsNowAvailable', 'The IPS has been updated and is now visible in the Patient History.'),
});
})
.catch((err) => {
setIps((ps) => ({ ...ps, history: null, isLoading: false, error: err?.message || 'Failed to fetch IPS' }));
setIsSubmitting(false);
showSnackbar({
title: t('ipsCreationError', 'Error updating IPS'),
kind: 'error',
isLowContrast: false,
subtitle: t(
'ipsNowAvailable',
'The IPS has been updated and is now visible in the Patient History.',
'checkForServerAvailability',
'The FHIR server maybe unreachable or the IPS generation process exited with an error!',
),
});
}
})
.catch((err) => {
setIps((ps) => ({...ps, history: null, isLoading: false, error: err?.message || "Failed to fetch IPS"}));
setIsSubmitting(false);
showSnackbar({
title: t('ipsCreationError', 'IPS'),
kind: 'error',
isLowContrast: false,
subtitle: t('checkForServerAvailability', 'The Fhir server maybe unreachable or the IPS generation process exited with an error!'),
});
})
.finally(() => {
abortController.abort();
});
console.error(err?.message);
})
.finally(() => {});
};

return (
Expand All @@ -95,13 +89,14 @@ const HistoryDetailOverview: React.FC<HistoryDetailOverviewProps> = () => {
<ErrorState headerTitle={t('patientHistory', 'Patient History')} error={error} />
) : ips?.history?.data?.entry ? (
<Tile className={styles.tile}>
<Button
<Button
className={styles.button}
kind="ghost"
renderIcon={Renew}
onClick={handleSubmit}
disabled={isSubmitting}
type="submit">
type="submit"
>
{t('refresh', 'Refresh')}
</Button>
{ips?.history?.data?.entry
Expand Down Expand Up @@ -129,20 +124,23 @@ const HistoryDetailOverview: React.FC<HistoryDetailOverviewProps> = () => {
})()}
</div>
))}

</Tile>
) : (
<Tile className={styles.tile}>
<Button
<Button
className={styles.button}
kind="ghost"
renderIcon={Renew}
onClick={handleSubmit}
disabled={isSubmitting}
type="submit">
type="submit"
>
{t('refresh', 'Refresh')}
</Button>
<EmptyState headerTitle={t('patientHistory', 'Patient History')} displayText={t('patientHistory', 'Patient History')}/>
<EmptyState
headerTitle={t('patientHistory', 'Patient History')}
displayText={t('patientHistory', 'Patient History')}
/>
</Tile>
)}
</TabPanel>
Expand Down
6 changes: 3 additions & 3 deletions src/history/patient-history.component.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react';
import React from 'react';
import { useParams } from 'react-router-dom';
import HistoryDetailOverview from './history-detail-overview.component';

const PatientHistory: React.FC = () => {
const { patientUuid } = useParams();
return (
<>
<HistoryDetailOverview patientUuid={patientUuid} />
<HistoryDetailOverview patientUuid={patientUuid} />
</>
);
}
};

export default PatientHistory;
143 changes: 143 additions & 0 deletions src/history/patient-history.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { useParams } from 'react-router-dom';
import HistoryDetailOverview from './history-detail-overview.component';
import React from 'react';
import { FetchResponse, usePatient } from '@openmrs/esm-framework';
import { createIpsResource, useIpsResource } from '../common/history.resource';
import { mockIPS, mockPUTResponse } from '../__mocks__/test.util';

const mockUsePatient = jest.mocked(usePatient);
const mockUseIpsResource = jest.mocked(useIpsResource);

jest.mock('../common/history.resource');

jest.mock('react-router-dom', () => ({
...(jest.requireActual('react-router-dom') as any),
useLocation: jest.fn().mockReturnValue({
pathname: 'openmrs/spa/patient',
}),
useHistory: () => [],
useParams: jest.fn().mockReturnValue({ patientUuid: 'undefined' }),
}));

describe('Display patient IPS', () => {
beforeEach(() => {
const mockUseParams = jest.mocked(useParams);
mockUseParams.mockReturnValue({ patientUuid: 'mock-uuid' });
});

it('renders without crashing', () => {
mockUsePatient.mockReturnValue({
isLoading: false,
patient: undefined,
patientUuid: 'mock-uuid',
error: null,
});

mockUseIpsResource.mockReturnValue({
isLoading: false,
history: { data: null },
error: undefined,
});
render(<HistoryDetailOverview patientUuid={'mock-uuid'} />, {});
});

it('renders loading before resolving ips', async () => {
mockUsePatient.mockReturnValue({
isLoading: false,
patient: undefined,
patientUuid: 'mock-uuid',
error: null,
});

mockUseIpsResource.mockReturnValue({
isLoading: true,
history: { data: null },
error: undefined,
});
render(<HistoryDetailOverview patientUuid={'mock-uuid'} />, {});

expect(screen.getByText('Loading ...')).toBeInTheDocument();
expect(screen.getByRole('progressbar')).toBeInTheDocument();
});

it('renders the ips', async () => {
mockUsePatient.mockReturnValue({
isLoading: false,
patient: undefined,
patientUuid: 'mock-uuid',
error: null,
});

mockUseIpsResource.mockReturnValue({
isLoading: false,
history: { data: mockIPS },
error: undefined,
});

render(<HistoryDetailOverview patientUuid={'mock-uuid'} />, {});

expect(screen.getByText('Refresh')).toBeInTheDocument();
expect(screen.getByText('AllergyIntolerance')).toBeInTheDocument();
expect(screen.getByText('Condition')).toBeInTheDocument();
expect(screen.getByText('Immunization')).toBeInTheDocument();
expect(screen.getByText('Observation')).toBeInTheDocument();

expect(screen.getByText('Penicillin allergy')).toBeInTheDocument();
expect(screen.getByText('Diabetes mellitus type 2')).toBeInTheDocument();
expect(screen.getByText('COVID-19 Vaccine')).toBeInTheDocument();
expect(screen.getByText('Blood pressure panel with all children optional')).toBeInTheDocument();
});

it('refresh button triggers downloading new ips', async () => {
const mockCreateIpsResource = jest.mocked(createIpsResource);

mockCreateIpsResource.mockReturnValue({
then: function <TResult1 = FetchResponse<unknown>, TResult2 = never>(
onfulfilled?: (value: FetchResponse<unknown>) => TResult1 | PromiseLike<TResult1>,
onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>,
): Promise<TResult1 | TResult2> {
return Promise.resolve(onfulfilled ? onfulfilled(mockPUTResponse) : (mockPUTResponse as TResult1));
},
catch: function <TResult = never>(
onrejected?: (reason: any) => TResult | PromiseLike<TResult>,
): Promise<FetchResponse<unknown> | TResult> {
throw new Error('Function not implemented.');
},
finally: function (onfinally?: () => void): Promise<FetchResponse<unknown>> {
return Promise.resolve(mockPUTResponse);
},
[Symbol.toStringTag]: '',
});

mockUsePatient.mockReturnValue({
isLoading: false,
patient: undefined,
patientUuid: 'mock-uuid',
error: null,
});

mockUseIpsResource.mockReturnValue({
isLoading: false,
history: { data: mockIPS },
error: undefined,
});

render(<HistoryDetailOverview patientUuid={'mock-uuid'} />, {});

expect(screen.getByText('Refresh')).toBeInTheDocument();
expect(screen.getByText('AllergyIntolerance')).toBeInTheDocument();
expect(screen.getByText('Condition')).toBeInTheDocument();
expect(screen.getByText('Immunization')).toBeInTheDocument();
expect(screen.getByText('Observation')).toBeInTheDocument();

expect(screen.getByText('Penicillin allergy')).toBeInTheDocument();
expect(screen.getByText('Diabetes mellitus type 2')).toBeInTheDocument();
expect(screen.getByText('COVID-19 Vaccine')).toBeInTheDocument();
expect(screen.getByText('Blood pressure panel with all children optional')).toBeInTheDocument();

fireEvent.click(screen.getByText('Refresh'));

expect(mockCreateIpsResource).toHaveBeenCalled();
});
});
4 changes: 2 additions & 2 deletions src/root.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const Root: React.FC = () => {
<div className={styles.patientHistoryWrapper}>
<BrowserRouter basename={spaRoot}>
<Routes>
<Route path={historyBasePath} element={<PatientHistory/>} />
<Route path={historyDashboardPath} element={<PatientHistory/>} />
<Route path={historyBasePath} element={<PatientHistory />} />
<Route path={historyDashboardPath} element={<PatientHistory />} />
</Routes>
</BrowserRouter>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/templates/allergies-template.component .tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import * as Constants from '../constants/constants';
import { type InternationalPatientSummary } from '../types';
import { type InternationalPatientSummaryResource } from '../types';
import {
StructuredListBody,
StructuredListCell,
Expand All @@ -12,7 +12,7 @@ import {
import styles from '../history/history-detail-overview.scss';
import { useTranslation } from 'react-i18next';

const AllergiesTemplate = (entry: InternationalPatientSummary) => {
const AllergiesTemplate = (entry: InternationalPatientSummaryResource) => {
const { t } = useTranslation();
const interpretation: string = entry?.resource?.criticality;
const flaggedCritical = interpretation && interpretation == Constants.CRITICAL_INTERPRETATION;
Expand Down
Loading

0 comments on commit d7ffa28

Please sign in to comment.