Skip to main content
Glama
ValueSetPreview.test.tsx19.7 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { MantineProvider } from '@mantine/core'; import { Notifications } from '@mantine/notifications'; import type { Parameters, ValueSet } from '@medplum/fhirtypes'; import { MockClient } from '@medplum/mock'; import { ErrorBoundary, Loading, MedplumProvider } from '@medplum/react'; import { Suspense } from 'react'; import { MemoryRouter } from 'react-router'; import { act, fireEvent, render, screen, waitFor } from '../test-utils/render'; import { ValueSetPreview } from './ValueSetPreview'; const medplum = new MockClient(); describe('ValueSetPreview', () => { async function setup(valueSet: ValueSet): Promise<void> { await act(async () => { render( <MedplumProvider medplum={medplum}> <MemoryRouter> <MantineProvider> <Notifications /> <ErrorBoundary> <Suspense fallback={<Loading />}> <ValueSetPreview valueSet={valueSet} /> </Suspense> </ErrorBoundary> </MantineProvider> </MemoryRouter> </MedplumProvider> ); }); } async function selectValue(input: HTMLInputElement, searchText: string): Promise<void> { // Type to trigger search await act(async () => { fireEvent.change(input, { target: { value: searchText } }); }); // Wait for the drop down await act(async () => { jest.advanceTimersByTime(1000); }); // Press the down arrow to select first option await act(async () => { fireEvent.keyDown(input, { key: 'ArrowDown', code: 'ArrowDown' }); }); // Press "Enter" to confirm selection await act(async () => { fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' }); }); } beforeEach(() => { jest.useFakeTimers(); jest.clearAllMocks(); }); afterEach(async () => { await act(async () => { jest.runOnlyPendingTimers(); }); jest.useRealTimers(); }); test('Renders ValueSetAutocomplete', async () => { const valueSet = await medplum.createResource<ValueSet>({ resourceType: 'ValueSet', status: 'active', url: 'http://example.com/valueset/test', expansion: { timestamp: '2023-01-01T00:00:00.000Z', contains: [ { system: 'http://example.com/codesystem', code: 'code1', display: 'Display 1', }, ], }, }); // Mock valueSetExpand medplum.valueSetExpand = jest.fn().mockResolvedValue(valueSet); await setup(valueSet); expect(await screen.findByPlaceholderText('Select a value from the ValueSet')).toBeInTheDocument(); }); test('Displays selected value details', async () => { const valueSet = await medplum.createResource<ValueSet>({ resourceType: 'ValueSet', status: 'active', url: 'http://example.com/valueset/test', expansion: { timestamp: '2023-01-01T00:00:00.000Z', contains: [ { system: 'http://example.com/codesystem', code: 'code1', display: 'Display 1', }, ], }, }); medplum.valueSetExpand = jest.fn().mockResolvedValue(valueSet); medplum.get = jest.fn().mockResolvedValue({ resourceType: 'Parameters', parameter: [], }); await setup(valueSet); const input = (await screen.findByPlaceholderText('Select a value from the ValueSet')) as HTMLInputElement; await selectValue(input, 'Display'); // Wait for lookup to complete await waitFor(() => { expect(medplum.get).toHaveBeenCalled(); }); // Verify selected value details are displayed // The values appear in the DescriptionList, but may also appear in the autocomplete pill // So we check that they exist somewhere in the document await waitFor(() => { // Check that the values appear (they might be in the pill or in the description list) const allCode1 = screen.queryAllByText('code1'); expect(allCode1.length).toBeGreaterThan(0); const allSystem = screen.queryAllByText('http://example.com/codesystem'); expect(allSystem.length).toBeGreaterThan(0); const allDisplay1 = screen.queryAllByText('Display 1'); expect(allDisplay1.length).toBeGreaterThan(0); }); }); test('Calls CodeSystem lookup when value is selected', async () => { const valueSet = await medplum.createResource<ValueSet>({ resourceType: 'ValueSet', status: 'active', url: 'http://example.com/valueset/test', expansion: { timestamp: '2023-01-01T00:00:00.000Z', contains: [ { system: 'http://example.com/codesystem', code: 'code1', display: 'Display 1', }, ], }, }); const lookupResult: Parameters = { resourceType: 'Parameters', parameter: [ { name: 'name', valueString: 'Test Code System' }, { name: 'display', valueString: 'Display 1' }, { name: 'property', part: [ { name: 'code', valueCode: 'parent' }, { name: 'value', valueCode: 'parent-code' }, { name: 'description', valueString: 'Parent code' }, ], }, ], }; medplum.valueSetExpand = jest.fn().mockResolvedValue(valueSet); medplum.get = jest.fn().mockResolvedValue(lookupResult); await setup(valueSet); const input = (await screen.findByPlaceholderText('Select a value from the ValueSet')) as HTMLInputElement; await selectValue(input, 'Display'); // Wait for lookup to be called await waitFor(() => { expect(medplum.get).toHaveBeenCalled(); }); // Check that lookup was called with the correct URL (URL-encoded) expect(medplum.get).toHaveBeenCalledWith(expect.stringContaining('CodeSystem/$lookup')); expect(medplum.get).toHaveBeenCalledWith(expect.stringContaining('system=http%3A%2F%2Fexample.com%2Fcodesystem')); expect(medplum.get).toHaveBeenCalledWith(expect.stringContaining('code=code1')); }); test('Shows loading state during lookup', async () => { const valueSet = await medplum.createResource<ValueSet>({ resourceType: 'ValueSet', status: 'active', url: 'http://example.com/valueset/test', expansion: { timestamp: '2023-01-01T00:00:00.000Z', contains: [ { system: 'http://example.com/codesystem', code: 'code1', display: 'Display 1', }, ], }, }); medplum.valueSetExpand = jest.fn().mockResolvedValue(valueSet); // Mock a delayed lookup response let resolveLookup: (value: Parameters) => void; const lookupPromise = new Promise<Parameters>((resolve) => { resolveLookup = resolve; }); medplum.get = jest.fn().mockReturnValue(lookupPromise); await setup(valueSet); const input = (await screen.findByPlaceholderText('Select a value from the ValueSet')) as HTMLInputElement; await selectValue(input, 'Display'); // Verify loading state appears expect(await screen.findByText('Loading properties...')).toBeInTheDocument(); // Resolve the lookup await act(async () => { if (resolveLookup) { resolveLookup({ resourceType: 'Parameters', parameter: [{ name: 'display', valueString: 'Display 1' }], }); await lookupPromise; } }); // Loading should disappear await waitFor(() => { expect(screen.queryByText('Loading properties...')).not.toBeInTheDocument(); }); }); test('Displays error when lookup fails', async () => { const valueSet = await medplum.createResource<ValueSet>({ resourceType: 'ValueSet', status: 'active', url: 'http://example.com/valueset/test', expansion: { timestamp: '2023-01-01T00:00:00.000Z', contains: [ { system: 'http://example.com/codesystem', code: 'code1', display: 'Display 1', }, ], }, }); medplum.valueSetExpand = jest.fn().mockResolvedValue(valueSet); medplum.get = jest.fn().mockRejectedValue(new Error('Lookup failed')); await setup(valueSet); const input = (await screen.findByPlaceholderText('Select a value from the ValueSet')) as HTMLInputElement; await selectValue(input, 'Display'); // Wait for error to appear await waitFor(() => { expect(screen.getByText(/Lookup Failed/)).toBeInTheDocument(); expect(screen.getByText(/Failed to retrieve code information/)).toBeInTheDocument(); }); }); test('Does not render properties section when lookup has no properties', async () => { const valueSet = await medplum.createResource<ValueSet>({ resourceType: 'ValueSet', status: 'active', url: 'http://example.com/valueset/test', expansion: { timestamp: '2023-01-01T00:00:00.000Z', contains: [ { system: 'http://example.com/codesystem', code: 'code1', display: 'Display 1', }, ], }, }); const lookupResult: Parameters = { resourceType: 'Parameters', parameter: [ { name: 'name', valueString: 'Test Code System' }, { name: 'display', valueString: 'Display 1' }, // No property parameters ], }; medplum.valueSetExpand = jest.fn().mockResolvedValue(valueSet); medplum.get = jest.fn().mockResolvedValue(lookupResult); await setup(valueSet); const input = (await screen.findByPlaceholderText('Select a value from the ValueSet')) as HTMLInputElement; await selectValue(input, 'Display'); // Wait for lookup to complete await waitFor(() => { expect(medplum.get).toHaveBeenCalled(); }); // Verify that Code, System, and Display are shown expect(await screen.findByText('code1')).toBeInTheDocument(); // But no properties section should be rendered expect(screen.queryByText('Properties')).not.toBeInTheDocument(); }); test('Renders properties when lookup returns properties', async () => { const valueSet = await medplum.createResource<ValueSet>({ resourceType: 'ValueSet', status: 'active', url: 'http://example.com/valueset/test', expansion: { timestamp: '2023-01-01T00:00:00.000Z', contains: [ { system: 'http://example.com/codesystem', code: 'code1', display: 'Display 1', }, ], }, }); const lookupResult: Parameters = { resourceType: 'Parameters', parameter: [ { name: 'property', part: [ { name: 'code', valueCode: 'parent' }, { name: 'value', valueCode: 'parent-code' }, { name: 'description', valueString: 'Parent code' }, ], }, ], }; medplum.valueSetExpand = jest.fn().mockResolvedValue(valueSet); medplum.get = jest.fn().mockResolvedValue(lookupResult); await setup(valueSet); const input = (await screen.findByPlaceholderText('Select a value from the ValueSet')) as HTMLInputElement; await selectValue(input, 'Display'); // Wait for properties to be rendered await waitFor(() => { expect(screen.getByText('Properties')).toBeInTheDocument(); expect(screen.getByText('parent')).toBeInTheDocument(); expect(screen.getByText('parent-code')).toBeInTheDocument(); expect(screen.getByText('Parent code')).toBeInTheDocument(); }); }); test('Renders properties with different value types', async () => { const valueSet = await medplum.createResource<ValueSet>({ resourceType: 'ValueSet', status: 'active', url: 'http://example.com/valueset/test', expansion: { timestamp: '2023-01-01T00:00:00.000Z', contains: [ { system: 'http://example.com/codesystem', code: 'code1', display: 'Display 1', }, ], }, }); const lookupResult: Parameters = { resourceType: 'Parameters', parameter: [ { name: 'property', part: [ { name: 'code', valueCode: 'test1' }, { name: 'value', valueString: 'string-value' }, { name: 'description', valueString: 'String property' }, ], }, { name: 'property', part: [ { name: 'code', valueCode: 'test2' }, { name: 'value', valueBoolean: true }, { name: 'description', valueString: 'Boolean property' }, ], }, { name: 'property', part: [ { name: 'code', valueCode: 'test3' }, { name: 'value', valueInteger: 42 }, { name: 'description', valueString: 'Integer property' }, ], }, { name: 'property', part: [ { name: 'code', valueCode: 'test4' }, { name: 'value', valueDecimal: 3.14 }, { name: 'description', valueString: 'Decimal property' }, ], }, { name: 'property', part: [ { name: 'code', valueCode: 'test5' }, { name: 'value', valueDate: '2023-01-01' }, { name: 'description', valueString: 'Date property' }, ], }, { name: 'property', part: [ { name: 'code', valueCode: 'test6' }, { name: 'value', valueDateTime: '2023-01-01T12:00:00Z' }, { name: 'description', valueString: 'DateTime property' }, ], }, { name: 'property', part: [ { name: 'code', valueCode: 'test7' }, { name: 'value', valueCode: 'code-value' }, { name: 'description', valueString: 'Code property' }, ], }, ], }; medplum.valueSetExpand = jest.fn().mockResolvedValue(valueSet); medplum.get = jest.fn().mockResolvedValue(lookupResult); await setup(valueSet); const input = (await screen.findByPlaceholderText('Select a value from the ValueSet')) as HTMLInputElement; await selectValue(input, 'Display'); // Wait for all properties to be rendered await waitFor(() => { expect(screen.getByText('string-value')).toBeInTheDocument(); expect(screen.getByText('true')).toBeInTheDocument(); expect(screen.getByText('42')).toBeInTheDocument(); expect(screen.getByText('3.14')).toBeInTheDocument(); expect(screen.getByText('2023-01-01')).toBeInTheDocument(); expect(screen.getByText('2023-01-01T12:00:00Z')).toBeInTheDocument(); expect(screen.getByText('code-value')).toBeInTheDocument(); }); }); test('Handles property with no value (shows N/A)', async () => { const valueSet = await medplum.createResource<ValueSet>({ resourceType: 'ValueSet', status: 'active', url: 'http://example.com/valueset/test', expansion: { timestamp: '2023-01-01T00:00:00.000Z', contains: [ { system: 'http://example.com/codesystem', code: 'code1', display: 'Display 1', }, ], }, }); const lookupResult: Parameters = { resourceType: 'Parameters', parameter: [ { name: 'property', part: [ { name: 'code', valueCode: 'test' }, { name: 'value' }, // No value provided { name: 'description', valueString: 'No value property' }, ], }, ], }; medplum.valueSetExpand = jest.fn().mockResolvedValue(valueSet); medplum.get = jest.fn().mockResolvedValue(lookupResult); await setup(valueSet); const input = (await screen.findByPlaceholderText('Select a value from the ValueSet')) as HTMLInputElement; await selectValue(input, 'Display'); // Wait for property to be rendered with N/A await waitFor(() => { expect(screen.getByText('N/A')).toBeInTheDocument(); }); }); test('Handles value without display', async () => { const valueSet = await medplum.createResource<ValueSet>({ resourceType: 'ValueSet', status: 'active', url: 'http://example.com/valueset/test', expansion: { timestamp: '2023-01-01T00:00:00.000Z', contains: [ { system: 'http://example.com/codesystem', code: 'code1', // No display - but ValueSetAutocomplete might add one, so we'll test differently }, ], }, }); medplum.valueSetExpand = jest.fn().mockResolvedValue(valueSet); medplum.get = jest.fn().mockResolvedValue({ resourceType: 'Parameters', parameter: [], }); await setup(valueSet); const input = (await screen.findByPlaceholderText('Select a value from the ValueSet')) as HTMLInputElement; // Type to trigger search await act(async () => { fireEvent.change(input, { target: { value: 'code' } }); jest.advanceTimersByTime(1000); }); // Press the down arrow await act(async () => { fireEvent.keyDown(input, { key: 'ArrowDown', code: 'ArrowDown' }); }); // Press "Enter" await act(async () => { fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' }); }); // Wait for lookup to complete await waitFor( () => { expect(medplum.get).toHaveBeenCalled(); }, { timeout: 3000 } ); // Code and System should be displayed await waitFor(() => { // The values appear in the DescriptionList, but may also appear in the autocomplete pill const allCode1 = screen.queryAllByText('code1'); expect(allCode1.length).toBeGreaterThan(0); const allSystem = screen.queryAllByText('http://example.com/codesystem'); expect(allSystem.length).toBeGreaterThan(0); }); }); test('Handles property with unknown part name', async () => { const valueSet = await medplum.createResource<ValueSet>({ resourceType: 'ValueSet', status: 'active', url: 'http://example.com/valueset/test', expansion: { timestamp: '2023-01-01T00:00:00.000Z', contains: [ { system: 'http://example.com/codesystem', code: 'code1', display: 'Display 1', }, ], }, }); const lookupResult: Parameters = { resourceType: 'Parameters', parameter: [ { name: 'property', part: [ { name: 'code', valueCode: 'test' }, { name: 'unknownPart', valueString: 'unknown value' }, // Unknown part name { name: 'description', valueString: 'Test property' }, ], }, ], }; medplum.valueSetExpand = jest.fn().mockResolvedValue(valueSet); medplum.get = jest.fn().mockResolvedValue(lookupResult); await setup(valueSet); const input = (await screen.findByPlaceholderText('Select a value from the ValueSet')) as HTMLInputElement; await selectValue(input, 'Display'); // Wait for properties to be rendered await waitFor(() => { expect(screen.getByText('Properties')).toBeInTheDocument(); expect(screen.getByText('test')).toBeInTheDocument(); expect(screen.getByText('Test property')).toBeInTheDocument(); }); // Unknown part should be ignored (return null), so unknown value should not appear expect(screen.queryByText('unknown value')).not.toBeInTheDocument(); }); });

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