Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.git
node_modules
coverage
coverage
dist
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
| Version | Date | Changes |
| ------- | ---------- | ---------- |
| 0.3.0 | 2025-05-26 | - fix a bug with nested questions, that resulted in invalid QuestionnaireResponse resources. <br />⚠️ This means that generated QuestionnaireResponse resources can differ from previous versions output.|
| 0.2.4 | 2024-12-06 | - fix bugs in narrative that resulted in resources not validating |
| 0.2.3 | 2024-11-20 | - make availableLanguages optional again in QuestionnaireData constructor |
| 0.2.2 | 2024-08-28 | - add isTouched() |
Expand Down
46 changes: 44 additions & 2 deletions __tests__/questionHandling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const POPULATE = require('./questionnaires/populate.json') as Questionnaire;
const EMPTY_QUESTIONNAIRE = require('./questionnaires/empty.json') as Questionnaire;
const DEPENDING = require('./questionnaires/depending.json') as Questionnaire;
const CALCULATED = require('./questionnaires/calculatedExpression.json') as Questionnaire;
const NESTED = require('./questionnaires/nested.json') as Questionnaire;
const RESPONSE = require('./questionnaires/variousResponse.json') as QuestionnaireResponse;
const EMPTY_RESPONSE = require('./questionnaires/variousResponseEmpty.json') as QuestionnaireResponse;
const PATIENT = require('./questionnaires/patient.json') as Patient;
Expand Down Expand Up @@ -457,7 +458,7 @@ test('multiple choice / unselectOthersExtension', () => {
expect(testData.isAnswerOptionSelected(mcQuestion!, tomatoOnlyCode)).toBeTruthy();
});

