Skip to main content
Glama
MedplumProvider.test.tsx17.3 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { MedplumClientEventMap, ProfileResource } from '@medplum/core'; import { ClientStorage, MemoryStorage, MockAsyncClientStorage, getDisplayString, sleep } from '@medplum/core'; import { FhirRouter, MemoryRepository } from '@medplum/fhir-router'; import { MockClient, MockFetchClient, createFakeJwt } from '@medplum/mock'; import { act, render, screen } from '@testing-library/react'; import type { JSX } from 'react'; import { useEffect, useRef, useState } from 'react'; import { MedplumProvider } from './MedplumProvider'; import { useMedplum, useMedplumContext, useMedplumNavigate, useMedplumProfile } from './MedplumProvider.context'; describe('MedplumProvider', () => { test('Renders component', () => { function MyComponent(): JSX.Element { const medplum = useMedplum(); const context = useMedplumContext(); const navigate = useMedplumNavigate(); const profile = useMedplumProfile(); return ( <div> <div>MyComponent</div> <div>{getDisplayString(medplum.getProfile() as ProfileResource)}</div> <div>Context: {Boolean(context).toString()}</div> <div>Navigate: {Boolean(navigate).toString()}</div> <div>Profile: {Boolean(profile).toString()}</div> </div> ); } render( <MedplumProvider medplum={new MockClient()}> <MyComponent /> </MedplumProvider> ); expect(screen.getByText('MyComponent')).toBeInTheDocument(); expect(screen.getByText('Alice Smith')).toBeInTheDocument(); expect(screen.getByText('Context: true')).toBeInTheDocument(); expect(screen.getByText('Navigate: true')).toBeInTheDocument(); expect(screen.getByText('Profile: true')).toBeInTheDocument(); }); describe('Loading is always in sync with MedplumClient#isLoading()', () => { function MyComponent(): JSX.Element { const { loading } = useMedplumContext(); return loading ? <div>Loading...</div> : <div>Loaded!</div>; } test('No active login & AsyncClientStorage', async () => { let storage: MockAsyncClientStorage; let medplum!: MockClient; act(() => { storage = new MockAsyncClientStorage(); medplum = new MockClient({ storage }); }); expect(medplum.isLoading()).toEqual(true); act(() => { render( <MedplumProvider medplum={medplum}> <MyComponent /> </MedplumProvider> ); }); expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(true); // Sleep to make sure that loading doesn't go to false before we set storage to initialized await act(async () => { await sleep(250); }); expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(true); // Finally set storage to initialized act(() => { storage.setInitialized(); }); expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(true); expect(await screen.findByText('Loaded!')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(false); }); test('Active login & AsyncClientStorage', async () => { let storage: MockAsyncClientStorage; let medplum!: MockClient; act(() => { storage = new MockAsyncClientStorage(); storage.setObject('activeLogin', { // This access token contains a field `login_id` which is necessary to pass the validation required in `isMedplumAccessToken` // to get into the state of `MedplumClient.medplumServer === true` accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJsb2dpbl9pZCI6InRlc3RpbmcxMjMifQ.lJGCbp2taTarRbamxaKFsTR_VRVgzvttKMmI5uFQSM0', refreshToken: '456', profile: { reference: 'Practitioner/123', }, project: { reference: 'Project/123', }, }); medplum = new MockClient({ storage, }); }); const getSpy = jest.spyOn(medplum, 'get'); expect(medplum.isLoading()).toEqual(true); act(() => { render( <MedplumProvider medplum={medplum}> <MyComponent /> </MedplumProvider> ); }); expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(true); // Sleep to make sure that loading doesn't go to false before we set storage to initialized await act(async () => { await sleep(250); }); expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(true); // Finally set storage to initialized act(() => { storage.setInitialized(); }); expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(true); // Sleep to make sure that we can resolve stuff await act(async () => { await sleep(0); }); expect(await screen.findByText('Loaded!')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(false); expect(getSpy).toHaveBeenCalledWith('auth/me', expect.any(Object)); getSpy.mockRestore(); }); test('No active login & NO AsyncClientStorage', async () => { const baseUrl = 'https://example.com/'; let medplum!: MockClient; const router = new FhirRouter(); const repo = new MemoryRepository(); const client = new MockFetchClient(router, repo, baseUrl); const mockFetchSpy = jest.spyOn(client, 'mockFetch'); act(() => { medplum = new MockClient(); }); expect(medplum.isLoading()).toEqual(true); act(() => { render( <MedplumProvider medplum={medplum}> <MyComponent /> </MedplumProvider> ); }); expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(true); expect(await screen.findByText('Loaded!')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(false); expect(mockFetchSpy).not.toHaveBeenCalledWith(`${baseUrl}auth/me`, expect.objectContaining({ method: 'GET' })); }); test('Active login & NO AsyncClientStorage', async () => { const baseUrl = 'https://example.com/'; const storage = new ClientStorage(new MemoryStorage()); storage.setObject('activeLogin', { accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJsb2dpbl9pZCI6InRlc3RpbmcxMjMifQ.lJGCbp2taTarRbamxaKFsTR_VRVgzvttKMmI5uFQSM0', refreshToken: '456', profile: { reference: 'Practitioner/123', }, project: { reference: 'Project/123', }, }); let medplum!: MockClient; const router = new FhirRouter(); const repo = new MemoryRepository(); const client = new MockFetchClient(router, repo, baseUrl); const mockFetchSpy = jest.spyOn(client, 'mockFetch'); act(() => { medplum = new MockClient({ storage, mockFetchOverride: { router, repo, client }, }); }); expect(medplum.isLoading()).toEqual(true); act(() => { render( <MedplumProvider medplum={medplum}> <MyComponent /> </MedplumProvider> ); }); expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(true); expect(await screen.findByText('Loaded!')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(false); expect(mockFetchSpy).toHaveBeenCalledWith(`${baseUrl}auth/me`, expect.objectContaining({ method: 'GET' })); mockFetchSpy.mockRestore(); }); test('Refreshing profile re-triggers loading when no profile present', async () => { const baseUrl = 'https://example.com/'; const storage = new ClientStorage(new MemoryStorage()); storage.setObject('activeLogin', { accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJsb2dpbl9pZCI6InRlc3RpbmcxMjMifQ.lJGCbp2taTarRbamxaKFsTR_VRVgzvttKMmI5uFQSM0', refreshToken: '456', profile: { reference: 'Practitioner/123', }, project: { reference: 'Project/123', }, }); let medplum!: MockClient; const router = new FhirRouter(); const repo = new MemoryRepository(); const client = new MockFetchClient(router, repo, baseUrl); const mockFetchSpy = jest.spyOn(client, 'mockFetch'); let dispatchEventSpy!: jest.SpyInstance; act(() => { medplum = new MockClient({ storage, mockFetchOverride: { router, repo, client }, }); dispatchEventSpy = jest.spyOn(medplum, 'dispatchEvent'); }); expect(medplum.isLoading()).toEqual(true); act(() => { render( <MedplumProvider medplum={medplum}> <MyComponent /> </MedplumProvider> ); }); expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(true); expect(await screen.findByText('Loaded!')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(false); expect(mockFetchSpy).toHaveBeenCalledWith(`${baseUrl}auth/me`, expect.objectContaining({ method: 'GET' })); expect(dispatchEventSpy).toHaveBeenCalledWith({ type: 'profileRefreshed' }); mockFetchSpy.mockClear(); dispatchEventSpy.mockClear(); let loginPromise!: Promise<void>; act(() => { // We clear the active login so that we can test what happens when we call `getProfileAsync()` // Which we want to call `refreshProfile()` // Which is only possible if `sessionDetails` is not defined medplum.clearActiveLogin(); medplum.setAccessToken( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJsb2dpbl9pZCI6InRlc3RpbmcxMjMifQ.lJGCbp2taTarRbamxaKFsTR_VRVgzvttKMmI5uFQSM0' ); // This is what is testing that we go back to a loading state when profile is being refreshed loginPromise = medplum.setActiveLogin({ accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJsb2dpbl9pZCI6InRlc3RpbmcxMjMifQ.lJGCbp2taTarRbamxaKFsTR_VRVgzvttKMmI5uFQSM0', refreshToken: '456', profile: { reference: 'Practitioner/123', }, project: { reference: 'Project/123', }, }); }); expect(medplum.isLoading()).toEqual(true); expect(await screen.findByText('Loading...')).toBeInTheDocument(); expect(dispatchEventSpy).toHaveBeenCalledWith({ type: 'profileRefreshing' }); expect(await screen.findByText('Loaded!')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(false); expect(dispatchEventSpy).toHaveBeenCalledWith({ type: 'profileRefreshed' }); await loginPromise; expect(mockFetchSpy).toHaveBeenCalledWith(`${baseUrl}auth/me`, expect.objectContaining({ method: 'GET' })); }); test('Refreshing profile when profile present does not unmount children guarded by `.isLoading()`', async () => { function RenderCounter({ parentRenderCount }: { parentRenderCount: number }): JSX.Element { const [childCount, setChildCount] = useState(0); useEffect(() => { setChildCount((prevCount) => prevCount + 1); }, [parentRenderCount]); return ( <div> <div>Parent count: {parentRenderCount}</div> <div>Child count: {childCount}</div> </div> ); } function MyComponent(): JSX.Element { const renderCountRef = useRef(0); renderCountRef.current += 1; const { loading } = useMedplumContext(); if (loading) { return <div>Loading...</div>; } return <RenderCounter parentRenderCount={renderCountRef.current} />; } const baseUrl = 'https://example.com/'; const storage = new ClientStorage(new MemoryStorage()); storage.setObject('activeLogin', { accessToken: createFakeJwt({ sub: '1234567890', iat: Math.ceil(Date.now() / 1000), exp: Math.ceil(Date.now() / 1000) + 60 * 60, client_id: '123', login_id: '123', }), refreshToken: createFakeJwt({ client_id: '123' }), profile: { reference: 'Practitioner/123', }, project: { reference: 'Project/123', }, }); let medplum!: MockClient; const router = new FhirRouter(); const repo = new MemoryRepository(); const client = new MockFetchClient(router, repo, baseUrl); const mockFetchSpy = jest.spyOn(client, 'mockFetch'); let dispatchEventSpy!: jest.SpyInstance; act(() => { medplum = new MockClient({ storage, mockFetchOverride: { router, repo, client }, }); dispatchEventSpy = jest.spyOn(medplum, 'dispatchEvent'); }); expect(medplum.isLoading()).toEqual(true); act(() => { render( <MedplumProvider medplum={medplum}> <MyComponent /> </MedplumProvider> ); }); expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(true); expect(await screen.findByText('Parent count: 2')).toBeInTheDocument(); expect(await screen.findByText('Child count: 1')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(false); expect(mockFetchSpy).toHaveBeenCalledWith(`${baseUrl}auth/me`, expect.objectContaining({ method: 'GET' })); expect(dispatchEventSpy).toHaveBeenCalledWith({ type: 'profileRefreshed' }); mockFetchSpy.mockClear(); dispatchEventSpy.mockClear(); const refreshingPromise = new Promise((resolve) => { medplum.addEventListener('profileRefreshing', resolve); }); act(() => { medplum.refreshIfExpired(1000 * 60 * 60 * 2 /* 2 hours in ms */).catch(console.error); }); const refreshedPromise = new Promise((resolve) => { medplum.addEventListener('profileRefreshing', resolve); }); await act(async () => { await refreshingPromise; expect(medplum.isLoading()).toEqual(false); await refreshedPromise; }); expect(dispatchEventSpy).toHaveBeenCalledWith({ type: 'profileRefreshing' }); expect(dispatchEventSpy).toHaveBeenCalledWith({ type: 'profileRefreshed' }); expect(medplum.isLoading()).toEqual(false); expect(screen.getByText('Parent count: 4')).toBeInTheDocument(); expect(screen.getByText('Child count: 3')).toBeInTheDocument(); expect(mockFetchSpy).toHaveBeenLastCalledWith(`${baseUrl}auth/me`, expect.objectContaining({ method: 'GET' })); }); test('Async ClientStorage.getInitPromise throws', async () => { const originalConsoleError = console.error; console.error = jest.fn(); class TestAsyncStorage extends MockAsyncClientStorage { private promise: Promise<void>; private reject!: (err: Error) => void; constructor() { super(); this.promise = new Promise((_resolve, reject) => { this.reject = reject; }); } getInitPromise(): Promise<void> { return this.promise; } rejectInitPromise(): void { this.reject(new Error('Failed to init storage!')); } } const storage = new TestAsyncStorage(); let medplum!: MockClient; let dispatchEventSpy!: jest.SpyInstance; act(() => { medplum = new MockClient({ storage, }); dispatchEventSpy = jest.spyOn(medplum, 'dispatchEvent'); }); expect(medplum.isLoading()).toEqual(true); act(() => { render( <MedplumProvider medplum={medplum}> <MyComponent /> </MedplumProvider> ); }); expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(true); act(() => { storage.rejectInitPromise(); }); expect(await screen.findByText('Loaded!')).toBeInTheDocument(); expect(medplum.isLoading()).toEqual(false); expect(dispatchEventSpy).toHaveBeenCalledWith<[MedplumClientEventMap['storageInitFailed']]>({ type: 'storageInitFailed', payload: { error: new Error('Failed to init storage!') }, }); expect(dispatchEventSpy).not.toHaveBeenCalledWith<[MedplumClientEventMap['storageInitialized']]>({ type: 'storageInitialized', }); expect(console.error).toHaveBeenCalledWith(new Error('Failed to init storage!')); console.error = originalConsoleError; }); }); });

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