@openmrs/esm-patient-programs-app 3.0.0-pre.65 → 3.0.0-pre.656

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/133.openmrs-esm-patient-programs-app.js +3 -0
  2. package/dist/133.openmrs-esm-patient-programs-app.js.LICENSE.txt +14 -0
  3. package/dist/133.openmrs-esm-patient-programs-app.js.map +1 -0
  4. package/dist/401.openmrs-esm-patient-programs-app.js +3 -0
  5. package/dist/401.openmrs-esm-patient-programs-app.js.LICENSE.txt +14 -0
  6. package/dist/401.openmrs-esm-patient-programs-app.js.map +1 -0
  7. package/dist/574.openmrs-esm-patient-programs-app.js +1 -0
  8. package/dist/639.openmrs-esm-patient-programs-app.js +2 -0
  9. package/dist/639.openmrs-esm-patient-programs-app.js.map +1 -0
  10. package/dist/757.openmrs-esm-patient-programs-app.js +1 -0
  11. package/dist/783.openmrs-esm-patient-programs-app.js +3 -0
  12. package/dist/783.openmrs-esm-patient-programs-app.js.LICENSE.txt +23 -0
  13. package/dist/783.openmrs-esm-patient-programs-app.js.map +1 -0
  14. package/dist/839.openmrs-esm-patient-programs-app.js +2 -0
  15. package/dist/839.openmrs-esm-patient-programs-app.js.map +1 -0
  16. package/dist/972.openmrs-esm-patient-programs-app.js +2 -0
  17. package/dist/972.openmrs-esm-patient-programs-app.js.map +1 -0
  18. package/dist/openmrs-esm-patient-programs-app.js +2 -1
  19. package/dist/openmrs-esm-patient-programs-app.js.LICENSE.txt +5 -0
  20. package/dist/openmrs-esm-patient-programs-app.js.buildmanifest.json +220 -0
  21. package/dist/openmrs-esm-patient-programs-app.js.map +1 -1
  22. package/package.json +10 -11
  23. package/src/dashboard.meta.tsx +6 -0
  24. package/src/declarations.d.tsx +3 -3
  25. package/src/index.ts +31 -30
  26. package/src/programs/program-record.component.tsx +29 -48
  27. package/src/programs/program-record.test.outdated.tsx +30 -34
  28. package/src/programs/programs-detailed-summary.component.tsx +123 -114
  29. package/src/programs/programs-detailed-summary.scss +24 -0
  30. package/src/programs/programs-detailed-summary.test.tsx +71 -0
  31. package/src/programs/programs-form.component.tsx +257 -473
  32. package/src/programs/programs-form.scss +20 -0
  33. package/src/programs/programs-form.test.tsx +206 -0
  34. package/src/programs/programs-overview.component.tsx +44 -74
  35. package/src/programs/programs-overview.scss +4 -0
  36. package/src/programs/programs-overview.test.tsx +70 -0
  37. package/src/programs/programs.component.test.tsx +21 -0
  38. package/src/programs/programs.component.tsx +6 -10
  39. package/src/programs/programs.context.tsx +1 -1
  40. package/src/programs/programs.resource.tsx +31 -43
  41. package/translations/en.json +29 -1
  42. package/webpack.config.js +1 -80
  43. package/dist/0.openmrs-esm-patient-programs-app.js +0 -22
  44. package/dist/0.openmrs-esm-patient-programs-app.js.map +0 -1
  45. package/dist/1.openmrs-esm-patient-programs-app.js +0 -2
  46. package/dist/1.openmrs-esm-patient-programs-app.js.map +0 -1
  47. package/dist/3.openmrs-esm-patient-programs-app.js +0 -23
  48. package/dist/3.openmrs-esm-patient-programs-app.js.map +0 -1
  49. package/dist/4.openmrs-esm-patient-programs-app.js +0 -2
  50. package/dist/4.openmrs-esm-patient-programs-app.js.map +0 -1
  51. package/dist/5.openmrs-esm-patient-programs-app.js +0 -2
  52. package/dist/5.openmrs-esm-patient-programs-app.js.map +0 -1
  53. package/dist/6.openmrs-esm-patient-programs-app.js +0 -2
  54. package/dist/6.openmrs-esm-patient-programs-app.js.map +0 -1
  55. package/dist/7.openmrs-esm-patient-programs-app.js +0 -2
  56. package/dist/7.openmrs-esm-patient-programs-app.js.map +0 -1
  57. package/dist/8.openmrs-esm-patient-programs-app.js +0 -2
  58. package/dist/8.openmrs-esm-patient-programs-app.js.map +0 -1
  59. package/src/cards/summary-card.component.tsx +0 -82
  60. package/src/cards/summary-card.css +0 -65
  61. package/src/dashboard/dashboard-link.component.tsx +0 -7
  62. package/src/dashboard/dashboard.meta.tsx +0 -6
  63. package/src/openmrs-backend-dependencies.ts +0 -4
  64. package/src/programs/empty-state/empty-data-illustration.component.tsx +0 -53
  65. package/src/programs/empty-state/empty-state.component.tsx +0 -39
  66. package/src/programs/empty-state/empty-state.scss +0 -34
  67. package/src/programs/error-state/error-state.component.tsx +0 -26
  68. package/src/programs/error-state/error-state.scss +0 -36
  69. package/src/programs/openWorkspaceTab.tsx +0 -40
  70. package/src/programs/programs-detailed-summary.css +0 -63
  71. package/src/programs/programs-detailed-summary.test.outdated.tsx +0 -57
  72. package/src/programs/programs-form.css +0 -95
  73. package/src/programs/programs-form.test.outdated.tsx +0 -276
  74. package/src/programs/programs-overview.test.outdated.tsx +0 -73
  75. package/src/programs/programs.component.test.outdated.tsx +0 -18
  76. package/src/set-public-path.ts +0 -3