test('calculated Expression', () => {
test('calculated expression', () => {
const testData = new QuestionnaireData(CALCULATED, LANG);

const hidden = testData.findQuestionById('score-choice');
Expand Down Expand Up @@ -526,4 +527,45 @@ test('narrative', () => {
expect(div.includes('Hep B given y / n')).toBeTruthy();
expect(div.includes('Mozzarella cheese')).toBeTruthy();
expect(div.includes('Tomato')).toBeTruthy();
});
});

test('nested questions', () => {
const testData = new QuestionnaireData(NESTED, ['en']);
const parentQuestion = testData.findQuestionById('Q1');
const childQuestion = testData.findQuestionById('Q1a');
const grandchildQuestion = testData.findQuestionById('Q1a1');
expect(parentQuestion).toBeDefined();
expect(childQuestion).toBeDefined();
expect(grandchildQuestion).toBeDefined();

testData.updateQuestionAnswers(parentQuestion!, {answer: {},code: {valueBoolean: true}});
testData.updateQuestionAnswers(childQuestion!, {answer: {},code: {valueString: 'first subanswer'}});
testData.updateQuestionAnswers(grandchildQuestion!, {answer: {},code: {valueString: 'second subanswer'}});

const groupQuestions = [
testData.findQuestionById('Q2a'),
testData.findQuestionById('Q2b')
];
expect(groupQuestions[0]).toBeDefined();
expect(groupQuestions[1]).toBeDefined();
groupQuestions.forEach((gq, i) => testData.updateQuestionAnswers(gq!, {answer: {},code: {valueString: 'group answer #' + (i+1)}}));

expect(() => testData.getQuestionnaireResponse('en')).not.toThrow();
const response = testData.getQuestionnaireResponse('en');
expect(response).toBeDefined();
expect(response.item?.length).toBe(2);
const parentAnswer = response.item?.find(i => i.linkId === parentQuestion?.id);
expect(parentAnswer).toBeDefined();
expect(parentAnswer?.answer?.length).toBe(1);
expect(parentAnswer?.item).toBeUndefined(); // if there is answer present, we can't have subitems
const childAnswer = parentAnswer?.answer ? parentAnswer.answer[0].item?.find((sa) => sa.linkId === childQuestion!.id) : undefined;
expect(childAnswer).toBeDefined();
expect(childAnswer?.answer?.length).toBe(1);
expect(childAnswer?.item).toBeUndefined(); // if there is answer present, we can't have subitems
const grandchildAnswer = childAnswer?.answer ? childAnswer.answer[0].item?.find((sa) => sa.linkId === grandchildQuestion!.id) : undefined;
expect(grandchildAnswer).toBeDefined();
const answerGroup = response.item?.find(i => i.linkId === 'Q2');
expect(answerGroup).toBeDefined();
expect(answerGroup?.answer).toBeUndefined(); // group items don't have answers
expect(answerGroup?.item?.length).toBe(2);
});
68 changes: 68 additions & 0 deletions __tests__/questionnaires/nested.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"resourceType": "Questionnaire",
"id": "nested-questionnaire",
"url": "http://to.be.defined",
"version": "1.0",
"name": "NestedQuestionnaire",
"status": "active",
"subjectType": [
"Patient"
],
"item": [
{
"linkId": "Q1",
"prefix": "1",
"text": "This is the parent question the sub-question depends on.",
"type": "boolean",
"required": true,
"repeats": false,
"item": [
{
"linkId": "Q1a",
"text": "This is a follow-up question for the parent question",
"type": "string",
"enableWhen": [
{
"question": "Q1",
"operator": "=",
"answerBoolean": true
}
],
"required": true,
"repeats": false,
"item": [
{
"linkId": "Q1a1",
"text": "Even the nested question has a subquestion",
"type": "string",
"required": false,
"repeats": false
}
]
}
]
},
{
"linkId": "Q2",
"prefix": "2",
"text": "This is just a question group, that does not require answers.",
"type": "group",
"item": [
{
"linkId": "Q2a",
"text": "This is the first question of the group",
"type": "string",
"required": true,
"repeats": false
},
{
"linkId": "Q2b",
"text": "And this is the second question of the group",
"type": "string",
"required": true,
"repeats": false
}
]
}
]
}

Large diffs are not rendered by default.

137 changes: 137 additions & 0 deletions demo/dist/assets/index-JrfXX6aq.js

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion demo/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FHIR Questionnaire Demo</title>
<script type="module" crossorigin src="/fhir-questionnaire/demo/dist/assets/index-CE1Y1moS.js"></script>
<script type="module" crossorigin src="/fhir-questionnaire/demo/dist/assets/index-C8kO_TjB.js"></script>
<link rel="stylesheet" crossorigin href="/fhir-questionnaire/demo/dist/assets/index-DPhlpSYZ.css">
</head>
<body>
Expand Down
14 changes: 7 additions & 7 deletions demo/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@i4mi/fhir_questionnaire": "0.2.4",
"@i4mi/fhir_questionnaire": "0.3.0",
"@i4mi/fhir_r4": "^2.1.5",
"vue": "^3.5.14"
},
Expand Down
6 changes: 3 additions & 3 deletions demo/src/assets/questionnaires/effort.json
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@
},
{
"url": "content",
"valueString": "Der geleistete Zeitaufwand wird aufgrund der gegebenen Antworten automatisch berechnet."
"valueString": "Der wöchentlich geleistete Zeitaufwand wird aufgrund der gegebenen Antworten automatisch berechnet."
}
]
},
Expand All @@ -1153,7 +1153,7 @@
},
{
"url": "content",
"valueString": "Le temps passé est calculé automatiquement en fonction des réponses données."
"valueString": "Le temps passé par semaine est calculé automatiquement en fonction des réponses données."
}
]
},
Expand All @@ -1166,7 +1166,7 @@
},
{
"url": "content",
"valueString": "The time spent is calculated automatically based on the answers given."
"valueString": "The time spent per week is calculated automatically based on the answers given."
}
]
}
Expand Down
3 changes: 2 additions & 1 deletion demo/src/components/Question.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
:step="question.options.sliderStep"
:min="question.options.min !== undefined ? question.options.min : 0"
:max="question.options.max !== undefined ? question.options.max : 100"
@change="updateValue(value, question.type)" />
@change="updateValue(Number(value), question.type)" />
<span>{{ question.options.max }}</span>
</div>
</div>
Expand Down Expand Up @@ -162,6 +162,7 @@ export default defineComponent({
},
methods: {
updateValue(value: string | number | boolean, type: QuestionnaireItemType) {
console.log('update value', value, typeof value, type)
const answer: IAnswerOption = {
answer: {},
code: {}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
{
"name": "@i4mi/fhir_questionnaire",
"version": "0.2.4",
"version": "0.3.0",
"description": "TS Package for handling FHIR Questionnaire and generating QuestionnaireRespones.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "rm -rf ./dist && npm i && tsc",
"test": "jest --config jestconfig.json --coverage --silent",
"lint": "eslint --ext .js,.ts ./ --fix"
"lint": "eslint --ext .js,.ts ./ --fix",
"publish:beta": "npm i && npm run build && npm run test && npm publish --access public --tag beta",
"publish": "npm i && npm run build && npm run test && npm publish --access public"
},
"repository": {
"type": "git",
Expand All @@ -21,8 +23,9 @@
"fhir",
"resources",
"i4mi",
"pcdh",
"patient-centered digital healt",
"medical informatics",
"bern university of applied sciences",
"patient-centered digital health",
"questionnaire",
"questionnaireresponse",
"typescript",
Expand Down
10 changes: 6 additions & 4 deletions src/QuestionnaireData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,14 @@ function mapIQuestionToQuestionnaireResponseItem(_questions: IQuestion[], _respo
} else {
responseItem.answer!.push(answer);
}

if (question.subItems && question.subItems.length > 0) {
answer.item = [];
mapIQuestionToQuestionnaireResponseItem(question.subItems, answer.item, _language);
}
});

if (question.subItems && question.subItems.length > 0) {
(responseItem as QuestionnaireResponseItem).item = [];
mapIQuestionToQuestionnaireResponseItem(question.subItems, (responseItem as QuestionnaireResponseItem).item || [], _language);
}

if (question.type === QuestionnaireItemType.DISPLAY) responseItem.answer = undefined;
// add to array
_responseItems.push(responseItem);
Expand Down