diff --git a/app/prototype_v4_1/controllers/question.js b/app/prototype_v4_1/controllers/question.js index 6b3dc3e..06c3efa 100644 --- a/app/prototype_v4_1/controllers/question.js +++ b/app/prototype_v4_1/controllers/question.js @@ -3,6 +3,7 @@ const { renderQuestion, version, view } = require('../lib/question-renderer') const { getCheckYourAnswers } = require('../lib/summary') const { deleteUnselectedShishaSettingAnswers, + deleteUnselectedSmokingQuantityOtherAnswer, deleteUnselectedSmokingChangeAnswers, deleteUnselectedSmokingTypeAnswers, getFormerSmokerFallbackStep, @@ -773,6 +774,7 @@ exports.smokingQuantity_get = (req, res) => { exports.smokingQuantity_post = (req, res) => { const { step, steps } = getSmokingTypeStep(req, 'smoking-quantity') + deleteUnselectedSmokingQuantityOtherAnswer(req.session.data.answers, step) const errors = step ? validateSmokingTypeQuestion(req, 'smoking-quantity', step) : [] if (!step) { @@ -859,6 +861,7 @@ exports.smokingQuantityChange_get = (req, res) => { exports.smokingQuantityChange_post = (req, res) => { const { step, steps } = getSmokingTypeStep(req, 'smoking-quantity-change') + deleteUnselectedSmokingQuantityOtherAnswer(req.session.data.answers, step, 'quantity') const errors = step ? validateSmokingTypeQuestion(req, 'smoking-quantity-change', step) : [] if (!step) { diff --git a/app/prototype_v4_1/data/questions.yaml b/app/prototype_v4_1/data/questions.yaml index 97e33ee..423d47e 100644 --- a/app/prototype_v4_1/data/questions.yaml +++ b/app/prototype_v4_1/data/questions.yaml @@ -960,6 +960,18 @@ questions: value: 1_to_2_hours - label: More than 2 hours value: more_than_2_hours + - divider: or + - label: Another amount + value: another_amount + conditionalInput: + id: smoking-quantity-other + name: answers[smokingQuantityOther] + answerKey: smokingQuantityOther + label: Enter the number of hours + hint: Give an estimate if you are not sure + suffix: hours + inputmode: numeric + classes: nhsuk-input--width-4 validation: required: true validation: diff --git a/app/prototype_v4_1/lib/question-validator.js b/app/prototype_v4_1/lib/question-validator.js index cf60198..35d5ecf 100644 --- a/app/prototype_v4_1/lib/question-validator.js +++ b/app/prototype_v4_1/lib/question-validator.js @@ -55,6 +55,21 @@ const getAnswerValue = (answers = {}, question) => { return value } +/** + * Resolve a conditional reveal value from a runtime override or answer key. + * + * @param {Object} answers - Session answers object. + * @param {Object} rule - Conditional validation rule. + * @returns {*} Submitted conditional value. + */ +const getConditionalValue = (answers = {}, rule = {}) => { + if (rule.value !== undefined) { + return rule.value + } + + return answers[rule.answerKey] +} + /** * Resolve a date input value from the answers object. * @@ -128,7 +143,7 @@ const validateConditional = (answers, question, errors) => { return } - const conditionalValue = answers[rule.answerKey] + const conditionalValue = getConditionalValue(answers, rule) if (isBlank(conditionalValue)) { const error = question.errors?.conditional?.[triggerValue]?.required || { diff --git a/app/prototype_v4_1/lib/summary.js b/app/prototype_v4_1/lib/summary.js index b6702dd..3004166 100644 --- a/app/prototype_v4_1/lib/summary.js +++ b/app/prototype_v4_1/lib/summary.js @@ -92,6 +92,24 @@ const formatWeight = (weight = {}) => { return '' } +/** + * Format a smoking quantity, including conditional reveal "another amount" answers. + * + * @param {string} type - Tobacco type key. + * @param {Object} answer - Answer object containing quantity fields. + * @param {string} quantityKey - Quantity answer key. + * @returns {string} Formatted quantity answer. + */ +const formatSmokingQuantityAnswer = (type, answer = {}, quantityKey = 'smokingQuantity') => { + if (answer[quantityKey] === 'another_amount') { + return answer.smokingQuantityOther + ? formatQuantity(answer.smokingQuantityOther, 'hour', 'hours') + : '' + } + + return getSmokingQuantity(type, answer[quantityKey]) +} + /** * Format one or more stored values using display labels. * @@ -226,7 +244,7 @@ const getCheckYourAnswers = (answers = {}) => { }), makeSummaryRow({ key: getSmokingStepHeading('smoking-quantity', type, setting, isPast, settingAnswer), - value: getSmokingQuantity(type, settingAnswer.smokingQuantity), + value: formatSmokingQuantityAnswer(type, settingAnswer), href: getSmokingTypeStepUrl({ page: 'smoking-quantity', type, setting }) }) ] @@ -270,7 +288,7 @@ const getCheckYourAnswers = (answers = {}) => { }), type !== 'shisha' && makeSummaryRow({ key: getSmokingStepHeading('smoking-quantity', type, undefined, isPast, answer), - value: getSmokingQuantity(type, answer.smokingQuantity), + value: formatSmokingQuantityAnswer(type, answer), href: getSmokingTypeStepUrl({ page: 'smoking-quantity', type }) }), type !== 'shisha' && makeSummaryRow({ diff --git a/app/prototype_v4_1/lib/tobacco-flow.js b/app/prototype_v4_1/lib/tobacco-flow.js index 790fb80..490b9b3 100644 --- a/app/prototype_v4_1/lib/tobacco-flow.js +++ b/app/prototype_v4_1/lib/tobacco-flow.js @@ -480,6 +480,48 @@ const getSmokingChangeAnswer = (answer = {}, change) => { return answerKey ? answer[answerKey] || {} : {} } +/** + * Get the object that stores a smoking quantity answer for the current step. + * + * @param {Object} answers - Session answers object. + * @param {SmokingTypeStep} step - Current tobacco step. + * @returns {Object} Answer object containing the quantity answer. + */ +const getSmokingQuantityAnswer = (answers = {}, step = {}) => { + const answer = answers[step.type] || {} + + if (step.change) { + return getSmokingChangeAnswer(answer, step.change) + } + + if (step.setting) { + return getShishaSettingAnswer(answer, step.setting) + } + + return answer +} + +/** + * Remove a stale conditional reveal quantity when "another amount" is not selected. + * + * @param {Object} answers - Session answers object, mutated in place. + * @param {SmokingTypeStep} step - Current tobacco step. + * @param {string} quantityKey - Quantity answer key. + */ +const deleteUnselectedSmokingQuantityOtherAnswer = (answers = {}, step, quantityKey = 'smokingQuantity') => { + delete answers.smokingQuantityOther + + if (!step) { + return + } + + const answer = getSmokingQuantityAnswer(answers, step) + + if (answer[quantityKey] !== 'another_amount') { + delete answer.smokingQuantityOther + } +} + /** * Build contextual comparison text for changed-smoking questions. * @@ -637,6 +679,7 @@ const getSmokingQuantityQuestionOverrides = ({ caption, name, value, + conditionalValue, smokingType }) => { const question = getQuestion(page) @@ -645,6 +688,29 @@ const getSmokingQuantityQuestionOverrides = ({ const hasHintOverride = Object.prototype.hasOwnProperty.call(variantInput, 'hint') const hint = hasHintOverride ? variantInput.hint : question.input.hint const questionType = variant.type || question.type + const conditionalHref = '#smoking-quantity-other' + const items = variant.options + ? variant.options.map((option) => { + const item = toComponentItem(option) + + if (!item.conditionalInput) { + return item + } + + const conditionalName = step.setting + ? `answers[${step.type}][${step.setting}][${item.conditionalInput.answerKey}]` + : `answers[${step.type}][${item.conditionalInput.answerKey}]` + + return { + ...item, + conditionalInput: { + ...item.conditionalInput, + name: conditionalName, + value: conditionalValue + } + } + }) + : question.items return { type: questionType, @@ -659,8 +725,43 @@ const getSmokingQuantityQuestionOverrides = ({ hintParam: hint ? { text: hint } : undefined, suffix: questionType === 'text' ? smokingType.suffix : undefined }, - validation: variant.validation, - items: variant.options ? variant.options.map(toComponentItem) : question.items, + validation: { + ...variant.validation, + conditional: { + another_amount: { + required: true, + type: 'number', + min: 0.5, + max: 24, + answerKey: 'smokingQuantityOther', + value: conditionalValue, + href: conditionalHref + } + } + }, + errors: { + conditional: { + another_amount: { + required: { + text: 'Enter the number of hours', + href: conditionalHref + } + } + }, + invalid: { + text: 'Number of hours must be a number', + href: conditionalHref + }, + min: { + text: 'Number of hours must be 0.5 or more', + href: conditionalHref + }, + max: { + text: 'Number of hours must be 24 or fewer', + href: conditionalHref + } + }, + items, value } } @@ -831,6 +932,7 @@ const getSmokingContentQuestionOverrides = ({ ? `answers[${step.type}][${step.setting}][smokingQuantity]` : `answers[${step.type}][smokingQuantity]`, value: isSettingSpecific ? settingAnswer.smokingQuantity : answer.smokingQuantity, + conditionalValue: isSettingSpecific ? settingAnswer.smokingQuantityOther : answer.smokingQuantityOther, smokingType }) } @@ -871,6 +973,7 @@ const getSmokingContentQuestionOverrides = ({ caption: smokingType.caption, name: `answers[${step.type}][${smokingChange.answerKey}][quantity]`, value: changeAnswer.quantity, + conditionalValue: changeAnswer.smokingQuantityOther, smokingType }) } @@ -1012,6 +1115,7 @@ const validateSmokingTypeQuestion = (req, page, step) => { module.exports = { deleteUnselectedShishaSettingAnswers, + deleteUnselectedSmokingQuantityOtherAnswer, deleteUnselectedSmokingChangeAnswers, deleteUnselectedSmokingTypeAnswers, formatQuantity, diff --git a/app/prototype_v4_1/views/questions/_question.html b/app/prototype_v4_1/views/questions/_question.html index c4364e8..7ce7bbb 100644 --- a/app/prototype_v4_1/views/questions/_question.html +++ b/app/prototype_v4_1/views/questions/_question.html @@ -68,7 +68,7 @@ suffix: item.conditionalInput.suffix, prefix: item.conditionalInput.prefix, inputmode: item.conditionalInput.inputmode, - value: data.answers[item.conditionalInput.answerKey], + value: item.conditionalInput.value if item.conditionalInput.value is defined else data.answers[item.conditionalInput.answerKey], classes: item.conditionalInput.classes, errorMessage: { text: errorMap[item.conditionalInput.id].text