Skip to main content
Glama
useDebouncedUpdateResource.test.tsx5.58 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { renderHook, waitFor } from '@testing-library/react'; import type { Patient } from '@medplum/fhirtypes'; import { HomerSimpson, MockClient } from '@medplum/mock'; import { describe, expect, test, beforeEach, vi } from 'vitest'; import { useDebouncedUpdateResource, DEFAULT_SAVE_TIMEOUT_MS } from './useDebouncedUpdateResource'; describe('useDebouncedUpdateResource', () => { let medplum: MockClient; beforeEach(async () => { medplum = new MockClient(); }); test('debounces resource updates with default timeout', async () => { const { result } = renderHook(() => useDebouncedUpdateResource(medplum)); const mockPatient = { ...HomerSimpson } as Patient & { id: string }; const updatedPatient1 = { ...HomerSimpson, name: [{ given: ['Jane'], family: 'Simpson' }] } as Patient & { id: string; }; const updatedPatient2 = { ...HomerSimpson, name: [{ given: ['Bob'], family: 'Smith' }] } as Patient & { id: string; }; const updateSpy = vi.spyOn(medplum, 'updateResource').mockResolvedValue(updatedPatient2); // Call the debounced function multiple times rapidly const debouncedUpdate = result.current; debouncedUpdate(mockPatient).catch(() => {}); debouncedUpdate(updatedPatient1).catch(() => { // Ignore promise rejections }); debouncedUpdate(updatedPatient2).catch(() => { // Ignore promise rejections }); // Should not have been called immediately expect(updateSpy).not.toHaveBeenCalled(); // Wait for debounce timeout await waitFor( () => { // Should have been called only once (last call) expect(updateSpy).toHaveBeenCalledTimes(1); expect(updateSpy).toHaveBeenCalledWith(updatedPatient2); }, { timeout: DEFAULT_SAVE_TIMEOUT_MS + 1000 } ); }); test('debounces resource updates with custom timeout', async () => { const customTimeout = 100; const { result } = renderHook(() => useDebouncedUpdateResource(medplum, customTimeout)); const mockPatient = { ...HomerSimpson } as Patient & { id: string }; const updateSpy = vi.spyOn(medplum, 'updateResource').mockResolvedValue(mockPatient); const debouncedUpdate = result.current; debouncedUpdate(mockPatient).catch(() => { // Ignore promise rejections }); expect(updateSpy).not.toHaveBeenCalled(); await waitFor( () => { expect(updateSpy).toHaveBeenCalledTimes(1); expect(updateSpy).toHaveBeenCalledWith(mockPatient); }, { timeout: customTimeout + 100 } ); }); test('returns the resource payload immediately', async () => { const { result } = renderHook(() => useDebouncedUpdateResource(medplum)); const mockPatient = { ...HomerSimpson } as Patient & { id: string }; const debouncedUpdate = result.current; const returnedValue = await debouncedUpdate(mockPatient); expect(returnedValue).toEqual(mockPatient); }); test('cancels previous pending updates when new update is called', async () => { const { result } = renderHook(() => useDebouncedUpdateResource(medplum)); const mockPatient1 = { ...HomerSimpson, name: [{ given: ['First'], family: 'Simpson' }] } as Patient & { id: string; }; const mockPatient2 = { ...HomerSimpson, name: [{ given: ['Second'], family: 'Simpson' }] } as Patient & { id: string; }; const updateSpy = vi.spyOn(medplum, 'updateResource').mockResolvedValue(mockPatient2); const debouncedUpdate = result.current; debouncedUpdate(mockPatient1).catch(() => { // Ignore promise rejections }); await new Promise<void>((resolve) => { setTimeout(() => { resolve(); }, DEFAULT_SAVE_TIMEOUT_MS - 100); }); debouncedUpdate(mockPatient2).catch(() => { // Ignore promise rejections }); await waitFor( () => { expect(updateSpy).toHaveBeenCalledTimes(1); expect(updateSpy).toHaveBeenCalledWith(mockPatient2); }, { timeout: DEFAULT_SAVE_TIMEOUT_MS + 500 } ); }); test('handles update errors gracefully', async () => { const { result } = renderHook(() => useDebouncedUpdateResource(medplum)); const mockPatient = { ...HomerSimpson } as Patient & { id: string }; const error = new Error('Update failed'); const updateSpy = vi.spyOn(medplum, 'updateResource').mockRejectedValue(error); let caughtError: Error | undefined; const rejectionHandler = (reason: unknown): void => { caughtError = reason as Error; }; process.on('unhandledRejection', rejectionHandler); try { const debouncedUpdate = result.current; const returnedValue = await debouncedUpdate(mockPatient); expect(returnedValue).toEqual(mockPatient); // Wait for debounce timeout - error happens inside debounced callback await waitFor( () => { expect(updateSpy).toHaveBeenCalled(); }, { timeout: DEFAULT_SAVE_TIMEOUT_MS + 500 } ); await new Promise<void>((resolve) => { setTimeout(() => { resolve(); }, 100); }); // Verify updateResource was called with the correct patient expect(updateSpy).toHaveBeenCalledWith(mockPatient); // Verify the error was caught (it happens in the debounced callback) expect(caughtError).toBe(error); } finally { process.removeListener('unhandledRejection', rejectionHandler); } }); });

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