Skip to main content
Glama
TaskQuestionnaireForm.test.tsx13.5 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { MantineProvider } from '@mantine/core'; import { act, render, screen, waitFor } from '@testing-library/react'; import { MedplumProvider } from '@medplum/react'; import type { Encounter, Practitioner, Questionnaire, QuestionnaireResponse, Reference, Task, } from '@medplum/fhirtypes'; import { MockClient } from '@medplum/mock'; import { MemoryRouter } from 'react-router'; import { describe, expect, test, vi, beforeEach } from 'vitest'; import { TaskQuestionnaireForm } from './TaskQuestionnaireForm'; import userEvent from '@testing-library/user-event'; const mockQuestionnaire: Questionnaire = { resourceType: 'Questionnaire', id: 'questionnaire-123', status: 'active', title: 'Test Questionnaire', item: [ { linkId: 'q1', type: 'string', text: 'Question 1', }, ], }; const mockQuestionnaireResponse: QuestionnaireResponse = { resourceType: 'QuestionnaireResponse', id: 'response-123', status: 'in-progress', questionnaire: 'Questionnaire/questionnaire-123', item: [ { linkId: 'q1', answer: [{ valueString: 'Initial answer' }], }, ], }; const mockCompletedQuestionnaireResponse: QuestionnaireResponse = { ...mockQuestionnaireResponse, status: 'completed', }; const mockTask: Task = { resourceType: 'Task', id: 'task-123', status: 'in-progress', intent: 'order', input: [ { type: { coding: [{ code: 'Questionnaire' }], }, valueReference: { reference: 'Questionnaire/questionnaire-123', } as Reference<Questionnaire>, }, ], output: [ { type: { coding: [{ code: 'QuestionnaireResponse' }], }, valueReference: { reference: 'QuestionnaireResponse/response-123', } as Reference<QuestionnaireResponse>, }, ], }; const mockCompletedTask: Task = { ...mockTask, status: 'completed', }; const mockTaskWithoutResponse: Task = { ...mockTask, output: undefined, }; const mockTaskWithoutQuestionnaire: Task = { ...mockTask, input: undefined, }; const mockEncounter: Encounter = { resourceType: 'Encounter', id: 'encounter-123', status: 'in-progress', class: { code: 'AMB', system: 'http://terminology.hl7.org/CodeSystem/v3-ActCode' }, }; const mockTaskWithEncounter: Task = { ...mockTask, encounter: { reference: 'Encounter/encounter-123', } as Reference<Encounter>, }; const mockPractitioner: Practitioner = { resourceType: 'Practitioner', id: 'practitioner-123', name: [{ given: ['Test'], family: 'Practitioner' }], }; describe('TaskQuestionnaireForm', () => { let medplum: MockClient; beforeEach(async () => { medplum = new MockClient(); vi.clearAllMocks(); // Set up default profile await medplum.createResource(mockPractitioner); }); const setup = ( task: Task, onChangeResponse?: (response: QuestionnaireResponse) => void ): ReturnType<typeof render> => { return render( <MemoryRouter> <MedplumProvider medplum={medplum}> <MantineProvider> <TaskQuestionnaireForm task={task} onChangeResponse={onChangeResponse} /> </MantineProvider> </MedplumProvider> </MemoryRouter> ); }; test('fetches and displays questionnaire form when task is not completed', async () => { await medplum.createResource(mockQuestionnaire); await medplum.createResource(mockQuestionnaireResponse); medplum.readReference = vi.fn().mockImplementation(async (ref: Reference) => { if (ref.reference === 'Questionnaire/questionnaire-123') { return mockQuestionnaire; } if (ref.reference === 'QuestionnaireResponse/response-123') { return mockQuestionnaireResponse; } throw new Error('Not found'); }); await act(async () => { setup(mockTask); }); await waitFor(() => { expect(screen.getByTestId('questionnaire-form')).toBeInTheDocument(); expect(screen.getByText('Test Questionnaire')).toBeInTheDocument(); }); }); test('displays questionnaire response when task is completed', async () => { await medplum.createResource(mockQuestionnaire); await medplum.createResource(mockCompletedQuestionnaireResponse); medplum.readReference = vi.fn().mockImplementation(async (ref: Reference) => { if (ref.reference === 'Questionnaire/questionnaire-123') { return mockQuestionnaire; } if (ref.reference === 'QuestionnaireResponse/response-123') { return mockCompletedQuestionnaireResponse; } throw new Error('Not found'); }); await act(async () => { setup(mockCompletedTask); }); await waitFor(() => { expect(screen.getByText('Initial answer')).toBeInTheDocument(); }); expect(screen.queryByTestId('questionnaire-form')).not.toBeInTheDocument(); }); test('calls onChangeResponse when questionnaire form changes', async () => { const user = userEvent.setup(); await medplum.createResource(mockQuestionnaire); await medplum.createResource(mockQuestionnaireResponse); const onChangeResponse = vi.fn(); medplum.readReference = vi.fn().mockImplementation(async (ref: Reference) => { if (ref.reference === 'Questionnaire/questionnaire-123') { return mockQuestionnaire; } if (ref.reference === 'QuestionnaireResponse/response-123') { return mockQuestionnaireResponse; } throw new Error('Not found'); }); await act(async () => { setup(mockTask, onChangeResponse); }); await waitFor(() => { expect(screen.getByTestId('questionnaire-form')).toBeInTheDocument(); }); const input = screen.getByLabelText('Question 1'); await user.type(input, 'test answer'); await waitFor(() => { expect(onChangeResponse).toHaveBeenCalled(); }); const callArgs = onChangeResponse.mock.calls[onChangeResponse.mock.calls.length - 1][0]; expect(callArgs.resourceType).toBe('QuestionnaireResponse'); expect(callArgs.status).toBe('in-progress'); expect(callArgs.item).toBeDefined(); expect(callArgs.authored).toBeDefined(); expect(callArgs.source).toBeDefined(); }); test('includes encounter in onChangeResponse when task has encounter', async () => { const user = userEvent.setup(); await medplum.createResource(mockQuestionnaire); await medplum.createResource(mockQuestionnaireResponse); await medplum.createResource(mockEncounter); const onChangeResponse = vi.fn(); medplum.readReference = vi.fn().mockImplementation(async (ref: Reference) => { if (ref.reference === 'Questionnaire/questionnaire-123') { return mockQuestionnaire; } if (ref.reference === 'QuestionnaireResponse/response-123') { return mockQuestionnaireResponse; } if (ref.reference === 'Encounter/encounter-123') { return mockEncounter; } throw new Error('Not found'); }); await act(async () => { setup(mockTaskWithEncounter, onChangeResponse); }); await waitFor(() => { expect(screen.getByTestId('questionnaire-form')).toBeInTheDocument(); }); const input = screen.getByLabelText('Question 1'); await user.type(input, 'test answer'); await waitFor(() => { expect(onChangeResponse).toHaveBeenCalled(); }); const callArgs = onChangeResponse.mock.calls[onChangeResponse.mock.calls.length - 1][0]; expect(callArgs.encounter).toBeDefined(); expect(callArgs.encounter?.reference).toBe('Encounter/encounter-123'); }); test('does not include encounter in onChangeResponse when task has no encounter', async () => { const user = userEvent.setup(); await medplum.createResource(mockQuestionnaire); await medplum.createResource(mockQuestionnaireResponse); const onChangeResponse = vi.fn(); medplum.readReference = vi.fn().mockImplementation(async (ref: Reference) => { if (ref.reference === 'Questionnaire/questionnaire-123') { return mockQuestionnaire; } if (ref.reference === 'QuestionnaireResponse/response-123') { return mockQuestionnaireResponse; } throw new Error('Not found'); }); await act(async () => { setup(mockTask, onChangeResponse); }); await waitFor(() => { expect(screen.getByTestId('questionnaire-form')).toBeInTheDocument(); }); const input = screen.getByLabelText('Question 1'); await user.type(input, 'test answer'); await waitFor(() => { expect(onChangeResponse).toHaveBeenCalled(); }); const callArgs = onChangeResponse.mock.calls[onChangeResponse.mock.calls.length - 1][0]; // When task has no encounter, encounter should be undefined expect(callArgs.encounter).toBeUndefined(); }); test('updates questionnaire response status to completed when task becomes completed', async () => { await medplum.createResource(mockQuestionnaire); await medplum.createResource(mockQuestionnaireResponse); medplum.readReference = vi.fn().mockImplementation(async (ref: Reference) => { if (ref.reference === 'Questionnaire/questionnaire-123') { return mockQuestionnaire; } if (ref.reference === 'QuestionnaireResponse/response-123') { return mockQuestionnaireResponse; } throw new Error('Not found'); }); medplum.updateResource = vi.fn().mockResolvedValue(mockCompletedQuestionnaireResponse); const { rerender } = setup(mockTask); await waitFor(() => { expect(screen.getByTestId('questionnaire-form')).toBeInTheDocument(); }); await act(async () => { rerender( <MemoryRouter> <MedplumProvider medplum={medplum}> <MantineProvider> <TaskQuestionnaireForm task={mockCompletedTask} /> </MantineProvider> </MedplumProvider> </MemoryRouter> ); }); await waitFor(() => { expect(medplum.updateResource).toHaveBeenCalledWith( expect.objectContaining({ resourceType: 'QuestionnaireResponse', id: 'response-123', status: 'completed', }) ); }); }); test('handles missing questionnaire response gracefully', async () => { await medplum.createResource(mockQuestionnaire); medplum.readReference = vi.fn().mockImplementation(async (ref: Reference) => { if (ref.reference === 'Questionnaire/questionnaire-123') { return mockQuestionnaire; } throw new Error('Not found'); }); await act(async () => { setup(mockTaskWithoutResponse); }); await waitFor(() => { expect(screen.getByTestId('questionnaire-form')).toBeInTheDocument(); expect(screen.getByText('Test Questionnaire')).toBeInTheDocument(); }); }); test('handles missing questionnaire gracefully', async () => { await medplum.createResource(mockQuestionnaireResponse); medplum.readReference = vi.fn().mockImplementation(async (ref: Reference) => { if (ref.reference === 'QuestionnaireResponse/response-123') { return mockQuestionnaireResponse; } throw new Error('Not found'); }); await act(async () => { setup(mockTaskWithoutQuestionnaire); }); await waitFor(() => { expect(screen.queryByTestId('questionnaire-form')).not.toBeInTheDocument(); expect(screen.queryByText('Initial answer')).not.toBeInTheDocument(); }); }); test('displays error when resource fetch fails', async () => { const error = new Error('Failed to fetch'); medplum.readReference = vi.fn().mockRejectedValue(error); await act(async () => { setup(mockTask); }); await waitFor(() => { expect(screen.getByText('Failed to fetch')).toBeInTheDocument(); }); }); test('handles task without input or output', async () => { const taskWithoutInputOutput: Task = { ...mockTask, input: undefined, output: undefined, }; await act(async () => { setup(taskWithoutInputOutput); }); await waitFor(() => { expect(screen.queryByTestId('questionnaire-form')).not.toBeInTheDocument(); expect(screen.queryByText('Initial answer')).not.toBeInTheDocument(); }); }); test('creates new questionnaire response when onChange is called without existing response', async () => { const user = userEvent.setup(); await medplum.createResource(mockQuestionnaire); const onChangeResponse = vi.fn(); medplum.readReference = vi.fn().mockImplementation(async (ref: Reference) => { if (ref.reference === 'Questionnaire/questionnaire-123') { return mockQuestionnaire; } throw new Error('Not found'); }); await act(async () => { setup(mockTaskWithoutResponse, onChangeResponse); }); await waitFor(() => { expect(screen.getByTestId('questionnaire-form')).toBeInTheDocument(); }); const input = screen.getByLabelText('Question 1'); await user.type(input, 'test answer'); await waitFor(() => { expect(onChangeResponse).toHaveBeenCalled(); }); const callArgs = onChangeResponse.mock.calls[onChangeResponse.mock.calls.length - 1][0]; expect(callArgs.resourceType).toBe('QuestionnaireResponse'); expect(callArgs.status).toBe('in-progress'); expect(callArgs.item).toBeDefined(); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server