Skip to main content
Glama
ReferenceRangeEditor.test.tsx17 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { deepClone } from '@medplum/core'; import type { ObservationDefinition } from '@medplum/fhirtypes'; import { MockClient } from '@medplum/mock'; import { MedplumProvider } from '@medplum/react-hooks'; import { StrictMode } from 'react'; import { MemoryRouter } from 'react-router'; import { HDLDefinition, TestosteroneDefinition } from '../stories/referenceLab'; import { act, fireEvent, render, screen } from '../test-utils/render'; import type { ReferenceRangeEditorProps } from './ReferenceRangeEditor'; import { ReferenceRangeEditor } from './ReferenceRangeEditor'; const medplum = new MockClient(); async function setup(args: ReferenceRangeEditorProps): Promise<void> { await act(async () => { render( <StrictMode> <MemoryRouter> <MedplumProvider medplum={medplum}> <ReferenceRangeEditor {...args} /> </MedplumProvider> </MemoryRouter> </StrictMode> ); }); } describe('ReferenceRangeEditor', () => { /** * Render empty object */ test('Renders empty', async () => { await setup({ definition: { resourceType: 'ObservationDefinition', } as ObservationDefinition, onSubmit: jest.fn(), }); const checkAddButton = screen.getByTitle('Add Group'); expect(screen.getByTestId('reference-range-editor')).toBeDefined(); expect(checkAddButton).toBeDefined(); expect(checkAddButton.tagName).toEqual('BUTTON'); }); /** * Add group with no filters. Make sure value looks ok */ test('Add and Remove group', async () => { const onSubmit = jest.fn(); await setup({ definition: { resourceType: 'ObservationDefinition', } as ObservationDefinition, onSubmit, }); fireEvent.click(screen.getByTitle('Add Group')); fireEvent.click(screen.getByTitle('Remove Group')); expect(screen.queryByTestId(/'group-id-.*'/)).toBeNull(); fireEvent.click(screen.getByText('Save')); expect(onSubmit).toHaveBeenCalledWith({ qualifiedInterval: [], resourceType: 'ObservationDefinition', }); }); /** * Add group with no filters. Make sure value looks ok */ test('Add Empty Group', async () => { const onSubmit = jest.fn(); await setup({ definition: { resourceType: 'ObservationDefinition', } as ObservationDefinition, onSubmit, }); const addGroupButton = screen.getByTitle('Add Group'); fireEvent.click(addGroupButton); fireEvent.click(screen.getByTitle('Add Interval')); const genderDropdown = screen.getByLabelText('Gender:'); expect(genderDropdown).toBeDefined(); // Change the gender filter for the group (should be a no-op) fireEvent.change(genderDropdown, { target: { value: 'male' } }); fireEvent.click(screen.getByText('Save')); // Add a second empty group expect(screen.getByTestId('group-id-1')).toBeDefined(); fireEvent.click(addGroupButton); expect(screen.getByTestId('group-id-2')).toBeDefined(); expect(onSubmit).toHaveBeenCalledWith({ qualifiedInterval: [], resourceType: 'ObservationDefinition', }); }); /** * Create a group 'hole'. Make sure ids are monotonic */ test('Non-sequential groups', async () => { const onSubmit = jest.fn(); await setup({ definition: { resourceType: 'ObservationDefinition', } as ObservationDefinition, onSubmit, }); const addGroupButton = screen.getByTitle('Add Group'); // Add 3 groups fireEvent.click(addGroupButton); fireEvent.click(addGroupButton); fireEvent.click(addGroupButton); // Remove group #2 fireEvent.click(screen.getByTestId('remove-group-button-group-id-2')); // Add a new group and make sure it's number 4 fireEvent.click(addGroupButton); expect(screen.getByTestId('remove-group-button-group-id-4')).toBeDefined(); }); /** * Modify HDL gender filter. Make sure submitted value reflects filter */ test('Set Gender Filter', async () => { const onSubmit = jest.fn(); await setup({ definition: HDLDefinition, onSubmit, }); const genderDropdown = screen.getByLabelText('Gender:'); fireEvent.change(genderDropdown, { target: { value: 'female' } }); fireEvent.click(screen.getByText('Save')); const checkSubmitted = onSubmit.mock.calls[0][0] as ObservationDefinition; expect(checkSubmitted.qualifiedInterval).toMatchObject( Array(3).fill({ gender: 'female', }) ); }); /** * Modify HDL age filter. Make sure submitted value reflects filter */ test('Set Age Filter', async () => { const onSubmit = jest.fn(); await setup({ definition: HDLDefinition, onSubmit, }); const ageLowInput = screen.getByTestId('age-group-id-1-low-value'); const ageHighInput = screen.getByTestId('age-group-id-1-high-value'); fireEvent.change(ageLowInput, { target: { value: '10' } }); fireEvent.change(ageHighInput, { target: { value: '18' } }); fireEvent.click(screen.getByText('Save')); const checkSubmitted = onSubmit.mock.calls[0][0] as ObservationDefinition; expect(checkSubmitted.qualifiedInterval).toMatchObject( Array(3).fill({ age: { low: { value: 10, unit: 'years', system: 'http://unitsofmeasure.org' }, high: { value: 18, unit: 'years', system: 'http://unitsofmeasure.org' }, }, }) ); }); /** * Modify HDL endocrine filter. Make sure submitted value reflects filter */ test('Set Endocrine Filter', async () => { const onSubmit = jest.fn(); await setup({ definition: HDLDefinition, onSubmit, }); const endocrineInput = screen.getByLabelText('Endocrine:'); fireEvent.change(endocrineInput, { target: { value: 'luteal' } }); fireEvent.click(screen.getByText('Save')); const checkSubmitted = onSubmit.mock.calls[0][0] as ObservationDefinition; expect(checkSubmitted.qualifiedInterval).toMatchObject( Array(3).fill({ context: { text: 'luteal', }, }) ); }); /** * Modify null HDL filter to a value to something and back to null. Make sure resulting values are "undefined" */ test('Set Null Filter', async () => { const onSubmit = jest.fn(); await setup({ definition: HDLDefinition, onSubmit, }); const genderDropdown = screen.getByLabelText('Gender:'); fireEvent.change(genderDropdown, { target: { value: 'female' } }); fireEvent.change(genderDropdown, { target: { value: '' } }); const endocrineDropdown = screen.getByLabelText('Endocrine:'); fireEvent.change(endocrineDropdown, { target: { value: 'luteal' } }); fireEvent.change(endocrineDropdown, { target: { value: '' } }); const categoryDropdown = screen.getByLabelText('Category:'); fireEvent.change(categoryDropdown, { target: { value: 'reference' } }); fireEvent.change(categoryDropdown, { target: { value: '' } }); fireEvent.click(screen.getByText('Save')); const checkSubmitted = onSubmit.mock.calls[0][0] as ObservationDefinition; expect(checkSubmitted.qualifiedInterval).toMatchObject( Array(3).fill({ gender: undefined, context: undefined, category: undefined, }) ); }); /** * Modify category filter. Make sure submitted value reflects filter */ test('Set Category Filter', async () => { const onSubmit = jest.fn(); await setup({ definition: HDLDefinition, onSubmit, }); const input = screen.getByLabelText('Category:'); fireEvent.change(input, { target: { value: 'absolute' } }); fireEvent.click(screen.getByText('Save')); const checkSubmitted = onSubmit.mock.calls[0][0] as ObservationDefinition; expect(checkSubmitted.qualifiedInterval).toMatchObject( Array(3).fill({ category: 'absolute', }) ); }); /** * Add an interval, and ensure that unit is pre-populated */ test('Add Interval', async () => { const onSubmit = jest.fn(); await setup({ definition: HDLDefinition, onSubmit, }); fireEvent.click(screen.getByTitle('Add Interval')); const unitInputs = screen .getAllByTestId(/range-id-\d+-low-unit/) .map((element) => (element as HTMLInputElement).value); expect(unitInputs).toHaveLength(4); const lastUnitInput = screen.getByTestId('range-id-4-low-unit') as HTMLInputElement; expect(lastUnitInput.value).toEqual('mg/dL'); }); /** * Add an interval with existing filters, and ensure that filters propagate */ test('Add Interval w/ filters', async () => { const onSubmit = jest.fn(); const definition = deepClone(HDLDefinition); definition.qualifiedInterval = definition.qualifiedInterval?.map((interval) => ({ ...interval, gender: 'female', age: { low: { value: 10 }, high: { value: 15 } }, })); await setup({ definition: definition, onSubmit, }); // Add a new interval fireEvent.click(screen.getByTitle('Add Interval')); fireEvent.change(screen.getByTestId('range-id-4-low-value'), { target: { value: 99 } }); // Save all intervals fireEvent.click(screen.getByText('Save')); // Ensure that all intervals receive the filters const checkSubmission = onSubmit.mock.calls[0][0] as ObservationDefinition; expect(checkSubmission.qualifiedInterval?.map((interval) => interval.gender)).toMatchObject( Array(4).fill('female') ); expect(checkSubmission.qualifiedInterval?.map((interval) => interval.age)).toMatchObject( Array(4).fill({ low: { value: 10 }, high: { value: 15 } }) ); }); /** * Remove an interval. Test Submitted Observation def */ test('Remove Interval', async () => { const onSubmit = jest.fn(); await setup({ definition: HDLDefinition, onSubmit, }); fireEvent.click(screen.getAllByTitle('Remove Interval')[1]); fireEvent.click(screen.getByText('Save')); const checkSubmission = onSubmit.mock.calls[0][0] as ObservationDefinition; const checkIntervalIds = checkSubmission.qualifiedInterval?.map((interval) => interval.id); expect(checkSubmission.qualifiedInterval).toHaveLength(2); expect(checkSubmission.qualifiedInterval?.[0].range).toMatchObject({ high: { value: 39, unit: 'mg/dL', system: 'http://unitsofmeasure.org', }, }); expect(checkSubmission.qualifiedInterval?.[1].range).toMatchObject({ low: { value: 61, unit: 'mg/dL', system: 'http://unitsofmeasure.org', }, }); expect(checkIntervalIds).toMatchObject(['id-1', 'id-3']); }); /** * Test existing ids (numeric + non-numeric). */ test('Interval Ids', async () => { const definition = deepClone(HDLDefinition); if (definition.qualifiedInterval?.[0]) { definition.qualifiedInterval[0].id = 'id-66'; } if (definition.qualifiedInterval?.[1]) { definition.qualifiedInterval[1].id = 'id-foo'; } if (definition.qualifiedInterval?.[2]) { definition.qualifiedInterval[2].id = 'id-21'; } const onSubmit = jest.fn(); await setup({ definition, onSubmit, }); // Add an interval and submit. The id should be one more than the highest existing interval fireEvent.click(screen.getByTitle('Add Interval')); fireEvent.click(screen.getByTitle('Add Interval')); fireEvent.change(screen.getByTestId('range-id-67-low-value'), { target: { value: 2 } }); fireEvent.change(screen.getByTestId('range-id-68-low-value'), { target: { value: 8 } }); fireEvent.click(screen.getByText('Save')); const checkSubmission = onSubmit.mock.calls[0][0] as ObservationDefinition; const checkIntervalIds = checkSubmission.qualifiedInterval?.map((interval) => interval.id); expect(checkIntervalIds).toMatchObject(['id-66', 'id-foo', 'id-21', 'id-67', 'id-68']); }); /** * Test existing ids (numeric + non-numeric). */ test('Interval Ids with "hole"', async () => { const definition = deepClone(HDLDefinition); if (definition.qualifiedInterval?.[0]) { definition.qualifiedInterval[0].id = 'id-1'; } if (definition.qualifiedInterval?.[1]) { delete definition.qualifiedInterval[1].id; } if (definition.qualifiedInterval?.[2]) { definition.qualifiedInterval[2].id = 'id-2'; } const onSubmit = jest.fn(); await setup({ definition, onSubmit, }); fireEvent.click(screen.getByText('Save')); const checkSubmission = onSubmit.mock.calls[0][0] as ObservationDefinition; const checkIntervalIds = checkSubmission.qualifiedInterval?.map((interval) => interval.id); expect(new Set(checkIntervalIds).size).toBe(3); expect(checkIntervalIds).toMatchObject(['id-1', 'id-3', 'id-2']); }); /** * Modify HDL value. Make sure submitted interval reflects change */ test('Update Range', async () => { const onSubmit = jest.fn(); await setup({ definition: HDLDefinition, onSubmit, }); const valueInput = screen.getByTestId('range-id-1-low-value'); fireEvent.change(valueInput, { target: { value: '3' } }); fireEvent.click(screen.getByText('Save')); const checkSubmitted = onSubmit.mock.calls[0][0] as ObservationDefinition; expect(checkSubmitted.qualifiedInterval?.[0].range?.low?.value).toEqual(3); }); /** * Modify HDL condition. Make sure submitted interval reflects change */ test('Update Condition', async () => { const onSubmit = jest.fn(); await setup({ definition: HDLDefinition, onSubmit, }); const conditionInput = screen.getByTestId('condition-id-1'); fireEvent.change(conditionInput, { target: { value: 'Very Low ' } }); fireEvent.click(screen.getByText('Save')); const checkSubmitted = onSubmit.mock.calls[0][0] as ObservationDefinition; expect(checkSubmitted.qualifiedInterval?.map((interval) => interval.condition)).toMatchObject([ 'Very Low', 'Normal risk', 'Negative risk', ]); }); /** * Make sure updating one group's filters does not affect the others */ test('Update Multiple Group Filters', async () => { const onSubmit = jest.fn(); await setup({ definition: deepClone(TestosteroneDefinition), onSubmit, }); const groups = screen.getAllByTestId(/^group-id-\d+/); expect(groups).toHaveLength(7); const ageHighInput = screen.getByTestId('age-group-id-1-high-value'); const ageLowInput = screen.getByTestId('age-group-id-2-low-value'); fireEvent.change(ageHighInput, { target: { value: '16' } }); fireEvent.change(ageLowInput, { target: { value: '17' } }); fireEvent.click(screen.getByText('Save')); const checkSubmitted = onSubmit.mock.calls[0][0] as ObservationDefinition; expect(checkSubmitted.qualifiedInterval?.map((interval) => interval.age?.low?.value)).toMatchObject([ 11, 11, 17, 17, 17, 20, 20, 20, 50, 50, 50, 11, 11, 20, 20, 20, 50, 50, 50, ]); expect(checkSubmitted.qualifiedInterval?.map((interval) => interval.age?.high?.value)).toMatchObject([ 16, 16, 19, 19, 19, 49, 49, 49, undefined, undefined, undefined, 19, 19, 49, 49, 49, undefined, undefined, undefined, ]); }); /** * Update one group's filters to match another group. Make sure no other groups are affected */ test('Overlapping Group Filters', async () => { const onSubmit = jest.fn(); await setup({ definition: deepClone(TestosteroneDefinition), onSubmit, }); const groups = screen.getAllByTestId(/^group-id-\d+/); expect(groups).toHaveLength(7); const ageLowInput = screen.getByTestId('age-group-id-2-low-value'); fireEvent.change(ageLowInput, { target: { value: '11' } }); fireEvent.click(screen.getByText('Save')); const checkSubmitted = onSubmit.mock.calls[0][0] as ObservationDefinition; expect(checkSubmitted.qualifiedInterval?.map((interval) => interval.gender)).toMatchObject([ ...Array(11).fill('male'), ...Array(8).fill('female'), ]); expect(checkSubmitted.qualifiedInterval?.map((interval) => interval.age?.low?.value)).toMatchObject([ ...Array(5).fill(11), 20, 20, 20, 50, 50, 50, 11, 11, 20, 20, 20, 50, 50, 50, ]); expect(checkSubmitted.qualifiedInterval?.map((interval) => interval.age?.high?.value)).toMatchObject([ 14, 14, 19, 19, 19, 49, 49, 49, undefined, undefined, undefined, 19, 19, 49, 49, 49, undefined, undefined, undefined, ]); }); });

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