@@ -0,0 +1,20 @@
1
+ @import "~@openmrs/esm-styleguide/src/vars";
2
+ @import "~carbon-components/src/globals/scss/vars";
3
+ @import "~carbon-components/src/globals/scss/mixins";
4
+
5
+ .alreadyEnrolledText {
6
+ @include carbon--type-style("body-short-01");
7
+ color: $text-02;
8
+ min-height: 1rem;
9
+ }
10
+
11
+ .selectContainer {
12
+ display: flex;
13
+ align-items: center;
14
+ }
15
+
16
+ .loading {
17
+ margin-left: 1rem;
18
+ width: 2rem;
19
+ min-height: 2.5rem;
20
+ }
@@ -0,0 +1,206 @@
1
+ import React from 'react';
2
+ import dayjs from 'dayjs';
3
+ import { of } from 'rxjs/internal/observable/of';
4
+ import { throwError } from 'rxjs';
5
+ import { fireEvent, render, screen } from '@testing-library/react';
6
+ import { createErrorHandler, detach, showNotification, showToast } from '@openmrs/esm-framework';
7
+ import { mockPatient } from '../../../../__mocks__/patient.mock';
8
+ import {
9
+ mockCareProgramsResponse,
10
+ mockEnrolledProgramsResponse,
11
+ mockLocationsResponse,
12
+ } from '../../../../__mocks__/programs.mock';
13
+ import {
14
+ createProgramEnrollment,
15
+ fetchAvailablePrograms,
16
+ fetchEnrolledPrograms,
17
+ fetchLocations,
18
+ } from './programs.resource';
19
+ import ProgramsForm from './programs-form.component';
20
+
21
+ const testProps = {
22
+ patientUuid: mockPatient.id,
23
+ isTablet: false,
24
+ };
25
+
26
+ const mockCreateErrorHandler = createErrorHandler as jest.Mock;
27
+ const mockCreateProgramEnrollment = createProgramEnrollment as jest.Mock;
28
+ const mockDetach = detach as jest.Mock;
29
+ const mockFetchAvailablePrograms = fetchAvailablePrograms as jest.Mock;
30
+ const mockFetchEnrolledPrograms = fetchEnrolledPrograms as jest.Mock;
31
+ const mockFetchLocations = fetchLocations as jest.Mock;
32
+ const mockShowNotification = showNotification as jest.Mock;
33
+ const mockShowToast = showToast as jest.Mock;
34
+
35
+ jest.mock('@openmrs/esm-framework', () => {
36
+ const originalModule = jest.requireActual('@openmrs/esm-framework');
37
+
38
+ return {
39
+ ...originalModule,
40
+ createErrorHandler: jest.fn(),
41
+ detach: jest.fn(),
42
+ showNotification: jest.fn(),
43
+ showToast: jest.fn(),
44
+ };
45
+ });
46
+
47
+ jest.mock('./programs.resource', () => ({
48
+ createProgramEnrollment: jest.fn(),
49
+ fetchAvailablePrograms: jest.fn(),
50
+ fetchEnrolledPrograms: jest.fn(),
51
+ fetchLocations: jest.fn(),
52
+ }));
53
+
54
+ describe('ProgramsForm: ', () => {
55
+ beforeEach(() => {
56
+ mockFetchAvailablePrograms.mockReturnValue(of(mockCareProgramsResponse));
57
+ mockFetchEnrolledPrograms.mockReturnValue(of(mockEnrolledProgramsResponse));
58
+ mockFetchLocations.mockReturnValue(of(mockLocationsResponse));
59
+ });
60
+
61
+ it('renders the programs form with all the relevant fields and values', async () => {
62
+ renderProgramsForm();
63
+
64
+ expect(screen.getByRole('group', { name: /Program/i })).toBeInTheDocument();
65
+ expect(screen.getByRole('group', { name: /Date enrolled/i })).toBeInTheDocument();
66
+ expect(screen.getByRole('group', { name: /Date completed/i })).toBeInTheDocument();
67
+ expect(screen.getByRole('group', { name: /Enrollment location/i })).toBeInTheDocument();
68
+ expect(screen.getByRole('option', { name: /Choose a program/i })).toBeInTheDocument();
69
+ expect(screen.getByRole('option', { name: /Oncology Screening and Diagnosis/i })).toBeInTheDocument();
70
+ expect(screen.getByRole('option', { name: /Choose a location/i })).toBeInTheDocument();
71
+ expect(screen.getByRole('option', { name: /Amani Hospital/i })).toBeInTheDocument();
72
+ expect(screen.getByRole('option', { name: /Inpatient Ward/i })).toBeInTheDocument();
73
+ expect(screen.getByRole('option', { name: /Isolation Ward/i })).toBeInTheDocument();
74
+ expect(screen.getByRole('option', { name: /Laboratory/i })).toBeInTheDocument();
75
+
76
+ const cancelButton = screen.getByRole('button', { name: /cancel/i });
77
+ const enrollButton = screen.getByRole('button', { name: /enroll/i });
78
+ expect(cancelButton).toBeInTheDocument();
79
+ expect(enrollButton).toBeInTheDocument();
80
+ expect(enrollButton).toBeDisabled();
81
+ });
82
+
83
+ it('closes the form and the workspace when the cancel button is clicked', () => {
84
+ renderProgramsForm();
85
+
86
+ const cancelButton = screen.getByRole('button', { name: /Cancel/i });
87
+ fireEvent.click(cancelButton);
88
+
89
+ expect(mockDetach).toHaveBeenCalledTimes(1);
90
+ expect(mockDetach).toHaveBeenCalledWith('patient-chart-workspace-slot', 'programs-form-workspace');
91
+ });
92
+
93
+ it('renders a light background for date inputs in the tablet viewport ', () => {
94
+ testProps.isTablet = true;
95
+
96
+ renderProgramsForm();
97
+
98
+ expect(screen.getAllByPlaceholderText('dd/mm/yyyy')[0]).toHaveClass('bx--date-picker--light', { exact: false });
99
+ expect(screen.getAllByPlaceholderText('dd/mm/yyyy')[1]).toHaveClass('bx--date-picker--light', { exact: false });
100
+ });
101
+
102
+ describe('Form submission: ', () => {
103
+ let cancelButton: HTMLElement;
104
+ let enrollButton: HTMLElement;
105
+ let enrollmentDateInput: HTMLElement;
106
+ let selectLocationInput: HTMLElement;
107
+ let selectProgramInput: HTMLElement;
108
+ const inpatientWardUuid = 'b1a8b05e-3542-4037-bbd3-998ee9c40574';
109
+ const oncologyScreeningProgramUuid = '11b129ca-a5e7-4025-84bf-b92a173e20de';
110
+
111
+ beforeEach(() => {
112
+ renderProgramsForm();
113
+
114
+ cancelButton = screen.getByRole('button', { name: /cancel/i });
115
+ enrollButton = screen.getByRole('button', { name: /enroll/i });
116
+ enrollmentDateInput = screen.getAllByRole('textbox', { name: '' })[0];
117
+ selectLocationInput = screen.getAllByRole('combobox', { name: '' })[1];
118
+ selectProgramInput = screen.getAllByRole('combobox', { name: '' })[0];
119
+ });
120
+
121
+ it('renders a success toast notification upon successfully recording a program enrollment', () => {
122
+ mockCreateProgramEnrollment.mockReturnValueOnce(of({ status: 201, statusText: 'Created' }));
123
+
124
+ fireEvent.change(selectProgramInput, {
125
+ target: { value: oncologyScreeningProgramUuid },
126
+ });
127
+ expect(screen.getByDisplayValue('Oncology Screening and Diagnosis')).toBeInTheDocument();
128
+
129
+ fireEvent.change(enrollmentDateInput, { target: { value: '2020-05-05' } });
130
+ expect(screen.getByDisplayValue(renderDate('2020-05-05'))).toBeInTheDocument();
131
+
132
+ fireEvent.change(selectLocationInput, {
133
+ target: { value: inpatientWardUuid },
134
+ });
135
+ expect(screen.getByDisplayValue('Inpatient Ward')).toBeInTheDocument();
136
+
137
+ expect(enrollButton).not.toBeDisabled();
138
+ fireEvent.click(enrollButton);
139
+
140
+ expect(mockCreateProgramEnrollment).toHaveBeenCalledTimes(1);
141
+ expect(mockCreateProgramEnrollment).toHaveBeenCalledWith(
142
+ expect.objectContaining({
143
+ dateCompleted: null,
144
+ // dateEnrolled: '2020-05-05',
145
+ location: inpatientWardUuid,
146
+ patient: mockPatient.id,
147
+ program: oncologyScreeningProgramUuid,
148
+ }),
149
+ new AbortController(),
150
+ );
151
+
152
+ expect(mockShowToast).toHaveBeenCalledTimes(1);
153
+ expect(mockShowToast).toHaveBeenCalledWith(
154
+ expect.objectContaining({
155
+ description: 'Program enrollment saved successfully',
156
+ kind: 'success',
157
+ }),
158
+ );
159
+ });
160
+
161
+ it('renders an error notification if there was a problem recording a program enrollment', async () => {
162
+ const error = {
163
+ message: 'Internal Server Error',
164
+ response: {
165
+ status: 500,
166
+ statusText: 'Internal Server Error',
167
+ },
168
+ };
169
+
170
+ mockCreateProgramEnrollment.mockReturnValueOnce(throwError(error));
171
+
172
+ fireEvent.change(selectProgramInput, {
173
+ target: { value: oncologyScreeningProgramUuid },
174
+ });
175
+ expect(screen.getByDisplayValue('Oncology Screening and Diagnosis')).toBeInTheDocument();
176
+
177
+ fireEvent.change(enrollmentDateInput, { target: { value: '2020-05-05' } });
178
+ expect(screen.getByDisplayValue(renderDate('2020-05-05'))).toBeInTheDocument();
179
+
180
+ fireEvent.change(selectLocationInput, {
181
+ target: { value: inpatientWardUuid },
182
+ });
183
+ expect(screen.getByDisplayValue('Inpatient Ward')).toBeInTheDocument();
184
+
185
+ expect(enrollButton).not.toBeDisabled();
186
+ fireEvent.click(enrollButton);
187
+
188
+ expect(mockCreateErrorHandler).toHaveBeenCalledTimes(1);
189
+ expect(mockShowNotification).toHaveBeenCalledTimes(1);
190
+ expect(mockShowNotification).toHaveBeenCalledWith({
191
+ critical: true,
192
+ description: 'Internal Server Error',
193
+ kind: 'error',
194
+ title: 'Error saving program enrollment',
195
+ });
196
+ });
197
+ });
198
+ });
199
+
200
+ function renderProgramsForm() {
201
+ render(<ProgramsForm {...testProps} />);
202
+ }
203
+
204
+ function renderDate(time: string) {
205
+ return dayjs(time).format('YYYY-MM-DD');
206
+ }
@@ -1,13 +1,13 @@
1
- import React from "react";
2
- import dayjs from "dayjs";
3
- import ProgramsForm from "./programs-form.component";
4
- import EmptyState from "./empty-state/empty-state.component";
5
- import ErrorState from "./error-state/error-state.component";
6
- import Add16 from "@carbon/icons-react/es/add/16";
7
- import Button from "carbon-components-react/es/components/Button";
8
- import Pagination from "carbon-components-react/es/components/Pagination";
9
- import DataTableSkeleton from "carbon-components-react/es/components/DataTableSkeleton";
10
- import DataTable, {
1
+ import React from 'react';
2
+ import dayjs from 'dayjs';
3
+ import Add16 from '@carbon/icons-react/es/add/16';
4
+ import styles from './programs-overview.scss';
5
+ import { attach } from '@openmrs/esm-framework';
6
+ import {
7
+ DataTable,
8
+ Pagination,
9
+ DataTableSkeleton,
10
+ Button,
11
11
  Table,
12
12
  TableCell,
13
13
  TableContainer,
@@ -15,13 +15,11 @@ import DataTable, {
15
15
  TableHead,
16
16
  TableHeader,
17
17
  TableRow,
18
- } from "carbon-components-react/es/components/DataTable";
19
- import styles from "./programs-overview.scss";
20
- import { useTranslation } from "react-i18next";
21
- import { createErrorHandler } from "@openmrs/esm-framework";
22
- import { fetchActiveEnrollments } from "./programs.resource";
23
- import { openWorkspaceTab } from "./openWorkspaceTab";
24
- import { PatientProgram } from "../types";
18
+ } from 'carbon-components-react';
19
+ import { EmptyState, ErrorState } from '@openmrs/esm-patient-common-lib';
20
+ import { useTranslation } from 'react-i18next';
21
+ import { fetchActiveEnrollments } from './programs.resource';
22
+ import { PatientProgram } from '../types';
25
23
 
26
24
  interface ProgramsOverviewProps {
27
25
  basePath: string;
@@ -36,78 +34,59 @@ const ProgramsOverview: React.FC<ProgramsOverviewProps> = ({ patientUuid }) => {
36
34
  const [firstRowIndex, setFirstRowIndex] = React.useState(0);
37
35
  const [currentPageSize, setCurrentPageSize] = React.useState(5);
38
36
 
39
- const displayText = t("programs", "program enrollments");
40
- const headerTitle = t("carePrograms", "Care Programs");
41
- const previousPage = t("previousPage", "Previous page");
42
- const nextPage = t("nextPage", "Next Page");
43
- const itemPerPage = t("itemPerPage", "Item per page");
37
+ const displayText = t('programs', 'Program enrollments');
38
+ const headerTitle = t('carePrograms', 'Care Programs');
39
+ const previousPage = t('previousPage', 'Previous page');
40
+ const nextPage = t('nextPage', 'Next Page');
41
+ const itemsPerPage = t('itemsPerPage', 'Items per page');
44
42
 
45
43
  React.useEffect(() => {
46
44
  if (patientUuid) {
47
- const sub = fetchActiveEnrollments(patientUuid).subscribe(
48
- (programs) => setPrograms(programs),
49
- (error) => {
50
- setError(error);
51
- createErrorHandler();
52
- }
53
- );
45
+ const sub = fetchActiveEnrollments(patientUuid).subscribe((programs) => setPrograms(programs), setError);
54
46
 
55
47
  return () => sub.unsubscribe();
56
48
  }
57
49
  }, [patientUuid]);
58
50
 
59
- const launchProgramsForm = () => {
60
- openWorkspaceTab(ProgramsForm, t("programsForm", "Programs form"));
61
- };
51
+ const launchProgramsForm = React.useCallback(
52
+ () => attach('patient-chart-workspace-slot', 'programs-form-workspace'),
53
+ [],
54
+ );
62
55
 
63
56
  const headers = [
64
57
  {
65
- key: "display",
66
- header: t("activePrograms", "Active programs"),
58
+ key: 'display',
59
+ header: t('activePrograms', 'Active programs'),
67
60
  },
68
61
  {
69
- key: "dateEnrolled",
70
- header: t("dateEnrolled", "Date enrolled"),
62
+ key: 'dateEnrolled',
63
+ header: t('dateEnrolled', 'Date enrolled'),
71
64
  },
72
65
  ];
73
66
 
74
67
  const getRowItems = (rows: Array<PatientProgram>) => {
75
- return rows
76
- .slice(firstRowIndex, firstRowIndex + currentPageSize)
77
- .map((row) => ({
78
- id: row.uuid,
79
- display: row.display,
80
- dateEnrolled: dayjs(row.dateEnrolled).format("MMM-YYYY"),
81
- }));
68
+ return rows.slice(firstRowIndex, firstRowIndex + currentPageSize).map((row) => ({
69
+ id: row.uuid,
70
+ display: row.display,
71
+ dateEnrolled: dayjs(row.dateEnrolled).format('MMM-YYYY'),
72
+ }));
82
73
  };
83
74
 
84
75
  const RenderPrograms = () => {
85
76
  if (programs.length) {
86
77
  const rows = getRowItems(programs);
87
78
  return (
88
- <div>
79
+ <div className={styles.widgetCard}>
89
80
  <div className={styles.programsHeader}>
90
- <h4 className={`${styles.productiveHeading03} ${styles.text02}`}>
91
- {headerTitle}
92
- </h4>
93
- <Button
94
- kind="ghost"
95
- renderIcon={Add16}
96
- iconDescription="Add programs"
97
- onClick={launchProgramsForm}
98
- >
99
- {t("add", "Add")}
81
+ <h4 className={`${styles.productiveHeading03} ${styles.text02}`}>{headerTitle}</h4>
82
+ <Button kind="ghost" renderIcon={Add16} iconDescription="Add programs" onClick={launchProgramsForm}>
83
+ {t('add', 'Add')}
100
84
  </Button>
101
85
  </div>
102
86
  <TableContainer>
103
- <DataTable
104
- rows={rows}
105
- headers={headers}
106
- isSortable={true}
107
- size="short"
108
- >
87
+ <DataTable rows={rows} headers={headers} isSortable={true} size="short">
109
88
  {({ rows, headers, getHeaderProps, getTableProps }) => (
110
- <Table {...getTableProps()}>
89
+ <Table {...getTableProps()} useZebraStyles>
111
90
  <TableHead>
112
91
  <TableRow>
113
92
  {headers.map((header) => (
@@ -116,8 +95,7 @@ const ProgramsOverview: React.FC<ProgramsOverviewProps> = ({ patientUuid }) => {
116
95
  {...getHeaderProps({
117
96
  header,
118
97
  isSortable: header.isSortable,
119
- })}
120
- >
98
+ })}>
121
99
  {header.header?.content ?? header.header}
122
100
  </TableHeader>
123
101
  ))}
@@ -127,9 +105,7 @@ const ProgramsOverview: React.FC<ProgramsOverviewProps> = ({ patientUuid }) => {
127
105
  {rows.map((row) => (
128
106
  <TableRow key={row.id}>
129
107
  {row.cells.map((cell) => (
130
- <TableCell key={cell.id}>
131
- {cell.value?.content ?? cell.value}
132
- </TableCell>
108
+ <TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>
133
109
  ))}
134
110
  </TableRow>
135
111
  ))}
@@ -145,7 +121,7 @@ const ProgramsOverview: React.FC<ProgramsOverviewProps> = ({ patientUuid }) => {
145
121
  forwardText={nextPage}
146
122
  pageSize={currentPageSize}
147
123
  pageSizes={[5, 10, 15, 25]}
148
- itemsPerPageText={itemPerPage}
124
+ itemsPerPageText={itemsPerPage}
149
125
  onChange={({ page, pageSize }) => {
150
126
  if (pageSize !== currentPageSize) {
151
127
  setCurrentPageSize(pageSize);
@@ -157,13 +133,7 @@ const ProgramsOverview: React.FC<ProgramsOverviewProps> = ({ patientUuid }) => {
157
133
  </div>
158
134
  );
159
135
  }
160
- return (
161
- <EmptyState
162
- displayText={displayText}
163
- headerTitle={headerTitle}
164
- launchForm={launchProgramsForm}
165
- />
166
- );
136
+ return <EmptyState displayText={displayText} headerTitle={headerTitle} launchForm={launchProgramsForm} />;
167
137
  };
168
138
 
169
139
  return (
@@ -2,6 +2,10 @@
2
2
  @import "~carbon-components/src/globals/scss/vars";
3
3
  @import "~carbon-components/src/globals/scss/mixins";
4
4
 
5
+ .widgetCard {
6
+ border: 1px solid $ui-03;
7
+ }
8
+
5
9
  .programsHeader {
6
10
  display: flex;
7
11
  justify-content: space-between;
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import ProgramsOverview from './programs-overview.component';
3
+ import { of } from 'rxjs/internal/observable/of';
4
+ import { render, screen, fireEvent } from '@testing-library/react';
5
+ import { mockEnrolledProgramsResponse } from '../../../../__mocks__/programs.mock';
6
+ import * as mockProgramResource from './programs.resource';
7
+ import { mockPatient } from '../../../../__mocks__/patient.mock';
8
+ import { attach } from '@openmrs/esm-framework';
9
+ import { throwError } from 'rxjs';
10
+
11
+ const mockAttach = attach as jest.Mock;
12
+
13
+ jest.mock('@openmrs/esm-framework', () => ({
14
+ ...jest.requireActual('@openmrs/esm-framework/mock'),
15
+ attach: jest.fn(),
16
+ }));
17
+
18
+ describe('<ProgramsOverview />', () => {
19
+ const renderProgramOverview = () => {
20
+ spyOn(mockProgramResource, 'fetchActiveEnrollments').and.returnValue(of(mockEnrolledProgramsResponse));
21
+ render(<ProgramsOverview patientUuid={mockPatient.id} basePath={'/'} />);
22
+ };
23
+
24
+ const renderEmptyStateProgramOverview = () => {
25
+ spyOn(mockProgramResource, 'fetchActiveEnrollments').and.returnValue(of([]));
26
+ render(<ProgramsOverview patientUuid={mockPatient.id} basePath={'/'} />);
27
+ };
28
+
29
+ const renderErrorStateProgramOverview = () => {
30
+ spyOn(mockProgramResource, 'fetchActiveEnrollments').and.returnValue(throwError('loading error'));
31
+ render(<ProgramsOverview patientUuid={mockPatient.id} basePath={'/'} />);
32
+ };
33
+
34
+ it("should display the patient's program enrollments", async () => {
35
+ renderProgramOverview();
36
+ await screen.findByRole('heading', { name: /Care Programs/i });
37
+
38
+ expect(screen.getByText(/Care Programs/i)).toBeInTheDocument();
39
+ const addBtn = screen.getByRole('button', { name: 'Add' });
40
+ expect(addBtn).toBeInTheDocument();
41
+ expect(screen.getAllByText(/Active Programs/i)[0]).toBeInTheDocument();
42
+ expect(screen.getAllByText(/Date enrolled/i)[0]).toBeInTheDocument();
43
+ expect(screen.getAllByText(/HIV Care and Treatment/i)[0]).toBeInTheDocument();
44
+
45
+ // Clicking "Add" launches form tab
46
+ fireEvent.click(addBtn);
47
+ expect(mockAttach).toHaveBeenCalledWith('patient-chart-workspace-slot', 'programs-form-workspace');
48
+ });
49
+
50
+ it('renders an empty state view when conditions data is absent', async () => {
51
+ renderEmptyStateProgramOverview();
52
+
53
+ await screen.findByRole('heading', { name: /Care Programs/i });
54
+
55
+ expect(screen.getByText(/Care Programs/i)).toBeInTheDocument();
56
+ expect(screen.getByText(/There are no program enrollments to display for this patient/)).toBeInTheDocument();
57
+ expect(screen.getByText(/Record program enrollments/)).toBeInTheDocument();
58
+ });
59
+
60
+ it('render an error state when an error occurrs', () => {
61
+ renderErrorStateProgramOverview();
62
+ expect(screen.getByText(/Care Programs/i)).toBeInTheDocument();
63
+
64
+ expect(
65
+ screen.getByText(
66
+ /Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above./,
67
+ ),
68
+ ).toBeInTheDocument();
69
+ });
70
+ });
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { BrowserRouter } from 'react-router-dom';
4
+ import Programs from './programs.component';
5
+ import { mockPatient } from '../../../../__mocks__/patient.mock';
6
+
7
+ describe('<ProgramsComponent />', () => {
8
+ it('renders without dying', () => {
9
+ render(
10
+ <BrowserRouter>
11
+ <Programs
12
+ patient={mockPatient}
13
+ patientUuid={mockPatient.id}
14
+ basePath={'patient/8673ee4f-e2ab-4077-ba55-4980f408773e/chart'}
15
+ />
16
+ </BrowserRouter>,
17
+ );
18
+
19
+ expect(screen.getByRole('heading', { name: /Care Programs/i })).toBeInTheDocument();
20
+ });
21
+ });
@@ -1,8 +1,8 @@
1
- import React from "react";
2
- import ProgramsDetailedSummary from "./programs-detailed-summary.component";
3
- import ProgramRecord from "./program-record.component";
4
- import { Switch, Route, BrowserRouter } from "react-router-dom";
5
- import { ProgramsContext } from "./programs.context";
1
+ import React from 'react';
2
+ import ProgramsDetailedSummary from './programs-detailed-summary.component';
3
+ import ProgramRecord from './program-record.component';
4
+ import { Switch, Route, BrowserRouter } from 'react-router-dom';
5
+ import { ProgramsContext } from './programs.context';
6
6
 
7
7
  interface ProgramsProps {
8
8
  basePath: string;
@@ -10,11 +10,7 @@ interface ProgramsProps {
10
10
  patientUuid: string;
11
11
  }
12
12
 
13
- export default function Programs({
14
- basePath,
15
- patient,
16
- patientUuid,
17
- }: ProgramsProps) {
13
+ export default function Programs({ basePath, patient, patientUuid }: ProgramsProps) {
18
14
  return (
19
15
  <ProgramsContext.Provider value={{ patientUuid, patient }}>
20
16
  <BrowserRouter basename={`${window.spaBase}${basePath}/programs`}>
@@ -1,4 +1,4 @@
1
- import { createContext, useContext } from "react";
1
+ import { createContext, useContext } from 'react';
2
2
 
3
3
  export interface ProgramsContextShape {
4
4
  patientUuid: string;
@@ -1,33 +1,39 @@
1
- import { openmrsObservableFetch, openmrsFetch } from "@openmrs/esm-framework";
2
- import { map } from "rxjs/operators";
3
- import { PatientProgram, Program, LocationData, SessionData } from "../types";
1
+ import { openmrsObservableFetch } from '@openmrs/esm-framework';
2
+ import { map } from 'rxjs/operators';
3
+ import { LocationData, PatientProgram, Program } from '../types';
4
4
 
5
- export function fetchEnrolledPrograms(patientID: string) {
6
- return openmrsObservableFetch<PatientProgram>(
7
- `/ws/rest/v1/programenrollment?patient=${patientID}`
5
+ export function fetchActiveEnrollments(patientID: string) {
6
+ return openmrsObservableFetch<{ results: Array<PatientProgram> }>(
7
+ `/ws/rest/v1/programenrollment?patient=${patientID}`,
8
8
  ).pipe(
9
9
  map(({ data }) =>
10
- data["results"].sort((a, b) => (b.dateEnrolled > a.dateEnrolled ? 1 : -1))
11
- )
10
+ data.results.filter((res) => !res.dateCompleted).sort((a, b) => (b.dateEnrolled > a.dateEnrolled ? 1 : -1)),
11
+ ),
12
12
  );
13
13
  }
14
14
 
15
- export function fetchActiveEnrollments(patientID: string) {
16
- return openmrsObservableFetch<Array<PatientProgram>>(
17
- `/ws/rest/v1/programenrollment?patient=${patientID}`
18
- ).pipe(
19
- map(({ data }) =>
20
- data["results"]
21
- .filter((res) => !res.dateCompleted)
22
- .sort((a, b) => (b.dateEnrolled > a.dateEnrolled ? 1 : -1))
23
- )
24
- );
15
+ export function fetchAvailablePrograms() {
16
+ return openmrsObservableFetch<{ results: Array<Program> }>(
17
+ `/ws/rest/v1/program?v=custom:(uuid,display,allWorkflows,concept:(uuid,display))`,
18
+ ).pipe(map(({ data }) => data.results));
19
+ }
20
+
21
+ export function fetchEnrolledPrograms(patientID: string) {
22
+ return openmrsObservableFetch<{ results: Array<PatientProgram> }>(
23
+ `/ws/rest/v1/programenrollment?patient=${patientID}`,
24
+ ).pipe(map(({ data }) => data.results.sort((a, b) => (b.dateEnrolled > a.dateEnrolled ? 1 : -1))));
25
25
  }
26
26
 
27
27
  export function getPatientProgramByUuid(programUuid: string) {
28
- return openmrsObservableFetch<PatientProgram>(
29
- `/ws/rest/v1/programenrollment/${programUuid}`
30
- ).pipe(map(({ data }) => data));
28
+ return openmrsObservableFetch<PatientProgram>(`/ws/rest/v1/programenrollment/${programUuid}`).pipe(
29
+ map(({ data }) => data),
30
+ );
31
+ }
32
+
33
+ export function fetchLocations() {
34
+ return openmrsObservableFetch<{ results: Array<LocationData> }>(`/ws/rest/v1/location?v=custom:(uuid,display)`).pipe(
35
+ map(({ data }) => data.results),
36
+ );
31
37
  }
32
38
 
33
39
  export function createProgramEnrollment(payload, abortController) {
@@ -36,9 +42,9 @@ export function createProgramEnrollment(payload, abortController) {
36
42
  }
37
43
  const { program, patient, dateEnrolled, dateCompleted, location } = payload;
38
44
  return openmrsObservableFetch(`/ws/rest/v1/programenrollment`, {
39
- method: "POST",
45
+ method: 'POST',
40
46
  headers: {
41
- "Content-Type": "application/json",
47
+ 'Content-Type': 'application/json',
42
48
  },
43
49
  body: { program, patient, dateEnrolled, dateCompleted, location },
44
50
  signal: abortController.signal,
@@ -51,29 +57,11 @@ export function updateProgramEnrollment(payload, abortController) {
51
57
  }
52
58
  const { program, dateEnrolled, dateCompleted, location } = payload;
53
59
  return openmrsObservableFetch(`/ws/rest/v1/programenrollment/${program}`, {
54
- method: "POST",
60
+ method: 'POST',
55
61
  headers: {
56
- "Content-Type": "application/json",
62
+ 'Content-Type': 'application/json',
57
63
  },
58
64
  body: { dateEnrolled, dateCompleted, location },
59
65
  signal: abortController.signal,
60
66
  });
61
67
  }
62
-
63
- export function fetchPrograms() {
64
- return openmrsObservableFetch<Array<Program>>(
65
- `/ws/rest/v1/program?v=custom:(uuid,display,allWorkflows,concept:(uuid,display))`
66
- ).pipe(map(({ data }) => data["results"]));
67
- }
68
-
69
- export function fetchLocations() {
70
- return openmrsObservableFetch<Array<LocationData>>(
71
- `/ws/rest/v1/location?v=custom:(uuid,display)`
72
- ).pipe(map(({ data }) => data["results"]));
73
- }
74
-
75
- export function getSession(abortController: AbortController) {
76
- return openmrsFetch<SessionData>(`/ws/rest/v1/appui/session`, {
77
- signal: abortController.signal,
78
- });
79
- }