Skip to content
Open
8 changes: 4 additions & 4 deletions __tests__/unit/app/components/navpatientheader/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import React from 'react';
import { Provider } from 'react-redux';
import { render, screen } from '@testing-library/react';
import { render, screen, within } from '@testing-library/react';
import _ from 'lodash';
import configureStore from 'redux-mock-store';
import { thunk } from 'redux-thunk';
Expand Down Expand Up @@ -39,7 +39,7 @@ jest.mock('@app/providers/ToastProvider', () => ({

describe('NavPatientHeader', () => {
const trackMetric = jest.fn();
const api = {};
const api = { clinics: { getPatientFromClinic: jest.fn() } };

const patientProps = {
userid: '1234',
Expand Down Expand Up @@ -191,7 +191,7 @@ describe('NavPatientHeader', () => {
expect(screen.getByText(/Naoya Inoue/)).toBeInTheDocument();
expect(screen.getByText(/999999/)).toBeInTheDocument();
expect(screen.getByText(/1965-01-01/)).toBeInTheDocument();
expect(screen.getByText(/Pre-diabetes/)).toBeInTheDocument();
expect(within(screen.getByTestId('nav-patient-header')).getByText(/Pre-diabetes/)).toBeInTheDocument();

// should NOT show demographic info from the 'patient' object
expect(screen.queryByText(/Vasyl Lomachenko/)).not.toBeInTheDocument();
Expand Down Expand Up @@ -228,7 +228,7 @@ describe('NavPatientHeader', () => {
expect(screen.getByText(/Naoya Inoue/)).toBeInTheDocument();
expect(screen.getByText(/999999/)).toBeInTheDocument();
expect(screen.getByText(/1965-01-01/)).toBeInTheDocument();
expect(screen.getByText(/Pre-diabetes/)).toBeInTheDocument();
expect(within(screen.getByTestId('nav-patient-header')).getByText(/Pre-diabetes/)).toBeInTheDocument();

// should NOT show demographic info from the 'patient' object
expect(screen.queryByText(/Vasyl Lomachenko/)).not.toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,34 @@ describe('Cells', () => {
expect(screen.queryByText('Very Low')).not.toBeInTheDocument();
});
});

describe('MoreMenuCell', () => {
it('dispatches the actions to open the Edit Patient dialog for the patient', async () => {
renderComponent(<MoreMenuCell patient={patient} />);

expect(screen.queryByRole('button', { name: /Edit Patient Details/ })).not.toBeInTheDocument();
await userEvent.click(screen.getByTestId('action-menu-patient-1-icon'));

await userEvent.click(screen.getByRole('button', { name: /Edit Patient Details/ }));

expect(store.getActions()).toStrictEqual([
{ type: 'tideDashboard/setEditPatientDialogIsOpen', payload: true },
{ type: 'tideDashboard/setEditPatientDialogPatientId', payload: 'patient-1' },
]);
});

it('dispatches the actions to open the Data Connections modal for the patient', async () => {
renderComponent(<MoreMenuCell patient={patient} />);

expect(screen.queryByRole('button', { name: /Bring Data into Tidepool/ })).not.toBeInTheDocument();
await userEvent.click(screen.getByTestId('action-menu-patient-1-icon'));

await userEvent.click(screen.getByRole('button', { name: /Bring Data into Tidepool/ }));

expect(store.getActions()).toStrictEqual([
{ type: 'tideDashboard/setDataConnectionsModalIsOpen', payload: true },
{ type: 'tideDashboard/setDataConnectionsModalPatientId', payload: 'patient-1' },
]);
});
});
});
138 changes: 138 additions & 0 deletions __tests__/unit/components/clinic/EditPatientDialog.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React from 'react';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import configureStore from 'redux-mock-store';
import { thunk } from 'redux-thunk';
import { ThemeProvider } from 'theme-ui';
import theme from '@app/themes/baseTheme';
import EditPatientDialog from '@app/components/clinic/EditPatientDialog';
import { usePrevious } from '@app/core/hooks';

const mockStore = configureStore([thunk]);

jest.mock('@app/core/hooks', () => ({
...jest.requireActual('@app/core/hooks'),
useIsFirstRender: jest.fn(() => false),
usePrevious: jest.fn(),
}));

const clinicPatient = {
id: 'patient123',
fullName: 'John Doe',
birthDate: '2000-01-01',
permissions: { custodian: {} },
};

const IDLE_UPDATE = { inProgress: false, completed: null, notification: null };
const COMPLETED_UPDATE = { inProgress: false, completed: true, notification: null };
const FAILED_UPDATE = { inProgress: false, completed: false, notification: { message: 'Something went wrong' } };

const makeState = ({ smartCorrelationId, updatingClinicPatient = IDLE_UPDATE } = {}) => ({
blip: {
selectedClinicId: 'clinic123',
smartCorrelationId,
clinics: { clinic123: { id: 'clinic123', mrnSettings: { required: false } } },
working: {
fetchingClinicMRNsForPatientFormValidation: { inProgress: false, completed: false, notification: null },
updatingClinicPatient,
},
clinicMRNsForPatientFormValidation: [],
},
});

const api = { clinics: { getPatientFromClinic: jest.fn(), updateClinicPatient: jest.fn() } };
const onClose = jest.fn();
const onEditConfirm = jest.fn();
const onEditSuccess = jest.fn();
const onEditFailure = jest.fn();

const ui = (store) => (
<Provider store={store}>
<MemoryRouter initialEntries={['/patients/patient123/data']}>
<ThemeProvider theme={theme}>
<EditPatientDialog
api={api}
clinicPatient={clinicPatient}
isOpen={true}
onClose={onClose}
onEditConfirm={onEditConfirm}
onEditSuccess={onEditSuccess}
onEditFailure={onEditFailure}
/>
</ThemeProvider>
</MemoryRouter>
</Provider>
);

describe('EditPatientDialog', () => {
beforeEach(() => {
jest.clearAllMocks();
usePrevious.mockReturnValue(true);
});

it('renders the patient form populated from clinicPatient, locking identity fields only in smart-on-fhir mode', () => {
const { rerender } = render(ui(mockStore(makeState())));

// The form is prefilled from clinicPatient ('2000-01-01' renders in the display format, 01/01/2000)
expect(screen.getByRole('textbox', { name: /Full Name/i })).toHaveValue('John Doe');
expect(screen.getByRole('textbox', { name: /Birthdate/i })).toHaveValue('01/01/2000');

// Outside smart-on-fhir mode every field is editable
expect(screen.getByRole('textbox', { name: /Full Name/i })).toBeEnabled();
expect(screen.getByRole('textbox', { name: /Birthdate/i })).toBeEnabled();
expect(screen.getByRole('textbox', { name: /MRN/i })).toBeEnabled();
expect(screen.getByRole('textbox', { name: /Email/i })).toBeEnabled();

// In smart-on-fhir mode the EHR-sourced identity fields lock
rerender(ui(mockStore(makeState({ smartCorrelationId: 'some-correlation-id' }))));
expect(screen.getByRole('textbox', { name: /Full Name/i })).toBeDisabled();
expect(screen.getByRole('textbox', { name: /Birthdate/i })).toBeDisabled();
expect(screen.getByRole('textbox', { name: /MRN/i })).toBeDisabled();
expect(screen.getByRole('textbox', { name: /Email/i })).toBeDisabled();

// Clinical fields stay editable in smart-on-fhir mode
expect(screen.getByLabelText(/Diabetes Type/i)).toBeEnabled();
expect(screen.getByLabelText('Target Range')).toBeEnabled();
});

it('notifies the parent through onEditConfirm and submits the form when Save Changes is clicked', async () => {
render(ui(mockStore(makeState())));

await userEvent.click(screen.getByRole('button', { name: 'Save Changes' }));

// The parent is handed the live form context before the submit fires
expect(onEditConfirm).toHaveBeenCalledWith(
expect.objectContaining({ values: expect.objectContaining({ fullName: 'John Doe' }) })
);

// The form submit runs through to the patient update endpoint
await waitFor(() => {
expect(api.clinics.updateClinicPatient).toHaveBeenCalledWith(
'clinic123', // clinicId
'patient123', // patientId
expect.objectContaining({ fullName: 'John Doe' }), // updated patient
expect.any(Function), // node-style callback
);
});
});

it('reports the update result: onEditSuccess when it completes and onEditFailure when it fails', () => {
const { rerender } = render(ui(mockStore(makeState({ updatingClinicPatient: IDLE_UPDATE }))));

// Nothing is reported while no update has resolved
expect(onEditSuccess).not.toHaveBeenCalled();
expect(onEditFailure).not.toHaveBeenCalled();

// The in-flight update completes successfully
rerender(ui(mockStore(makeState({ updatingClinicPatient: COMPLETED_UPDATE }))));
expect(onEditSuccess).toHaveBeenCalledTimes(1);
expect(onEditFailure).not.toHaveBeenCalled();

// A later update fails
rerender(ui(mockStore(makeState({ updatingClinicPatient: FAILED_UPDATE }))));
expect(onEditFailure).toHaveBeenCalledTimes(1);
expect(onEditSuccess).toHaveBeenCalledTimes(1);
});
});
Loading