Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@use '@carbon/colors';
@use '@carbon/type';
@use '@carbon/layout';
@use '@openmrs/esm-styleguide/src/vars' as *;
Expand All @@ -23,19 +24,19 @@
flex: 1;
}

.productiveHeading02 {
.columnLabel {
@include type.type-style('heading-compact-02');
color: colors.$gray-70;
padding: layout.$spacing-02;
}

.buttonSet {
margin: 0 (-(layout.$spacing-05)) (-(layout.$spacing-05)) (-(layout.$spacing-05));
display: flex;
align-items: center;
margin: ((layout.$spacing-05)) (-(layout.$spacing-05)) (-(layout.$spacing-05)) (-(layout.$spacing-05));

button {
max-width: unset !important;
width: auto !important;

> svg {
fill: currentColor !important;
}
width: 50% !important;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
import { render, screen, waitFor } from '@testing-library/react';
import { closeWorkspaceGroup2, showSnackbar, useAppContext } from '@openmrs/esm-framework';
import { useCreateEncounter, removePatientFromBed } from '../../ward.resource';
import PatientDischargeWorkspace from './patient-discharge.workspace';
import { mockInpatientRequestAlice, mockPatientAlice, mockVisitAlice } from '__mocks__';
import type { WardPatient, WardPatientWorkspaceDefinition } from '../../types';

const mockShowSnackbar = jest.mocked(showSnackbar);
const mockUseCreateEncounter = jest.mocked(useCreateEncounter);
const mockRemovePatientFromBed = jest.mocked(removePatientFromBed);
const mockUseAppContext = jest.mocked(useAppContext);
const mockCloseWorkspaceGroup2 = jest.mocked(closeWorkspaceGroup2);

jest.mock('../../ward.resource', () => ({
useCreateEncounter: jest.fn(),
removePatientFromBed: jest.fn(),
}));

jest.mock('../patient-banner/patient-banner.component', () => () => <div>patient-banner</div>);

const mockWardPatient: WardPatient = {
patient: mockPatientAlice,
visit: mockVisitAlice,
bed: {
id: 1,
uuid: 'bed-1',
bedNumber: 'Bed 1',
bedType: {
uuid: 'bed-type',
name: 'General',
displayName: 'General',
description: '',
resourceVersion: '',
},
row: 1,
column: 1,
status: 'OCCUPIED',
},
inpatientAdmission: null as any,
inpatientRequest: mockInpatientRequestAlice as any,
};

const testProps: WardPatientWorkspaceDefinition = {
groupProps: { wardPatient: mockWardPatient },
closeWorkspace: jest.fn(),
launchChildWorkspace: jest.fn(),
workspaceProps: null,
windowProps: null,
workspaceName: 'discharge',
windowName: 'discharge',
isRootWorkspace: false,
};

describe('PatientDischargeWorkspace', () => {
const mockCreateEncounter = jest.fn();
const mockWardPatientMutate = jest.fn();

beforeEach(() => {
mockUseCreateEncounter.mockReturnValue({
createEncounter: mockCreateEncounter,
emrConfiguration: {
consultFreeTextCommentsConcept: { uuid: 'consult-concept' },
exitFromInpatientEncounterType: { uuid: 'exit-encounter' } as any,
} as any,
isLoadingEmrConfiguration: false,
errorFetchingEmrConfiguration: null,
});

mockUseAppContext.mockReturnValue({
wardPatientGroupDetails: { mutate: mockWardPatientMutate },
} as any);
});

it('renders discharge workspace with note field and action buttons', () => {
render(<PatientDischargeWorkspace {...testProps} />);

expect(screen.getByPlaceholderText(/write any notes here/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Confirm discharge/i })).toBeInTheDocument();
});

it('calls closeWorkspace when cancel button is clicked', async () => {
const user = userEvent.setup();

render(<PatientDischargeWorkspace {...testProps} />);

await user.click(screen.getByRole('button', { name: /cancel/i }));

expect(testProps.closeWorkspace).toHaveBeenCalled();
});

it('submits discharge with note, removes patient from bed, and shows success snackbar', async () => {
const user = userEvent.setup();

mockCreateEncounter.mockResolvedValueOnce({ ok: true } as any);
mockRemovePatientFromBed.mockResolvedValueOnce({ ok: true } as any);

render(<PatientDischargeWorkspace {...testProps} />);

const noteInput = screen.getByPlaceholderText(/write any notes here/i);
await user.clear(noteInput);
await user.type(noteInput, 'Patient recovered');

await user.click(screen.getByRole('button', { name: /Confirm discharge/i }));

await waitFor(() => {
expect(mockCreateEncounter).toHaveBeenCalledWith(
mockWardPatient.patient,
{ uuid: 'exit-encounter' },
mockWardPatient.visit.uuid,
[
{
concept: 'consult-concept',
value: 'Patient recovered',
},
],
);
});

// Then verify all other calls happened
expect(mockRemovePatientFromBed).toHaveBeenCalledWith(mockWardPatient.bed.id, mockWardPatient.patient.uuid);
expect(mockWardPatientMutate).toHaveBeenCalled();
expect(mockShowSnackbar).toHaveBeenCalledWith({
title: 'Patient was discharged',
kind: 'success',
});
expect(testProps.closeWorkspace).toHaveBeenCalledWith({ discardUnsavedChanges: true });
expect(mockCloseWorkspaceGroup2).toHaveBeenCalled();
});

it('submits discharge without note, removes patient from bed, and shows success snackbar', async () => {
const user = userEvent.setup();

mockCreateEncounter.mockResolvedValueOnce({ ok: true } as any);
mockRemovePatientFromBed.mockResolvedValueOnce({ ok: true } as any);

render(<PatientDischargeWorkspace {...testProps} />);

await user.click(screen.getByRole('button', { name: /Confirm discharge/i }));

await waitFor(() => {
expect(mockCreateEncounter).toHaveBeenCalledWith(
mockWardPatient.patient,
{ uuid: 'exit-encounter' },
mockWardPatient.visit.uuid,
[],
);
});

// Then verify all other calls happened
expect(mockRemovePatientFromBed).toHaveBeenCalledWith(mockWardPatient.bed.id, mockWardPatient.patient.uuid);
expect(mockWardPatientMutate).toHaveBeenCalled();
expect(mockShowSnackbar).toHaveBeenCalledWith({
title: 'Patient was discharged',
kind: 'success',
});
expect(testProps.closeWorkspace).toHaveBeenCalledWith({ discardUnsavedChanges: true });
expect(mockCloseWorkspaceGroup2).toHaveBeenCalled();
});

it('disables discharge button during submission', async () => {
const user = userEvent.setup();

mockCreateEncounter.mockImplementation(
() => new Promise((resolve) => setTimeout(() => resolve({ ok: true }), 100)),
);
mockRemovePatientFromBed.mockResolvedValueOnce({ ok: true } as any);

render(<PatientDischargeWorkspace {...testProps} />);

const noteInput = screen.getByPlaceholderText(/write any notes here/i);
await user.type(noteInput, 'Patient recovered');

const dischargeButton = screen.getByRole('button', { name: /Confirm discharge/i });
expect(dischargeButton).toBeEnabled();

await user.click(dischargeButton);

// Button should show loading state
await waitFor(() => {
expect(screen.getByText(/discharging/i)).toBeInTheDocument();
});

await waitFor(() => {
expect(testProps.closeWorkspace).toHaveBeenCalled();
});
});

it('disables cancel button during submission', async () => {
const user = userEvent.setup();

mockCreateEncounter.mockImplementation(
() => new Promise((resolve) => setTimeout(() => resolve({ ok: true }), 100)),
);
mockRemovePatientFromBed.mockResolvedValueOnce({ ok: true } as any);

render(<PatientDischargeWorkspace {...testProps} />);

const noteInput = screen.getByPlaceholderText(/write any notes here/i);
await user.type(noteInput, 'Patient recovered');

await user.click(screen.getByRole('button', { name: /Confirm discharge/i }));

// Wait for loading state to appear, then check if cancel button is disabled
await waitFor(() => {
expect(screen.getByText(/discharging/i)).toBeInTheDocument();
});

const cancelButton = screen.getByRole('button', { name: /cancel/i });
expect(cancelButton).toBeDisabled();

await waitFor(() => {
expect(testProps.closeWorkspace).toHaveBeenCalled();
});
});

it('shows error snackbar when discharge fails', async () => {
const user = userEvent.setup();
mockCreateEncounter.mockRejectedValueOnce({ message: 'Discharge failed' });

render(<PatientDischargeWorkspace {...testProps} />);

const noteInput = screen.getByPlaceholderText(/write any notes here/i);
await user.type(noteInput, 'Patient recovered');
await user.click(screen.getByRole('button', { name: /Confirm discharge/i }));

await waitFor(() => {
expect(mockShowSnackbar).toHaveBeenCalledWith({
title: 'Error discharging patient',
subtitle: 'Discharge failed',
kind: 'error',
});
});
});

it('shows fallback error message when error has no message', async () => {
const user = userEvent.setup();

mockCreateEncounter.mockRejectedValueOnce({});

render(<PatientDischargeWorkspace {...testProps} />);

const noteInput = screen.getByPlaceholderText(/write any notes here/i);
await user.type(noteInput, 'Patient recovered');
await user.click(screen.getByRole('button', { name: /Confirm discharge/i }));

await waitFor(() => {
expect(mockShowSnackbar).toHaveBeenCalledWith({
title: 'Error discharging patient',
subtitle: 'Unable to discharge patient. Please try again.',
kind: 'error',
});
});
});

it('does not call mutate when discharge fails', async () => {
const user = userEvent.setup();
mockCreateEncounter.mockRejectedValueOnce({ message: 'Network issue' });

render(<PatientDischargeWorkspace {...testProps} />);

const noteInput = screen.getByPlaceholderText(/write any notes here/i);
await user.type(noteInput, 'Patient recovered');
await user.click(screen.getByRole('button', { name: /Confirm discharge/i }));

await waitFor(() => {
expect(mockShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'error' }));
});

expect(mockWardPatientMutate).not.toHaveBeenCalled();
});

it('re-enables buttons after failed discharge', async () => {
const user = userEvent.setup();
mockCreateEncounter.mockRejectedValueOnce({ message: 'Network issue' });

render(<PatientDischargeWorkspace {...testProps} />);

const noteInput = screen.getByPlaceholderText(/write any notes here/i);
await user.type(noteInput, 'Patient recovered');

const dischargeButton = screen.getByRole('button', { name: /Confirm discharge/i });
await user.click(dischargeButton);

await waitFor(() => {
expect(mockShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'error' }));
});

expect(dischargeButton).toBeEnabled();
expect(screen.getByRole('button', { name: /cancel/i })).toBeEnabled();
});
});
Loading
Loading