From c7c4358fe5ed34b289f2a738c69b9334eb3ccd8b Mon Sep 17 00:00:00 2001 From: qscgyjqscgyj Date: Tue, 3 Dec 2024 14:20:26 +0100 Subject: [PATCH] Added full enableWhen yup schema support Added tests for each enableWhen operator cases --- src/utils/__tests__/enableWhen/equal.test.ts | 152 ++++++++++++++++++ .../enableWhen/equalOperator.test.ts | 120 -------------- src/utils/__tests__/enableWhen/exists.test.ts | 152 ++++++++++++++++++ src/utils/__tests__/enableWhen/gt.test.ts | 152 ++++++++++++++++++ src/utils/__tests__/enableWhen/gte.test.ts | 152 ++++++++++++++++++ src/utils/__tests__/enableWhen/lt.test.ts | 152 ++++++++++++++++++ src/utils/__tests__/enableWhen/lte.test.ts | 152 ++++++++++++++++++ .../__tests__/enableWhen/notEqual.test.ts | 152 ++++++++++++++++++ src/utils/__tests__/enableWhen/utils.ts | 67 +++++--- src/utils/enableWhen.ts | 119 +++++++++++--- src/utils/questionnaire.ts | 20 +-- 11 files changed, 1211 insertions(+), 179 deletions(-) create mode 100644 src/utils/__tests__/enableWhen/equal.test.ts delete mode 100644 src/utils/__tests__/enableWhen/equalOperator.test.ts create mode 100644 src/utils/__tests__/enableWhen/exists.test.ts create mode 100644 src/utils/__tests__/enableWhen/gt.test.ts create mode 100644 src/utils/__tests__/enableWhen/gte.test.ts create mode 100644 src/utils/__tests__/enableWhen/lt.test.ts create mode 100644 src/utils/__tests__/enableWhen/lte.test.ts create mode 100644 src/utils/__tests__/enableWhen/notEqual.test.ts diff --git a/src/utils/__tests__/enableWhen/equal.test.ts b/src/utils/__tests__/enableWhen/equal.test.ts new file mode 100644 index 00000000..84b9e118 --- /dev/null +++ b/src/utils/__tests__/enableWhen/equal.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_EQUAL_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '=', + answer: { integer: 1 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '=', + answer: { string: 'test' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '=', + answer: { string: 'test1' }, + }, + { + question: 'q2', + operator: '=', + answer: { string: 'test2' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test1' } }], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '=', + answer: { string: 'test1' }, + }, + { + question: 'q2', + operator: '=', + answer: { string: 'test2' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'asd' } }], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '=', + answer: { Coding: { code: 'test1', display: 'test1' } }, + }, + { + question: 'q2', + operator: '=', + answer: { Coding: { code: 'test2', display: 'test2' } }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { Coding: { code: 'asd', display: 'asd' } } }], + }, + { + linkId: 'q2', + answer: [{ value: { Coding: { code: 'test2', display: 'test2' } } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: "="', () => { + test.each(ENABLE_WHEN_EQUAL_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/equalOperator.test.ts b/src/utils/__tests__/enableWhen/equalOperator.test.ts deleted file mode 100644 index adc63008..00000000 --- a/src/utils/__tests__/enableWhen/equalOperator.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { isItemEnabled } from 'src/utils/enableWhen'; - -import { generateQAndQRData, QuestionnaireData } from './utils'; - -const ENABLE_WHEN_EQUAL_QUESTIONAIRES: QuestionnaireData[] = [ - { - ...generateQAndQRData({ - type: 'integer', - operator: '=', - conditionValue: { integer: 1 }, - answerValue: { integer: 1 }, - }), - enabled: true, - }, - { - ...generateQAndQRData({ - type: 'integer', - operator: '=', - conditionValue: { integer: 1 }, - answerValue: { integer: 2 }, - }), - enabled: false, - }, - { - ...generateQAndQRData({ - type: 'string', - operator: '=', - conditionValue: { string: 'test' }, - answerValue: { string: 'test' }, - }), - enabled: true, - }, - { - ...generateQAndQRData({ - type: 'string', - operator: '=', - conditionValue: { string: 'test' }, - answerValue: { string: 'test1' }, - }), - enabled: false, - }, - { - ...generateQAndQRData({ - type: 'choice', - operator: '=', - conditionValue: { - Coding: { - system: 'http://loinc.org', - code: '12345', - display: 'Test', - }, - }, - answerValue: { - Coding: { - system: 'http://loinc.org', - code: '12345', - display: 'Test', - }, - }, - }), - enabled: true, - }, - { - ...generateQAndQRData({ - type: 'choice', - operator: '=', - conditionValue: { - Coding: { - system: 'http://loinc.org', - code: '12345', - display: 'Test', - }, - }, - answerValue: { - Coding: { - system: 'http://loinc123123.org', - code: '12345', - display: 'Test', - }, - }, - }), - enabled: false, - }, - { - ...generateQAndQRData({ - type: 'boolean', - operator: '=', - conditionValue: { boolean: true }, - answerValue: { boolean: true }, - }), - enabled: true, - }, - { - ...generateQAndQRData({ - type: 'boolean', - operator: '=', - conditionValue: { boolean: true }, - answerValue: { boolean: false }, - }), - enabled: false, - }, -]; - -describe('Enable when: "="', () => { - test.each(ENABLE_WHEN_EQUAL_QUESTIONAIRES)( - 'should check if enabledLinkId is enabled or not', - async (questionnaireData) => { - const answerOptionArray = questionnaireData!.questionnaireResponse!.item![0]?.answer; - const conditionValue = questionnaireData!.questionnaire!.item![1]?.enableWhen![0]?.answer; - - const enabledResult = isItemEnabled({ - answerOptionArray, - answer: conditionValue, - operator: '=', - }); - - expect(enabledResult).toBe(questionnaireData.enabled); - }, - ); -}); diff --git a/src/utils/__tests__/enableWhen/exists.test.ts b/src/utils/__tests__/enableWhen/exists.test.ts new file mode 100644 index 00000000..c2940bcc --- /dev/null +++ b/src/utils/__tests__/enableWhen/exists.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_EXISTS_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: 'exists', + answer: { boolean: true }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test1' } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: 'exists', + answer: { boolean: false }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: 'exists', + answer: { boolean: true }, + }, + { + question: 'q2', + operator: 'exists', + answer: { boolean: true }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test1' } }], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: 'exists', + answer: { boolean: true }, + }, + { + question: 'q2', + operator: 'exists', + answer: { boolean: true }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: 'exists', + answer: { boolean: true }, + }, + { + question: 'q2', + operator: 'exists', + answer: { boolean: false }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { Coding: { code: 'asd', display: 'asd' } } }], + }, + { + linkId: 'q2', + answer: [{ value: { Coding: { code: 'test2', display: 'test2' } } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: "exists"', () => { + test.each(ENABLE_WHEN_EXISTS_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/gt.test.ts b/src/utils/__tests__/enableWhen/gt.test.ts new file mode 100644 index 00000000..8df46d3c --- /dev/null +++ b/src/utils/__tests__/enableWhen/gt.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_GT_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 11 } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 15 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 6 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 11 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '>', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 6 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: ">"', () => { + test.each(ENABLE_WHEN_GT_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/gte.test.ts b/src/utils/__tests__/enableWhen/gte.test.ts new file mode 100644 index 00000000..8ff81bdc --- /dev/null +++ b/src/utils/__tests__/enableWhen/gte.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_GTE_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>=', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>=', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 9 } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '>=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 9 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '>=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '>=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: ">="', () => { + test.each(ENABLE_WHEN_GTE_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/lt.test.ts b/src/utils/__tests__/enableWhen/lt.test.ts new file mode 100644 index 00000000..aaaad297 --- /dev/null +++ b/src/utils/__tests__/enableWhen/lt.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_LT_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 4 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 4 } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '<', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 4 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: "<"', () => { + test.each(ENABLE_WHEN_LT_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/lte.test.ts b/src/utils/__tests__/enableWhen/lte.test.ts new file mode 100644 index 00000000..eddd54e8 --- /dev/null +++ b/src/utils/__tests__/enableWhen/lte.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_LTE_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<=', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<=', + answer: { integer: 10 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 11 } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 10 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 0 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '<=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 11 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '<=', + answer: { integer: 10 }, + }, + { + question: 'q2', + operator: '<=', + answer: { integer: 5 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: 'q2', + answer: [{ value: { integer: 5 } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: "<="', () => { + test.each(ENABLE_WHEN_LTE_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/notEqual.test.ts b/src/utils/__tests__/enableWhen/notEqual.test.ts new file mode 100644 index 00000000..b9feffe1 --- /dev/null +++ b/src/utils/__tests__/enableWhen/notEqual.test.ts @@ -0,0 +1,152 @@ +import { + CONTROL_ITEM_LINK_ID, + generateQAndQRData, + QuestionnaireData, + testEnableWhenCases, + ENABLE_WHEN_TESTS_TITLE, +} from './utils'; + +const ENABLE_WHEN_NOT_EQUAL_QUESTIONAIRES: QuestionnaireData[] = [ + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '!=', + answer: { integer: 1 }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { integer: 1 } }], + }, + { + linkId: 'q2', + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '!=', + answer: { string: 'test' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: 'q2', + answer: [], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '!=', + answer: { string: 'test1' }, + }, + { + question: 'q2', + operator: '!=', + answer: { string: 'test2' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'test1' } }], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableWhen: [ + { + question: 'q1', + operator: '!=', + answer: { string: 'test1' }, + }, + { + question: 'q2', + operator: '!=', + answer: { string: 'test2' }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { string: 'asd' } }], + }, + { + linkId: 'q2', + answer: [{ value: { string: 'test2' } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, + { + ...generateQAndQRData({ + type: 'integer', + enableBehavior: 'any', + enableWhen: [ + { + question: 'q1', + operator: '!=', + answer: { Coding: { code: 'test1', display: 'test1' } }, + }, + { + question: 'q2', + operator: '!=', + answer: { Coding: { code: 'test2', display: 'test2' } }, + }, + ], + qrItem: [ + { + linkId: 'q1', + answer: [{ value: { Coding: { code: 'asd', display: 'asd' } } }], + }, + { + linkId: 'q2', + answer: [{ value: { Coding: { code: 'test2', display: 'test2' } } }], + }, + { + linkId: CONTROL_ITEM_LINK_ID, + answer: [], + }, + ], + }), + }, +]; + +describe('Enable when: "!="', () => { + test.each(ENABLE_WHEN_NOT_EQUAL_QUESTIONAIRES)(ENABLE_WHEN_TESTS_TITLE, testEnableWhenCases); +}); diff --git a/src/utils/__tests__/enableWhen/utils.ts b/src/utils/__tests__/enableWhen/utils.ts index e221e801..238eecae 100644 --- a/src/utils/__tests__/enableWhen/utils.ts +++ b/src/utils/__tests__/enableWhen/utils.ts @@ -1,32 +1,30 @@ import { Questionnaire, QuestionnaireItem, - QuestionnaireItemEnableWhenAnswer, + QuestionnaireItemEnableWhen, QuestionnaireResponse, + QuestionnaireResponseItem, } from '@beda.software/aidbox-types'; -import { EnableWhenOperator } from 'src/utils/enableWhen'; +import { evaluate, questionnaireItemsToValidationSchema } from 'src/utils'; export type QuestionnaireData = { questionnaire: Questionnaire; questionnaireResponse: QuestionnaireResponse; - enabled: boolean; }; -export const ITEM_TO_CKECK_LINK_ID = 'item-to-check'; export const CONTROL_ITEM_LINK_ID = 'control-item'; interface GenerateQAndQRDataProps { type: QuestionnaireItem['type']; - operator: EnableWhenOperator; - conditionValue: QuestionnaireItemEnableWhenAnswer; - answerValue: QuestionnaireItemEnableWhenAnswer; + enableWhen: QuestionnaireItemEnableWhen[]; + enableBehavior?: QuestionnaireItem['enableBehavior']; + qrItem: QuestionnaireResponseItem[]; } - export function generateQAndQRData( props: GenerateQAndQRDataProps, ): Pick { - const { type, operator, conditionValue, answerValue } = props; + const { type, enableWhen, enableBehavior, qrItem } = props; return { questionnaire: { @@ -36,9 +34,15 @@ export function generateQAndQRData( status: 'active', item: [ { - linkId: ITEM_TO_CKECK_LINK_ID, + linkId: 'q1', + type: type, + text: 'Item to check 1', + required: false, + }, + { + linkId: 'q2', type: type, - text: 'Item to check', + text: 'Item to check 2', required: false, }, { @@ -46,25 +50,40 @@ export function generateQAndQRData( type: type, text: 'Control item', required: true, - enableWhen: [ - { - question: ITEM_TO_CKECK_LINK_ID, - operator, - answer: conditionValue, - }, - ], + enableWhen, + enableBehavior, }, ], }, questionnaireResponse: { resourceType: 'QuestionnaireResponse', status: 'completed', - item: [ - { - linkId: ITEM_TO_CKECK_LINK_ID, - answer: [{ value: answerValue }], - }, - ], + item: qrItem, }, }; } + +export const ENABLE_WHEN_TESTS_TITLE = 'Should check if CONTROL_ITEM_LINK_ID is required or not'; + +export async function testEnableWhenCases(questionnaireData: QuestionnaireData) { + const { questionnaire, questionnaireResponse } = questionnaireData; + + const qrValues: QuestionnaireResponseItem[] = evaluate(questionnaireResponse, `item`); + const values = qrValues.reduce( + (acc, item) => { + acc[item.linkId] = item.answer; + return acc; + }, + {} as Record, + ); + const schema = questionnaireItemsToValidationSchema(questionnaire.item!); + + // NOTE: A way to debug a schema errors + // try { + // schema.validateSync(values); + // } catch (e) { + // console.log('Test schema valiadtion errors:', e); + // } + + expect(schema.isValidSync(values)).toBeTruthy(); +} diff --git a/src/utils/enableWhen.ts b/src/utils/enableWhen.ts index eed6be40..ceea58c3 100644 --- a/src/utils/enableWhen.ts +++ b/src/utils/enableWhen.ts @@ -1,11 +1,15 @@ import _ from 'lodash'; -import type { QuestionnaireItemEnableWhenAnswer, QuestionnaireItemAnswerOption } from 'shared/src/contrib/aidbox'; +import type { + QuestionnaireItemEnableWhenAnswer, + QuestionnaireItemAnswerOption, + QuestionnaireItemEnableWhen, +} from 'shared/src/contrib/aidbox'; +import * as yup from 'yup'; type EnableWhenAnswerTypes = 'Coding' | 'string' | 'integer' | 'boolean'; export type EnableWhenOperator = 'exists' | '=' | '!=' | '>' | '<' | '>=' | '<='; -type EnableWhenAnswerTypesMap = Record; - export type EnableWhenValueType = string | number | boolean; +type EnableWhenAnswerTypesMap = Record; const VALUES_TYPES: (keyof EnableWhenAnswerTypesMap)[] = ['Coding', 'string', 'integer', 'boolean']; const VALUES_TYPES_PATH_MAP: EnableWhenAnswerTypesMap = { @@ -19,47 +23,120 @@ const ENABLE_WHEN_OPERATORS_MAP: Record< EnableWhenOperator, (a: EnableWhenValueType, b: EnableWhenValueType) => boolean > = { - exists: (a, b) => !!a === b, + exists: (a, b) => !!a === !!b, '=': (a, b) => _.isEqual(a, b), '!=': (a, b) => !_.isEqual(a, b), - '>': (a, b) => a > b, - '<': (a, b) => a < b, - '>=': (a, b) => a >= b, - '<=': (a, b) => a <= b, + '>': (a, b) => b > a, + '<': (a, b) => b < a, + '>=': (a, b) => b >= a, + '<=': (a, b) => b <= a, }; function isOperatorValid(operator: string): operator is EnableWhenOperator { return Object.keys(ENABLE_WHEN_OPERATORS_MAP).includes(operator); } -interface isItemEnabledProps { +function getValueBYType(value: (QuestionnaireItemAnswerOption | undefined) | QuestionnaireItemEnableWhenAnswer) { + if (!value) { + return null; + } + + for (const valueTypePathKey of VALUES_TYPES) { + const path = VALUES_TYPES_PATH_MAP[valueTypePathKey].path; + + const valueResult = _.get(value, path) as EnableWhenAnswerTypes; + if (valueResult) { + return valueResult; + } + } + + return null; +} + +interface IsEnableWhenItemSucceedProps { answerOptionArray: QuestionnaireItemAnswerOption[] | undefined; answer: QuestionnaireItemEnableWhenAnswer | undefined; operator: string; } -export function isItemEnabled(props: isItemEnabledProps): boolean { +function isEnableWhenItemSucceed(props: IsEnableWhenItemSucceedProps): boolean { const { answerOptionArray, answer, operator } = props; if (!isOperatorValid(operator)) { return false; } - if (!answerOptionArray || !answer) { + if (!answerOptionArray || answerOptionArray.length === 0 || !answer) { return false; } - for (const valueTypePathKey of VALUES_TYPES) { - const path = VALUES_TYPES_PATH_MAP[valueTypePathKey].path; + const value = getValueBYType(answer); - const value = _.get(answer, path); + if (!value) { + return false; + } - if (value) { - return answerOptionArray.some((answerOption) => { - const answerOptionValue = _.get(answerOption.value, path); - return ENABLE_WHEN_OPERATORS_MAP[operator](value, answerOptionValue); - }); + return answerOptionArray.some((answerOption) => { + const answerOptionValue = getValueBYType(answerOption.value); + + if (answerOptionValue) { + return ENABLE_WHEN_OPERATORS_MAP[operator](value, answerOptionValue); } - } - return false; + return false; + }); +} + +interface GetEnableWhenItemSchemaProps extends GetQuestionItemEnableWhenSchemaProps { + currentIndex: number; + prevConditionResults?: boolean[]; +} +function getEnableWhenItemsSchema(props: GetEnableWhenItemSchemaProps) { + const { enableWhenItems, enableBehavior, currentIndex, schema, prevConditionResults } = props; + + const { question, operator, answer } = enableWhenItems[currentIndex]!; + + const isLastItem = currentIndex === enableWhenItems.length - 1; + + const conditionResults = prevConditionResults ? [...prevConditionResults] : []; + return yup.mixed().when(question, { + is: (answerOptionArray: QuestionnaireItemAnswerOption[]) => { + const isConditionSatisfied = isEnableWhenItemSucceed({ + answerOptionArray, + answer, + operator, + }); + + if (!enableBehavior || enableBehavior === 'all') { + return isConditionSatisfied; + } + + conditionResults.push(isConditionSatisfied); + + if (isLastItem) { + return conditionResults.some((result) => result); + } + + return true; + }, + then: () => + !isLastItem + ? getEnableWhenItemsSchema({ + enableWhenItems, + currentIndex: currentIndex + 1, + schema, + enableBehavior, + prevConditionResults: [...conditionResults], + }) + : schema, + otherwise: () => yup.mixed().nullable(), + }); +} + +interface GetQuestionItemEnableWhenSchemaProps { + enableWhenItems: QuestionnaireItemEnableWhen[]; + enableBehavior: string | undefined; + schema: yup.AnySchema; +} +export function getQuestionItemEnableWhenSchema(props: GetQuestionItemEnableWhenSchemaProps) { + return getEnableWhenItemsSchema({ ...props, currentIndex: 0 }); } diff --git a/src/utils/questionnaire.ts b/src/utils/questionnaire.ts index 884a0f3d..b3536a33 100644 --- a/src/utils/questionnaire.ts +++ b/src/utils/questionnaire.ts @@ -13,7 +13,7 @@ import { import { parseFHIRTime } from '@beda.software/fhir-react'; import { formatHumanDate, formatHumanDateTime } from './date'; -import { EnableWhenValueType, isItemEnabled } from './enableWhen'; +import { getQuestionItemEnableWhenSchema } from './enableWhen'; import { evaluate } from './fhirpath'; export function getDisplay( @@ -113,20 +113,12 @@ export function questionnaireItemsToValidationSchema(questionnaireItems: Questio } else { schema = item.required ? yup.mixed().required() : yup.mixed().nullable(); } + if (item.enableWhen) { - item.enableWhen.forEach((itemEnableWhen) => { - const { question, operator, answer } = itemEnableWhen; - // TODO: handle all other operators - validationSchema[item.linkId] = yup.mixed().when(question, { - is: (answerOptionArray: QuestionnaireItemAnswerOption[]) => - isItemEnabled({ - answerOptionArray, - answer, - operator, - }), - then: () => schema, - otherwise: () => yup.mixed().nullable(), - }); + validationSchema[item.linkId] = getQuestionItemEnableWhenSchema({ + enableWhenItems: item.enableWhen, + enableBehavior: item.enableBehavior, + schema, }); } else { validationSchema[item.linkId] = schema;