diff --git a/src/server/common/model/answer/cph-number/cph-number.js b/src/server/common/model/answer/cph-number/cph-number.js index 564baefb..a5e5ef32 100644 --- a/src/server/common/model/answer/cph-number/cph-number.js +++ b/src/server/common/model/answer/cph-number/cph-number.js @@ -1,5 +1,7 @@ import { TextAnswer } from '../text/text.js' +/** @import { TextConfig } from '../text/text.js' */ + const cphNumberRegex = /^(\d{2})\/(\d{3})\/(\d{4})$/i const invalidCphNumberError = @@ -15,9 +17,13 @@ const emptyCphNumberError = 'Enter the farm or premises CPH number' * @augments {TextAnswer} */ export class CphNumberAnswer extends TextAnswer { + /** @type {TextConfig} */ static config = { payloadKey: 'cphNumber', stripWhitespace: true, + autocomplete: 'cph-number', + characterWidth: 10, + hint: 'For example, 12/345/6789', validation: { maxLength: { value: 11, message: invalidCphNumberError }, pattern: { regex: cphNumberRegex, message: invalidCphNumberError }, diff --git a/src/server/common/model/answer/email/email-address.js b/src/server/common/model/answer/email/email-address.js index 63c4cb22..f031c677 100644 --- a/src/server/common/model/answer/email/email-address.js +++ b/src/server/common/model/answer/email/email-address.js @@ -1,5 +1,7 @@ import { TextAnswer } from '../text/text.js' +/** @import {TextConfig} from '../text/text.js' */ + const emailAddressRegex = /^[^@]+@[^@]+$/ const maxLength = 255 @@ -17,9 +19,14 @@ const invalidAddressError = * @augments {TextAnswer} */ export class EmailAddressAnswer extends TextAnswer { + /** @type {TextConfig} */ static config = { payloadKey: 'emailAddress', stripWhitespace: true, + type: 'email', + spellcheck: false, + autocomplete: 'email-address', + characterWidth: 20, validation: { pattern: { regex: emailAddressRegex, message: invalidAddressError }, maxLength: { value: maxLength, message: invalidAddressError }, diff --git a/src/server/common/model/answer/text/text.js b/src/server/common/model/answer/text/text.js index a5609351..a8b76e73 100644 --- a/src/server/common/model/answer/text/text.js +++ b/src/server/common/model/answer/text/text.js @@ -3,6 +3,8 @@ import { AnswerModel } from '../answer-model.js' import { validateAnswerAgainstSchema } from '../validation.js' import { NotImplementedError } from '../../../helpers/not-implemented-error.js' +/** @import {AnswerViewModelOptions} from '../answer-model.js' */ + /** * @param {TextConfig} config * @returns {Joi.Schema} @@ -39,6 +41,11 @@ const whitespaceRegex = /\s+/g * export @typedef {{ * payloadKey: string, * stripWhitespace?: boolean, + * type?: 'email', // the default is text, so no need to specify + * spellcheck?: false, + * characterWidth?: 10 | 20, + * autocomplete?: string, + * hint?: string, * validation: { * maxLength: { value: number, message: string }, * empty: { message: string }, @@ -98,6 +105,45 @@ export class TextAnswer extends AnswerModel { ) } + /** + * @param {AnswerViewModelOptions} options + */ + viewModel({ validate }) { + const { payloadKey, type, spellcheck, autocomplete, characterWidth, hint } = + this.config + const viewModel = { + id: payloadKey, + name: payloadKey, + value: this.value + } + + if (characterWidth) { + viewModel.classes = `govuk-input--width-${characterWidth}` + } + + if (autocomplete) { + viewModel.autocomplete = autocomplete + } + + if (type) { + viewModel.type = type + } + + if (hint) { + viewModel.hint = { text: hint } + } + + if (spellcheck === false) { + viewModel.spellcheck = false + } + + if (validate) { + viewModel.errorMessage = this.validate().errors[payloadKey] + } + + return viewModel + } + /** * @param {Payload} fields */ diff --git a/src/server/common/model/answer/text/text.njk b/src/server/common/model/answer/text/text.njk new file mode 100644 index 00000000..ecd6a734 --- /dev/null +++ b/src/server/common/model/answer/text/text.njk @@ -0,0 +1,6 @@ +{% from "govuk/components/input/macro.njk" import govukInput %} +{% from "govuk/components/fieldset/macro.njk" import govukFieldset %} + +{% call govukFieldset() %} + {{ govukInput(answer.viewModel(viewModelOptions)) }} +{% endcall %} diff --git a/src/server/common/model/answer/text/text.test.js b/src/server/common/model/answer/text/text.test.js index 9b790313..7d390559 100644 --- a/src/server/common/model/answer/text/text.test.js +++ b/src/server/common/model/answer/text/text.test.js @@ -1,11 +1,13 @@ import { TextAnswer } from './text.js' /** @import {TextConfig} from './text.js' */ +const maxLengthError = 'Text exceeds maximum length (40)' + /** @type {TextConfig} */ const textConfig = { payloadKey: 'textPayload', validation: { - maxLength: { value: 40, message: 'Text exceeds maximum length (40)' }, + maxLength: { value: 40, message: maxLengthError }, empty: { message: 'Text must not be empty' } } } @@ -25,6 +27,11 @@ const validPayload = { textPayload: 'some text' } +const longAnswer = new Array(41).fill('a').join('') +const invalidPayload = { + textPayload: longAnswer +} + describe('TextAnswer.new', () => { it('should strip away any irrelevant values', () => { const payload = { ...validPayload, nextPage: '/other/page' } @@ -37,7 +44,7 @@ describe('TextAnswer.new', () => { describe('TextAnswer.validate', () => { it('should error if max length is exceeded', () => { const textAnswer = new TestTextAnswer({ - textPayload: new Array(41).fill('a').join('') + textPayload: longAnswer }) const { isValid, errors } = textAnswer.validate() @@ -220,3 +227,68 @@ describe('TestAnswer.html', () => { expect(textAnswer.html).toBe('') }) }) + +describe('TestAnswer.viewModel (without any extra options)', () => { + const textAnswer = new TestTextAnswer(invalidPayload) + + it('should return data to render without errors (if validate is false)', () => { + expect(textAnswer.viewModel({ validate: false })).toEqual({ + id: 'textPayload', + name: 'textPayload', + value: textAnswer.value + }) + }) + + it('should return data to render with errors (if validate is true)', () => { + expect(textAnswer.viewModel({ validate: true })).toEqual({ + id: 'textPayload', + name: 'textPayload', + value: textAnswer.value, + errorMessage: { text: maxLengthError } + }) + }) +}) + +describe('TestAnswer.viewModel (with all optional options)', () => { + /** @type {TextConfig} */ + const textConfigWithExtraOptions = { + ...textConfig, + type: 'email', + autocomplete: 'email-address', + spellcheck: false, + characterWidth: 20, + hint: 'Enter your email' + } + + class ExtraOptionsTextAnswer extends TextAnswer { + static config = textConfigWithExtraOptions + } + const textAnswer = new ExtraOptionsTextAnswer(invalidPayload) + + it('should return data to render without errors (if validate is false)', () => { + expect(textAnswer.viewModel({ validate: false })).toEqual({ + id: 'textPayload', + name: 'textPayload', + type: 'email', + hint: { text: 'Enter your email' }, + spellcheck: false, + autocomplete: 'email-address', + classes: 'govuk-input--width-20', + value: textAnswer.value + }) + }) + + it('should return data to render with errors (if validate is true)', () => { + expect(textAnswer.viewModel({ validate: true })).toEqual({ + id: 'textPayload', + name: 'textPayload', + type: 'email', + hint: { text: 'Enter your email' }, + spellcheck: false, + autocomplete: 'email-address', + classes: 'govuk-input--width-20', + value: textAnswer.value, + errorMessage: { text: maxLengthError } + }) + }) +}) diff --git a/src/server/licence/email-address/__snapshots__/index.test.js.snap b/src/server/licence/email-address/__snapshots__/index.test.js.snap index 119bd41f..dc6f9796 100644 --- a/src/server/licence/email-address/__snapshots__/index.test.js.snap +++ b/src/server/licence/email-address/__snapshots__/index.test.js.snap @@ -14,8 +14,7 @@ exports[`EmailAddressPage EmailAddressPage.content should render expected respon
- -
+
diff --git a/src/server/licence/email-address/index.njk b/src/server/licence/email-address/index.njk index 5930975d..0e9bbb33 100644 --- a/src/server/licence/email-address/index.njk +++ b/src/server/licence/email-address/index.njk @@ -1,22 +1,5 @@ -{% from "govuk/components/input/macro.njk" import govukInput %} -{% from "govuk/components/fieldset/macro.njk" import govukFieldset %} {% extends 'layouts/questions.njk' %} {% block questions %} - - {% call govukFieldset() %} - - {{ - govukInput({ - id: "emailAddress", - name: "emailAddress", - type: "email", - spellcheck: false, - autocomplete: "email-address", - classes: "govuk-input--width-20", - value: value, - errorMessage: errors.emailAddress - }) - }} - {% endcall %} + {% include "model/answer/text/text.njk" %} {% endblock %} diff --git a/src/server/origin/cph-number/__snapshots__/index.test.js.snap b/src/server/origin/cph-number/__snapshots__/index.test.js.snap index 5d0b6a00..9d3ccdbf 100644 --- a/src/server/origin/cph-number/__snapshots__/index.test.js.snap +++ b/src/server/origin/cph-number/__snapshots__/index.test.js.snap @@ -14,7 +14,7 @@ exports[`CphNumberPage #cphNumberPage.content licenceSummaryPage.content should
-
+
For example, 12/345/6789 diff --git a/src/server/origin/cph-number/index.njk b/src/server/origin/cph-number/index.njk index 62383cba..0e9bbb33 100644 --- a/src/server/origin/cph-number/index.njk +++ b/src/server/origin/cph-number/index.njk @@ -1,22 +1,5 @@ -{% from "govuk/components/input/macro.njk" import govukInput %} -{% from "govuk/components/fieldset/macro.njk" import govukFieldset %} {% extends 'layouts/questions.njk' %} {% block questions %} - - {% call govukFieldset() %} - {{ - govukInput({ - id: "cphNumber", - name: "cphNumber", - autocomplete: "cph-number", - classes: "govuk-input--width-10", - value: value, - errorMessage: errors.cphNumber, - hint: { - text: "For example, 12/345/6789" - } - }) - }} - {% endcall %} + {% include "model/answer/text/text.njk" %} {% endblock %}