Skip to main content
Glama
utils.test.ts46 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { TypedValue } from '@medplum/core'; import type { Encounter, Patient, QuestionnaireItem, QuestionnaireItemEnableWhen, Reference, ResourceType, } from '@medplum/fhirtypes'; import { buildInitialResponse, evaluateCalculatedExpressionsInQuestionnaire, getNewMultiSelectValues, getQuestionnaireItemReferenceFilter, getQuestionnaireItemReferenceTargetTypes, isChoiceQuestion, isQuestionEnabled, setQuestionnaireItemReferenceTargetTypes, typedValueToResponseItem, } from './utils'; describe('Questionnaire Utils', () => { test('isChoiceQuestion', () => { expect(isChoiceQuestion({ linkId: 'q3', type: 'string' })).toBe(false); expect(isChoiceQuestion({ linkId: 'q3', type: 'choice' })).toBe(true); expect(isChoiceQuestion({ linkId: 'q3', type: 'open-choice' })).toBe(true); }); }); describe('isQuestionEnabled', () => { test('default true', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [], } ) ).toBe(true); }); test('enableBehavior=any, match', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableBehavior: 'any', enableWhen: [ { question: 'q1', operator: '=', answerString: 'Yes', }, { question: 'q2', operator: '=', answerString: 'Yes', }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'No' }], }, { linkId: 'q2', answer: [{ valueString: 'Yes' }], }, ], } ) ).toBe(true); }); test('enableBehavior=any, no match', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableBehavior: 'any', enableWhen: [ { question: 'q1', answerString: 'Yes', operator: '=', }, { question: 'q2', answerString: 'Yes', operator: '=', }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'No' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(false); }); test('enableBehavior=all, match', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableBehavior: 'all', enableWhen: [ { question: 'q1', answerString: 'Yes', operator: '=', }, { question: 'q2', answerString: 'Yes', operator: '=', }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'Yes' }], }, { linkId: 'q2', answer: [{ valueString: 'Yes' }], }, ], } ) ).toBe(true); }); test('enableBehavior=all, no match', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableBehavior: 'all', enableWhen: [ { question: 'q1', answerString: 'Yes', operator: '=', }, { question: 'q2', answerString: 'Yes', operator: '=', }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'Yes' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(false); }); test('enableBehavior=any, no match with multiple answers', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableBehavior: 'any', enableWhen: [ { question: 'q1', answerString: 'Yes', operator: '=', }, { question: 'q2', answerString: 'Yes', operator: '=', }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'No' }, { valueString: 'Maybe' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }, { valueString: 'Maybe' }], }, ], } ) ).toBe(false); }); test('enableBehavior=all, no match with multiple answers', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableBehavior: 'all', enableWhen: [ { question: 'q1', answerString: 'Yes', operator: '=', }, { question: 'q2', answerString: 'Yes', operator: '=', }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'No' }, { valueString: 'Maybe' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }, { valueString: 'Maybe' }], }, ], } ) ).toBe(false); }); test('enableBehavior=any, one match with multiple answers', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableBehavior: 'any', enableWhen: [ { question: 'q1', answerString: 'Yes', operator: '=', }, { question: 'q2', answerString: 'Yes', operator: '=', }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'No' }, { valueString: 'Yes' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }, { valueString: 'Maybe' }], }, ], } ) ).toBe(true); }); test('enableBehavior=all, one non-match with multiple answers', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableBehavior: 'all', enableWhen: [ { question: 'q1', answerString: 'Yes', operator: '=', }, { question: 'q2', answerString: 'Yes', operator: '=', }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'Yes' }, { valueString: 'Yes' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }, { valueString: 'Maybe' }], }, ], } ) ).toBe(false); }); test('enableBehavior=all, all match with multiple answers', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableBehavior: 'all', enableWhen: [ { question: 'q1', answerString: 'Yes', operator: '=', }, { question: 'q2', answerString: 'Yes', operator: '=', }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'Yes' }, { valueString: 'Yes' }], }, { linkId: 'q2', answer: [{ valueString: 'Yes' }, { valueString: 'Yes' }], }, ], } ) ).toBe(true); }); test('enableBehavior=any, enableWhen `exists` operator, `answerBoolean` = true, answer present', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen: [ { question: 'q1', operator: 'exists', answerBoolean: true, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'Yes' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(true); }); test('enableBehavior=any, enableWhen `exists` operator, `answerBoolean` = false, answer present', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen: [ { question: 'q1', operator: 'exists', answerBoolean: false, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'Yes' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(false); }); test('enableBehavior=any, enableWhen `exists` operator, `answerBoolean` = true, answer missing', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen: [ { question: 'q1', operator: 'exists', answerBoolean: true, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(false); }); test('enableBehavior=any, enableWhen `exists` operator, `answerBoolean` = false, answer missing', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen: [ { question: 'q1', operator: 'exists', answerBoolean: false, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(true); }); test('enableBehavior=any, enableWhen `exists` operator, `answerBoolean` = false, answer missing', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen: [ { question: 'q1', operator: 'exists', answerBoolean: false, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(true); }); test('enableBehavior=any, enableWhen `exists` operator, `answerBoolean` = false, answer missing', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen: [ { question: 'q1', operator: 'exists', answerBoolean: false, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(true); }); test('enableBehavior=any, enableWhen `exists` operator, `answerBoolean` = true, multiple answers present', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen: [ { question: 'q1', operator: 'exists', answerBoolean: true, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'Yes' }, { valueString: 'No' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(true); }); test('enableBehavior=any, enableWhen `exists` operator, `answerBoolean` = false, multiple answers present', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen: [ { question: 'q1', operator: 'exists', answerBoolean: false, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'Yes' }, { valueString: 'No' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(false); }); test('enableBehavior=any, enableWhen `exists` operator, `answerBoolean` = true, one of the answers missing', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen: [ { question: 'q3', operator: 'exists', answerBoolean: true, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'Yes' }, { valueString: 'No' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(false); }); test('enableBehavior=any, enableWhen `exists` operator, `answerBoolean` = false, one of the answers missing', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen: [ { question: 'q3', operator: 'exists', answerBoolean: false, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'Yes' }, { valueString: 'No' }], }, { linkId: 'q2', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(true); }); test('enableBehavior=any, enableWhen `!=` operator', () => { const enableWhen = [ { question: 'q1', operator: '!=', answerString: 'Yes', }, ] satisfies QuestionnaireItemEnableWhen[]; expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'No' }], }, ], } ) ).toBe(true); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueString: 'Yes' }], }, ], } ) ).toBe(false); }); test('enableBehavior=any, enableWhen `>` operator', () => { const enableWhen = [ { question: 'q1', operator: '>', answerInteger: 3, }, ] satisfies QuestionnaireItemEnableWhen[]; expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueInteger: 4 }], }, ], } ) ).toBe(true); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueInteger: 2 }], }, ], } ) ).toBe(false); }); test('enableBehavior=any, enableWhen `>=` operator', () => { const enableWhen = [ { question: 'q1', operator: '>=', answerInteger: 3, }, ] satisfies QuestionnaireItemEnableWhen[]; expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueInteger: 4 }], }, ], } ) ).toBe(true); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueInteger: 3 }], }, ], } ) ).toBe(true); }); test('enableBehavior=any, enableWhen `<` operator', () => { const enableWhen = [ { question: 'q1', operator: '<', answerInteger: 3, }, ] satisfies QuestionnaireItemEnableWhen[]; expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueInteger: 2 }], }, ], } ) ).toBe(true); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueInteger: 3 }], }, ], } ) ).toBe(false); }); test('enableBehavior=any, enableWhen `<=` operator', () => { const enableWhen = [ { question: 'q1', operator: '<=', answerInteger: 3, }, ] satisfies QuestionnaireItemEnableWhen[]; expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueInteger: 2 }], }, ], } ) ).toBe(true); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueInteger: 3 }], }, ], } ) ).toBe(true); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueInteger: 4 }], }, ], } ) ).toBe(false); }); test('enableBehavior=any, enableWhen `=` operator for `valueCoding`', () => { const enableWhen = [ { question: 'q1', operator: '=', answerCoding: { code: 'MEDPLUM123' } }, ] satisfies QuestionnaireItemEnableWhen[]; expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueCoding: { code: 'MEDPLUM123' } }], }, ], } ) ).toBe(true); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueCoding: { code: 'MEDPLUM123', display: 'Medplum123' } }], }, ], } ) ).toBe(true); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueCoding: { code: 'NOT_MEDPLUM123', display: 'Medplum123' } }], }, ], } ) ).toBe(false); }); test('enableBehavior=any, enableWhen `!=` operator for `valueCoding`', () => { const enableWhen = [ { question: 'q1', operator: '!=', answerCoding: { code: 'MEDPLUM123' } }, ] satisfies QuestionnaireItemEnableWhen[]; expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueCoding: { code: 'NOT_MEDPLUM123' } }], }, ], } ) ).toBe(true); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueCoding: { code: 'NOT_MEDPLUM123', display: 'Medplum123' } }], }, ], } ) ).toBe(true); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueCoding: { code: 'MEDPLUM123', display: 'Medplum123' } }], }, ], } ) ).toBe(false); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', enableWhen, }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueCoding: { code: 'MEDPLUM123' } }], }, ], } ) ).toBe(false); }); test('expression evaluation', () => { expect( isQuestionEnabled( { linkId: 'q3', type: 'string', extension: [ { url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression', valueExpression: { language: 'text/fhirpath', expression: "%resource.item.where(linkId = 'q1').answer.valueCoding.code = 'MEDPLUM123' and %resource.item.where(linkId = 'q2').answer.valueString = 'Female'", }, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueCoding: { code: 'MEDPLUM123' } }], }, { linkId: 'q2', answer: [{ valueString: 'Female' }], }, ], } ) ).toBe(true); expect( isQuestionEnabled( { linkId: 'q3', type: 'string', extension: [ { url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression', valueExpression: { language: 'text/fhirpath', expression: "%resource.item.where(linkId = 'q1').answer.valueCoding.code = 'MEDPLUM123' and %resource.item.where(linkId = 'q2').answer.valueString = 'Female'", }, }, ], }, { resourceType: 'QuestionnaireResponse', status: 'completed', item: [ { linkId: 'q1', answer: [{ valueCoding: { code: 'DIFFERENT_CODE' } }], }, { linkId: 'q2', answer: [{ valueString: 'Female' }], }, ], } ) ).toBe(false); }); }); describe('getNewMultiSelectValues', () => { test('multi-select map selected values', () => { const selected = ['value1', 'value2']; const propertyName = 'valueString'; const item: QuestionnaireItem = { linkId: 'q3', type: 'string', answerOption: [ { valueString: 'value1', }, { valueString: 'value2', }, ], }; const result = getNewMultiSelectValues(selected, propertyName, item); expect(result).toStrictEqual([{ valueString: 'value1' }, { valueString: 'value2' }]); }); test('multi-select non selected values', () => { const selected = ['nonMatchingValue']; const propertyName = 'valueString'; const item: QuestionnaireItem = { linkId: 'q3', type: 'string', answerOption: [ { valueString: 'value1', }, { valueString: 'value2', }, ], }; const result = getNewMultiSelectValues(selected, propertyName, item); expect(result).toStrictEqual([]); }); test('multi-select empty array', () => { const selected: string[] = []; const propertyName = 'valueString'; const item: QuestionnaireItem = { linkId: 'q3', type: 'string', answerOption: [{ valueString: 'value1' }, { valueString: 'value2' }], }; const result = getNewMultiSelectValues(selected, propertyName, item); expect(result).toStrictEqual([]); }); test('multi-select with value coding', () => { const selected = ['code1']; const propertyName = 'valueCoding'; const item: QuestionnaireItem = { linkId: 'q3', type: 'string', answerOption: [ { valueCoding: { code: 'code1' }, }, { valueCoding: { code: 'code2' }, }, ], }; const result = getNewMultiSelectValues(selected, propertyName, item); expect(result).toStrictEqual([{ valueCoding: { code: 'code1' } }]); }); }); describe('typedValueToResponseItem', () => { test('returns undefined for missing type', () => { const item: QuestionnaireItem = { linkId: '1', type: 'integer' }; const value = { value: 'text' }; const result = typedValueToResponseItem(item, value as unknown as TypedValue); expect(result).toBeUndefined(); }); test('returns correct value for type boolean', () => { const item: QuestionnaireItem = { linkId: '1', type: 'boolean' }; const value = { type: 'boolean', value: true }; const result = typedValueToResponseItem(item, value); expect(result).toEqual({ valueBoolean: true }); }); test('returns undefined for mismatched boolean type', () => { const item: QuestionnaireItem = { linkId: '1', type: 'boolean' }; const value = { type: 'string', value: 'text' }; const result = typedValueToResponseItem(item, value); expect(result).toBeUndefined(); }); test('returns correct value for type date', () => { const item: QuestionnaireItem = { linkId: '1', type: 'date' }; const value = { type: 'date', value: '2024-01-01' }; const result = typedValueToResponseItem(item, value); expect(result).toEqual({ valueDate: '2024-01-01' }); }); test('returns correct value for type dateTime', () => { const item: QuestionnaireItem = { linkId: '1', type: 'dateTime' }; const value = { type: 'dateTime', value: '2024-01-01T12:00:00Z' }; const result = typedValueToResponseItem(item, value); expect(result).toEqual({ valueDateTime: '2024-01-01T12:00:00Z' }); }); test('returns correct value for type time', () => { const item: QuestionnaireItem = { linkId: '1', type: 'time' }; const value = { type: 'time', value: '12:00:00' }; const result = typedValueToResponseItem(item, value); expect(result).toEqual({ valueTime: '12:00:00' }); }); test('returns correct value for type url', () => { const item: QuestionnaireItem = { linkId: '1', type: 'url' }; const value = { type: 'url', value: 'http://example.com' }; const result = typedValueToResponseItem(item, value); expect(result).toEqual({ valueUrl: 'http://example.com' }); }); test('returns correct value for type text', () => { const item: QuestionnaireItem = { linkId: '1', type: 'text' }; const value = { type: 'string', value: 'Sample text' }; const result = typedValueToResponseItem(item, value); expect(result).toEqual({ valueString: 'Sample text' }); }); test('returns correct value for type attachment', () => { const item: QuestionnaireItem = { linkId: '1', type: 'attachment' }; const value = { type: 'Attachment', value: { file: 'file.pdf' } }; const result = typedValueToResponseItem(item, value); expect(result).toEqual({ valueAttachment: { file: 'file.pdf' } }); }); test('returns correct value for type reference', () => { const item: QuestionnaireItem = { linkId: '1', type: 'reference' }; const value = { type: 'Reference', value: { ref: '123' } }; const result = typedValueToResponseItem(item, value); expect(result).toEqual({ valueReference: { ref: '123' } }); }); test('returns correct value for type quantity', () => { const item: QuestionnaireItem = { linkId: '1', type: 'quantity' }; const value = { type: 'Quantity', value: { value: 10, unit: 'mg' } }; const result = typedValueToResponseItem(item, value); expect(result).toEqual({ valueQuantity: { value: 10, unit: 'mg' } }); }); test('returns undefined for unsupported type', () => { const item: QuestionnaireItem = { linkId: '1', type: 'unsupported' as any }; const value = { type: 'string', value: 'text' }; const result = typedValueToResponseItem(item, value); expect(result).toBeUndefined(); }); test('returns any type for choice question', () => { const item: QuestionnaireItem = { linkId: '1', type: 'choice' }; const value = { type: 'string', value: 'text' }; const result = typedValueToResponseItem(item, value); expect(result).toEqual({ valueString: 'text' }); }); test('returns any type for open-choice question', () => { const item: QuestionnaireItem = { linkId: '1', type: 'open-choice' }; const value = { type: 'string', value: 'text' }; const result = typedValueToResponseItem(item, value); expect(result).toEqual({ valueString: 'text' }); }); test('returns undefined for unsupported string type', () => { const item: QuestionnaireItem = { linkId: '1', type: 'string' }; const value = { type: 'integer', value: 10 }; const result = typedValueToResponseItem(item, value); expect(result).toBeUndefined(); }); }); describe('evaluateCalculatedExpressionsInQuestionnaire', () => { test('Boolean type with condition', () => { const items: QuestionnaireItem[] = [ { id: 'q1', linkId: 'q1', type: 'boolean', text: 'Is Age Over 18?', extension: [ { url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression', valueExpression: { expression: '20 > 18', language: 'text/fhirpath', }, }, ], }, ]; const response = buildInitialResponse({ resourceType: 'Questionnaire', status: 'active', item: items }); evaluateCalculatedExpressionsInQuestionnaire(items, response); expect(response.item).toMatchObject([ { linkId: 'q1', text: 'Is Age Over 18?', answer: [{ valueBoolean: true }], }, ]); }); test('Date type with today() function', () => { const items: QuestionnaireItem[] = [ { id: 'q2', linkId: 'q2', type: 'date', text: "Today's Date", extension: [ { url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression', valueExpression: { expression: 'today()', language: 'text/fhirpath', }, }, ], }, ]; const response = buildInitialResponse({ resourceType: 'Questionnaire', status: 'active', item: items }); evaluateCalculatedExpressionsInQuestionnaire(items, response); const today = new Date().toISOString().split('T')[0]; // Get today's date in YYYY-MM-DD format expect(response.item).toMatchObject([ { linkId: 'q2', text: "Today's Date", answer: [{ valueDate: today }], }, ]); }); test('Integer type with addition', () => { const items: QuestionnaireItem[] = [ { id: 'q3', linkId: 'q3', type: 'integer', text: 'Age Next Year', extension: [ { url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression', valueExpression: { expression: '30 + 1', language: 'text/fhirpath', }, }, ], }, ]; const response = buildInitialResponse({ resourceType: 'Questionnaire', status: 'active', item: items }); evaluateCalculatedExpressionsInQuestionnaire(items, response); expect(response.item).toMatchObject([ { linkId: 'q3', text: 'Age Next Year', answer: [{ valueInteger: 31 }], }, ]); }); test('Decimal type with division', () => { const items: QuestionnaireItem[] = [ { id: 'q4', linkId: 'q4', type: 'decimal', text: 'Half of 98', extension: [ { url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression', valueExpression: { expression: '98 / 2', language: 'text/fhirpath', }, }, ], }, ]; const response = buildInitialResponse({ resourceType: 'Questionnaire', status: 'active', item: items }); evaluateCalculatedExpressionsInQuestionnaire(items, response); expect(response.item).toMatchObject([ { linkId: 'q4', text: 'Half of 98', answer: [{ valueDecimal: 49.0 }], }, ]); }); test('String type with concatenation', () => { const items: QuestionnaireItem[] = [ { id: 'q5', linkId: 'q5', type: 'string', text: 'Full Name', extension: [ { url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression', valueExpression: { expression: "'John' + ' ' + 'Doe'", language: 'text/fhirpath', }, }, ], }, ]; const response = buildInitialResponse({ resourceType: 'Questionnaire', status: 'active', item: items }); evaluateCalculatedExpressionsInQuestionnaire(items, response); expect(response.item).toMatchObject([ { linkId: 'q5', text: 'Full Name', answer: [{ valueString: 'John Doe' }], }, ]); }); test('Failed to evaluate expression', () => { const items: QuestionnaireItem[] = [ { id: 'q5', linkId: 'q5', type: 'string', text: 'Full Name', extension: [ { url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression', valueExpression: { expression: '%fail', language: 'text/fhirpath', }, }, ], }, ]; const response = buildInitialResponse({ resourceType: 'Questionnaire', status: 'active', item: items }); evaluateCalculatedExpressionsInQuestionnaire(items, response); expect(response.item).toMatchObject([ { linkId: 'q5', text: 'Full Name', extension: [ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-validationError', valueString: 'Expression evaluation failed: FhirPathError on "%fail": Error: Undefined variable %fail', }, ], }, ]); }); test('Nested expressions', () => { const items: QuestionnaireItem[] = [ { linkId: 'group', type: 'group', item: [ { id: 'q3', linkId: 'q3', type: 'integer', text: 'Age Next Year', extension: [ { url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression', valueExpression: { expression: '30 + 1', language: 'text/fhirpath', }, }, ], }, ], }, ]; const response = buildInitialResponse({ resourceType: 'Questionnaire', status: 'active', item: items }); evaluateCalculatedExpressionsInQuestionnaire(items, response); expect(response.item).toMatchObject([ { linkId: 'group', item: [ { linkId: 'q3', text: 'Age Next Year', answer: [{ valueInteger: 31 }], }, ], }, ]); }); }); describe('getQuestionnaireItemReferenceTargetTypes', () => { test('No extension', () => { const item: QuestionnaireItem = { linkId: 'q1', type: 'reference' }; const result = getQuestionnaireItemReferenceTargetTypes(item); expect(result).toEqual(undefined); }); test('Extension with valueCode', () => { const item: QuestionnaireItem = { linkId: 'q1', type: 'reference', extension: [ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', valueCode: 'Patient', }, ], }; const result = getQuestionnaireItemReferenceTargetTypes(item); expect(result).toEqual(['Patient']); }); test('Extension with valueCodeableConcept', () => { const item: QuestionnaireItem = { linkId: 'q1', type: 'reference', extension: [ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', valueCodeableConcept: { coding: [{ code: 'Observation' }], }, }, ], }; const result = getQuestionnaireItemReferenceTargetTypes(item); expect(result).toEqual(['Observation']); }); test('Extension with unsupported value type', () => { const item: QuestionnaireItem = { linkId: 'q1', type: 'reference', extension: [ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', valueString: 'UnsupportedType', }, ], }; const result = getQuestionnaireItemReferenceTargetTypes(item); expect(result).toEqual(undefined); }); }); describe('setQuestionnaireItemReferenceTargetTypes', () => { test('No existing extension', () => { const item: QuestionnaireItem = { linkId: 'q1', type: 'reference' }; const targetTypes: ResourceType[] = ['Patient', 'Observation']; const result = setQuestionnaireItemReferenceTargetTypes(item, targetTypes); expect(result.extension).toEqual([ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', valueCodeableConcept: { coding: [{ code: 'Patient' }, { code: 'Observation' }], }, }, ]); }); test('Remove existing extension', () => { const item: QuestionnaireItem = { linkId: 'q1', type: 'reference', extension: [ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', valueCode: 'Patient', }, ], }; const result = setQuestionnaireItemReferenceTargetTypes(item, []); expect(result.extension).toEqual([]); }); test('Change code to CodeableConcept', () => { const item: QuestionnaireItem = { linkId: 'q1', type: 'reference', extension: [ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', valueCode: 'Patient', }, ], }; const targetTypes: ResourceType[] = ['Patient', 'Observation']; const result = setQuestionnaireItemReferenceTargetTypes(item, targetTypes); expect(result.extension).toEqual([ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', valueCodeableConcept: { coding: [{ code: 'Patient' }, { code: 'Observation' }], }, }, ]); }); test('Change CodeableConcept to code', () => { const item: QuestionnaireItem = { linkId: 'q1', type: 'reference', extension: [ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', valueCodeableConcept: { coding: [{ code: 'Patient' }, { code: 'Observation' }], }, }, ], }; const targetTypes: ResourceType[] = ['Patient']; const result = setQuestionnaireItemReferenceTargetTypes(item, targetTypes); expect(result.extension).toEqual([ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource', valueCode: 'Patient', }, ]); }); }); describe('getQuestionnaireItemReferenceFilter', () => { const subject: Reference<Patient> = { reference: 'Patient/123' }; const encounter: Reference<Encounter> = { reference: 'Encounter/456' }; test('No extension', () => { const item: QuestionnaireItem = { linkId: 'q1', type: 'reference' }; const result = getQuestionnaireItemReferenceFilter(item, subject, encounter); expect(result).toEqual(undefined); }); test('Extension with valueString', () => { const item: QuestionnaireItem = { linkId: 'q1', type: 'reference', extension: [ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceFilter', valueString: 'subject=$subj&encounter=$encounter&active=true', }, ], }; const result = getQuestionnaireItemReferenceFilter(item, subject, encounter); expect(result).toEqual({ subject: 'Patient/123', encounter: 'Encounter/456', active: 'true', }); }); test('Extension with unsupported value type', () => { const item: QuestionnaireItem = { linkId: 'q1', type: 'reference', extension: [ { url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-referenceFilter', valueInteger: 42, }, ], }; const result = getQuestionnaireItemReferenceFilter(item, subject, encounter); expect(result).toEqual(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