@defra/forms-engine-plugin 4.0.11 → 4.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +2 -2
  2. package/.server/server/plugins/engine/components/EastingNorthingField.js +4 -24
  3. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  4. package/.server/server/plugins/engine/components/LatLongField.js +8 -31
  5. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
  6. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +1 -3
  7. package/.server/server/plugins/engine/components/LocationFieldBase.js +1 -8
  8. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  9. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +0 -2
  10. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +7 -18
  11. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
  12. package/.server/server/plugins/engine/components/NumberField.d.ts +0 -18
  13. package/.server/server/plugins/engine/components/NumberField.js +2 -103
  14. package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
  15. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +0 -2
  16. package/.server/server/plugins/engine/components/OsGridRefField.js +4 -32
  17. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
  18. package/.server/server/plugins/engine/models/FormModel.d.ts +2 -2
  19. package/.server/server/plugins/engine/models/FormModel.js +1 -1
  20. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  21. package/.server/server/plugins/engine/models/types.d.ts +1 -1
  22. package/.server/server/plugins/engine/models/types.js.map +1 -1
  23. package/package.json +2 -2
  24. package/src/server/forms/register-as-a-unicorn-breeder.yaml +2 -2
  25. package/src/server/plugins/engine/components/EastingNorthingField.test.ts +0 -14
  26. package/src/server/plugins/engine/components/EastingNorthingField.ts +4 -24
  27. package/src/server/plugins/engine/components/LatLongField.test.ts +4 -24
  28. package/src/server/plugins/engine/components/LatLongField.ts +8 -33
  29. package/src/server/plugins/engine/components/LocationFieldBase.ts +1 -15
  30. package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +22 -8
  31. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +9 -20
  32. package/src/server/plugins/engine/components/NumberField.test.ts +12 -504
  33. package/src/server/plugins/engine/components/NumberField.ts +4 -133
  34. package/src/server/plugins/engine/components/OsGridRefField.test.ts +11 -33
  35. package/src/server/plugins/engine/components/OsGridRefField.ts +5 -38
  36. package/src/server/plugins/engine/models/FormModel.ts +1 -1
  37. package/src/server/plugins/engine/models/types.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"NumberField.js","names":["joi","FormComponent","isFormValue","messageTemplate","NumberField","constructor","def","props","options","schema","formSchema","number","custom","getValidatorPrecision","label","required","allow","messages","customValidationMessages","empty","min","max","precision","integer","customValidationMessage","message","default","stateSchema","getFormValueFromState","state","name","getFormValue","value","isValue","undefined","getViewModel","payload","errors","viewModel","attributes","prefix","suffix","inputmode","text","isNumber","getAllPossibleErrors","baseErrors","type","template","numberInteger","advancedSettingsErrors","numberMin","numberMax","numberPrecision","validateStringLength","minLength","maxLength","isValid","valueStr","String","length","error","validateMinimumPrecision","minPrecision","Number","isInteger","decimalIndex","indexOf","decimalPlaces","handleLengthValidationError","lengthCheck","helpers","errorType","contextData","context","component","validator","limit","validationSchema","prefs","convert","attempt"],"sources":["../../../../../src/server/plugins/engine/components/NumberField.ts"],"sourcesContent":["import { type NumberFieldComponent } from '@defra/forms-model'\nimport joi, {\n type CustomHelpers,\n type CustomValidator,\n type NumberSchema\n} from 'joi'\n\nimport {\n FormComponent,\n isFormValue\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type ErrorMessageTemplateList,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nexport class NumberField extends FormComponent {\n declare options: NumberFieldComponent['options']\n declare schema: NumberFieldComponent['schema']\n declare formSchema: NumberSchema\n declare stateSchema: NumberSchema\n\n constructor(\n def: NumberFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { options, schema } = def\n\n let formSchema = joi\n .number()\n .custom(getValidatorPrecision(this))\n .label(this.label)\n .required()\n\n if (options.required === false) {\n formSchema = formSchema.allow('')\n } else {\n const messages = options.customValidationMessages\n\n formSchema = formSchema.empty('').messages({\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n 'any.required':\n messages?.['any.required'] ?? (messageTemplate.required as string)\n })\n }\n\n if (typeof schema.min === 'number') {\n formSchema = formSchema.min(schema.min)\n }\n\n if (typeof schema.max === 'number') {\n formSchema = formSchema.max(schema.max)\n }\n\n if (typeof schema.precision === 'number' && schema.precision <= 0) {\n formSchema = formSchema.integer()\n }\n\n if (options.customValidationMessage) {\n const message = options.customValidationMessage\n\n formSchema = formSchema.messages({\n 'any.required': message,\n 'number.base': message,\n 'number.precision': message,\n 'number.integer': message,\n 'number.min': message,\n 'number.max': message\n })\n } else if (options.customValidationMessages) {\n formSchema = formSchema.messages(options.customValidationMessages)\n }\n\n this.formSchema = formSchema.default('')\n this.stateSchema = formSchema.default(null).allow(null)\n this.options = options\n this.schema = schema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const { name } = this\n return this.getFormValue(state[name])\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isValue(value) ? value : undefined\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const { options, schema } = this\n\n const viewModel = super.getViewModel(payload, errors)\n let { attributes, prefix, suffix, value } = viewModel\n\n if (typeof schema.precision === 'undefined' || schema.precision <= 0) {\n // If precision isn't provided or provided and\n // less than or equal to 0, use numeric inputmode\n attributes.inputmode = 'numeric'\n }\n\n if (options.prefix) {\n prefix = {\n text: options.prefix\n }\n }\n\n if (options.suffix) {\n suffix = {\n text: options.suffix\n }\n }\n\n // Allow any `toString()`-able value so non-numeric\n // values are shown alongside their error messages\n if (!isFormValue(value)) {\n value = undefined\n }\n\n return {\n ...viewModel,\n attributes,\n prefix,\n suffix,\n value\n }\n }\n\n isValue(value?: FormStateValue | FormState) {\n return NumberField.isNumber(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return NumberField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n { type: 'numberInteger', template: messageTemplate.numberInteger }\n ],\n advancedSettingsErrors: [\n { type: 'numberMin', template: messageTemplate.numberMin },\n { type: 'numberMax', template: messageTemplate.numberMax },\n { type: 'numberPrecision', template: messageTemplate.numberPrecision }\n ]\n }\n }\n\n static isNumber(value?: FormStateValue | FormState): value is number {\n return typeof value === 'number'\n }\n}\n\n/**\n * Validates string length of a numeric value\n * @param value - The numeric value to validate\n * @param minLength - Minimum required string length\n * @param maxLength - Maximum allowed string length\n * @returns Object with validation result\n */\nexport function validateStringLength(\n value: number,\n minLength?: number,\n maxLength?: number\n): { isValid: boolean; error?: 'minLength' | 'maxLength' } {\n if (typeof minLength !== 'number' && typeof maxLength !== 'number') {\n return { isValid: true }\n }\n\n const valueStr = String(value)\n\n if (typeof minLength === 'number' && valueStr.length < minLength) {\n return { isValid: false, error: 'minLength' }\n }\n\n if (typeof maxLength === 'number' && valueStr.length > maxLength) {\n return { isValid: false, error: 'maxLength' }\n }\n\n return { isValid: true }\n}\n\n/**\n * Validates minimum decimal precision\n * @param value - The numeric value to validate\n * @param minPrecision - Minimum required decimal places\n * @returns true if valid, false if invalid\n */\nexport function validateMinimumPrecision(\n value: number,\n minPrecision: number\n): boolean {\n if (Number.isInteger(value)) {\n return false\n }\n\n const valueStr = String(value)\n const decimalIndex = valueStr.indexOf('.')\n\n if (decimalIndex !== -1) {\n const decimalPlaces = valueStr.length - decimalIndex - 1\n return decimalPlaces >= minPrecision\n }\n\n return false\n}\n\n/**\n * Helper function to handle length validation errors\n * Returns the appropriate error response based on the validation result\n */\nfunction handleLengthValidationError(\n lengthCheck: ReturnType<typeof validateStringLength>,\n helpers: CustomHelpers,\n custom: string | undefined,\n minLength: number | undefined,\n maxLength: number | undefined\n) {\n if (!lengthCheck.isValid && lengthCheck.error) {\n const errorType = `number.${lengthCheck.error}`\n\n if (custom) {\n // Only pass the relevant length value in context\n const contextData =\n lengthCheck.error === 'minLength'\n ? { minLength: minLength ?? 0 }\n : { maxLength: maxLength ?? 0 }\n return helpers.message({ custom }, contextData)\n }\n\n const context =\n lengthCheck.error === 'minLength'\n ? { minLength: minLength ?? 0 }\n : { maxLength: maxLength ?? 0 }\n return helpers.error(errorType, context)\n }\n return null\n}\n\nexport function getValidatorPrecision(component: NumberField) {\n const validator: CustomValidator = (value: number, helpers) => {\n const { options, schema } = component\n const { customValidationMessage: custom } = options\n const {\n precision: limit,\n minPrecision,\n minLength,\n maxLength\n } = schema as {\n precision?: number\n minPrecision?: number\n minLength?: number\n maxLength?: number\n }\n\n if (!limit || limit <= 0) {\n const lengthCheck = validateStringLength(value, minLength, maxLength)\n const error = handleLengthValidationError(\n lengthCheck,\n helpers,\n custom,\n minLength,\n maxLength\n )\n if (error) return error\n return value\n }\n\n // Validate precision (max decimal places)\n const validationSchema = joi\n .number()\n .precision(limit)\n .prefs({ convert: false })\n\n try {\n joi.attempt(value, validationSchema)\n } catch {\n return custom\n ? helpers.message({ custom }, { limit })\n : helpers.error('number.precision', { limit })\n }\n\n // Validate minimum precision (min decimal places)\n if (typeof minPrecision === 'number' && minPrecision > 0) {\n if (!validateMinimumPrecision(value, minPrecision)) {\n return helpers.error('number.minPrecision', { minPrecision })\n }\n }\n\n // Check string length validation after precision checks\n const lengthCheck = validateStringLength(value, minLength, maxLength)\n const error = handleLengthValidationError(\n lengthCheck,\n helpers,\n custom,\n minLength,\n maxLength\n )\n if (error) return error\n\n return value\n }\n\n return validator\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"NumberField.js","names":["joi","FormComponent","isFormValue","messageTemplate","NumberField","constructor","def","props","options","schema","formSchema","number","custom","getValidatorPrecision","label","required","allow","messages","customValidationMessages","empty","min","max","precision","integer","customValidationMessage","message","default","stateSchema","getFormValueFromState","state","name","getFormValue","value","isValue","undefined","getViewModel","payload","errors","viewModel","attributes","prefix","suffix","inputmode","text","isNumber","getAllPossibleErrors","baseErrors","type","template","numberInteger","advancedSettingsErrors","numberMin","numberMax","numberPrecision","component","validator","helpers","limit","validationSchema","prefs","convert","attempt","error"],"sources":["../../../../../src/server/plugins/engine/components/NumberField.ts"],"sourcesContent":["import { type NumberFieldComponent } from '@defra/forms-model'\nimport joi, { type CustomValidator, type NumberSchema } from 'joi'\n\nimport {\n FormComponent,\n isFormValue\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type ErrorMessageTemplateList,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nexport class NumberField extends FormComponent {\n declare options: NumberFieldComponent['options']\n declare schema: NumberFieldComponent['schema']\n declare formSchema: NumberSchema\n declare stateSchema: NumberSchema\n\n constructor(\n def: NumberFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { options, schema } = def\n\n let formSchema = joi\n .number()\n .custom(getValidatorPrecision(this))\n .label(this.label)\n .required()\n\n if (options.required === false) {\n formSchema = formSchema.allow('')\n } else {\n const messages = options.customValidationMessages\n\n formSchema = formSchema.empty('').messages({\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n 'any.required':\n messages?.['any.required'] ?? (messageTemplate.required as string)\n })\n }\n\n if (typeof schema.min === 'number') {\n formSchema = formSchema.min(schema.min)\n }\n\n if (typeof schema.max === 'number') {\n formSchema = formSchema.max(schema.max)\n }\n\n if (typeof schema.precision === 'number' && schema.precision <= 0) {\n formSchema = formSchema.integer()\n }\n\n if (options.customValidationMessage) {\n const message = options.customValidationMessage\n\n formSchema = formSchema.messages({\n 'any.required': message,\n 'number.base': message,\n 'number.precision': message,\n 'number.integer': message,\n 'number.min': message,\n 'number.max': message\n })\n } else if (options.customValidationMessages) {\n formSchema = formSchema.messages(options.customValidationMessages)\n }\n\n this.formSchema = formSchema.default('')\n this.stateSchema = formSchema.default(null).allow(null)\n this.options = options\n this.schema = schema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const { name } = this\n return this.getFormValue(state[name])\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isValue(value) ? value : undefined\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const { options, schema } = this\n\n const viewModel = super.getViewModel(payload, errors)\n let { attributes, prefix, suffix, value } = viewModel\n\n if (typeof schema.precision === 'undefined' || schema.precision <= 0) {\n // If precision isn't provided or provided and\n // less than or equal to 0, use numeric inputmode\n attributes.inputmode = 'numeric'\n }\n\n if (options.prefix) {\n prefix = {\n text: options.prefix\n }\n }\n\n if (options.suffix) {\n suffix = {\n text: options.suffix\n }\n }\n\n // Allow any `toString()`-able value so non-numeric\n // values are shown alongside their error messages\n if (!isFormValue(value)) {\n value = undefined\n }\n\n return {\n ...viewModel,\n attributes,\n prefix,\n suffix,\n value\n }\n }\n\n isValue(value?: FormStateValue | FormState) {\n return NumberField.isNumber(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return NumberField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n { type: 'numberInteger', template: messageTemplate.numberInteger }\n ],\n advancedSettingsErrors: [\n { type: 'numberMin', template: messageTemplate.numberMin },\n { type: 'numberMax', template: messageTemplate.numberMax },\n { type: 'numberPrecision', template: messageTemplate.numberPrecision }\n ]\n }\n }\n\n static isNumber(value?: FormStateValue | FormState): value is number {\n return typeof value === 'number'\n }\n}\n\nexport function getValidatorPrecision(component: NumberField) {\n const validator: CustomValidator = (value: number, helpers) => {\n const { options, schema } = component\n\n const { customValidationMessage: custom } = options\n const { precision: limit } = schema\n\n if (!limit || limit <= 0) {\n return value\n }\n\n const validationSchema = joi\n .number()\n .precision(limit)\n .prefs({ convert: false })\n\n try {\n return joi.attempt(value, validationSchema)\n } catch {\n return custom\n ? helpers.message({ custom }, { limit })\n : helpers.error('number.precision', { limit })\n }\n }\n\n return validator\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,12 +1,10 @@
1
1
  import { type OsGridRefFieldComponent } from '@defra/forms-model';
2
- import type joi from 'joi';
3
2
  import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js';
4
3
  export declare class OsGridRefField extends LocationFieldBase {
5
4
  options: OsGridRefFieldComponent['options'];
6
5
  protected getValidationConfig(): {
7
6
  pattern: RegExp;
8
7
  patternErrorMessage: string;
9
- customValidation: (value: string, helpers: joi.CustomHelpers) => string | joi.ErrorReport;
10
8
  };
11
9
  protected getErrorTemplates(): {
12
10
  type: string;
@@ -3,39 +3,11 @@ export class OsGridRefField extends LocationFieldBase {
3
3
  getValidationConfig() {
4
4
  // Regex for OS grid references and parcel IDs
5
5
  // Validates specific valid OS grid letter combinations with:
6
- // - 6 digits (e.g., SD865005 or SD 865 005)
7
- // - 8 digits in 2 blocks of 4 (parcel ID) e.g., ST 6789 6789
8
- // - 10 digits in 2 blocks of 5 (OS grid reference) e.g., SO 12345 12345
9
- const osGridPattern = /^(?:[sn][a-hj-z]|[to][abfglmqrvw]|h[l-z]|j[lmqrvw])\s?(?:\d{3}\s?\d{3}|\d{4}\s?\d{4}|\d{5}\s?\d{5})$/i;
10
-
11
- // More permissive pattern for initial validation (allows spaces to be cleaned)
12
- const initialPattern = /^[A-Za-z]{2}[\d\s]*$/;
6
+ // - 2 letters & 6 digits (e.g., SD865005 or SD 865 005)
7
+ const pattern = /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\s?(([0-9]{3})\s?([0-9]{3}))$/;
13
8
  return {
14
- pattern: initialPattern,
15
- patternErrorMessage: `Enter a valid OS grid reference for ${this.title} like TQ123456`,
16
- customValidation: (value, helpers) => {
17
- // Strip spaces from the input for processing
18
- const cleanValue = value.replace(/\s/g, '');
19
- const letters = cleanValue.substring(0, 2);
20
- const numbers = cleanValue.substring(2);
21
-
22
- // Validate number length
23
- if (numbers.length !== 6 && numbers.length !== 8 && numbers.length !== 10) {
24
- return helpers.error('string.pattern.base');
25
- }
26
-
27
- // Format with spaces: XX 123 456, XX 1234 5678, or XX 12345 67890
28
- const halfLength = numbers.length / 2;
29
- const formattedValue = `${letters} ${numbers.substring(0, halfLength)} ${numbers.substring(halfLength)}`;
30
-
31
- // Validate the formatted value against the OS grid pattern
32
- if (!osGridPattern.test(formattedValue)) {
33
- return helpers.error('string.pattern.base');
34
- }
35
-
36
- // Return formatted value with spaces per GDS guidance
37
- return formattedValue;
38
- }
9
+ pattern,
10
+ patternErrorMessage: `Enter a valid OS grid reference for ${this.title} like TQ123456`
39
11
  };
40
12
  }
41
13
  getErrorTemplates() {
@@ -1 +1 @@
1
- {"version":3,"file":"OsGridRefField.js","names":["LocationFieldBase","OsGridRefField","getValidationConfig","osGridPattern","initialPattern","pattern","patternErrorMessage","title","customValidation","value","helpers","cleanValue","replace","letters","substring","numbers","length","error","halfLength","formattedValue","test","getErrorTemplates","type","template","getAllPossibleErrors","instance","Object","create","prototype"],"sources":["../../../../../src/server/plugins/engine/components/OsGridRefField.ts"],"sourcesContent":["import { type OsGridRefFieldComponent } from '@defra/forms-model'\nimport type joi from 'joi'\n\nimport { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'\n\nexport class OsGridRefField extends LocationFieldBase {\n declare options: OsGridRefFieldComponent['options']\n\n protected getValidationConfig() {\n // Regex for OS grid references and parcel IDs\n // Validates specific valid OS grid letter combinations with:\n // - 6 digits (e.g., SD865005 or SD 865 005)\n // - 8 digits in 2 blocks of 4 (parcel ID) e.g., ST 6789 6789\n // - 10 digits in 2 blocks of 5 (OS grid reference) e.g., SO 12345 12345\n const osGridPattern =\n /^(?:[sn][a-hj-z]|[to][abfglmqrvw]|h[l-z]|j[lmqrvw])\\s?(?:\\d{3}\\s?\\d{3}|\\d{4}\\s?\\d{4}|\\d{5}\\s?\\d{5})$/i\n\n // More permissive pattern for initial validation (allows spaces to be cleaned)\n const initialPattern = /^[A-Za-z]{2}[\\d\\s]*$/\n\n return {\n pattern: initialPattern,\n patternErrorMessage: `Enter a valid OS grid reference for ${this.title} like TQ123456`,\n customValidation: (value: string, helpers: joi.CustomHelpers) => {\n // Strip spaces from the input for processing\n const cleanValue = value.replace(/\\s/g, '')\n const letters = cleanValue.substring(0, 2)\n const numbers = cleanValue.substring(2)\n\n // Validate number length\n if (\n numbers.length !== 6 &&\n numbers.length !== 8 &&\n numbers.length !== 10\n ) {\n return helpers.error('string.pattern.base')\n }\n\n // Format with spaces: XX 123 456, XX 1234 5678, or XX 12345 67890\n const halfLength = numbers.length / 2\n const formattedValue = `${letters} ${numbers.substring(0, halfLength)} ${numbers.substring(halfLength)}`\n\n // Validate the formatted value against the OS grid pattern\n if (!osGridPattern.test(formattedValue)) {\n return helpers.error('string.pattern.base')\n }\n\n // Return formatted value with spaces per GDS guidance\n return formattedValue\n }\n }\n }\n\n protected getErrorTemplates() {\n return [\n {\n type: 'pattern',\n template:\n 'Enter a valid OS grid reference for [short description] like TQ123456'\n }\n ]\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors() {\n const instance = Object.create(OsGridRefField.prototype) as OsGridRefField\n return instance.getAllPossibleErrors()\n }\n}\n"],"mappings":"AAGA,SAASA,iBAAiB;AAE1B,OAAO,MAAMC,cAAc,SAASD,iBAAiB,CAAC;EAG1CE,mBAAmBA,CAAA,EAAG;IAC9B;IACA;IACA;IACA;IACA;IACA,MAAMC,aAAa,GACjB,uGAAuG;;IAEzG;IACA,MAAMC,cAAc,GAAG,sBAAsB;IAE7C,OAAO;MACLC,OAAO,EAAED,cAAc;MACvBE,mBAAmB,EAAE,uCAAuC,IAAI,CAACC,KAAK,gBAAgB;MACtFC,gBAAgB,EAAEA,CAACC,KAAa,EAAEC,OAA0B,KAAK;QAC/D;QACA,MAAMC,UAAU,GAAGF,KAAK,CAACG,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QAC3C,MAAMC,OAAO,GAAGF,UAAU,CAACG,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1C,MAAMC,OAAO,GAAGJ,UAAU,CAACG,SAAS,CAAC,CAAC,CAAC;;QAEvC;QACA,IACEC,OAAO,CAACC,MAAM,KAAK,CAAC,IACpBD,OAAO,CAACC,MAAM,KAAK,CAAC,IACpBD,OAAO,CAACC,MAAM,KAAK,EAAE,EACrB;UACA,OAAON,OAAO,CAACO,KAAK,CAAC,qBAAqB,CAAC;QAC7C;;QAEA;QACA,MAAMC,UAAU,GAAGH,OAAO,CAACC,MAAM,GAAG,CAAC;QACrC,MAAMG,cAAc,GAAG,GAAGN,OAAO,IAAIE,OAAO,CAACD,SAAS,CAAC,CAAC,EAAEI,UAAU,CAAC,IAAIH,OAAO,CAACD,SAAS,CAACI,UAAU,CAAC,EAAE;;QAExG;QACA,IAAI,CAACf,aAAa,CAACiB,IAAI,CAACD,cAAc,CAAC,EAAE;UACvC,OAAOT,OAAO,CAACO,KAAK,CAAC,qBAAqB,CAAC;QAC7C;;QAEA;QACA,OAAOE,cAAc;MACvB;IACF,CAAC;EACH;EAEUE,iBAAiBA,CAAA,EAAG;IAC5B,OAAO,CACL;MACEC,IAAI,EAAE,SAAS;MACfC,QAAQ,EACN;IACJ,CAAC,CACF;EACH;;EAEA;AACF;AACA;EACE,OAAOC,oBAAoBA,CAAA,EAAG;IAC5B,MAAMC,QAAQ,GAAGC,MAAM,CAACC,MAAM,CAAC1B,cAAc,CAAC2B,SAAS,CAAmB;IAC1E,OAAOH,QAAQ,CAACD,oBAAoB,CAAC,CAAC;EACxC;AACF","ignoreList":[]}
1
+ {"version":3,"file":"OsGridRefField.js","names":["LocationFieldBase","OsGridRefField","getValidationConfig","pattern","patternErrorMessage","title","getErrorTemplates","type","template","getAllPossibleErrors","instance","Object","create","prototype"],"sources":["../../../../../src/server/plugins/engine/components/OsGridRefField.ts"],"sourcesContent":["import { type OsGridRefFieldComponent } from '@defra/forms-model'\n\nimport { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'\n\nexport class OsGridRefField extends LocationFieldBase {\n declare options: OsGridRefFieldComponent['options']\n\n protected getValidationConfig() {\n // Regex for OS grid references and parcel IDs\n // Validates specific valid OS grid letter combinations with:\n // - 2 letters & 6 digits (e.g., SD865005 or SD 865 005)\n const pattern =\n /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\\s?(([0-9]{3})\\s?([0-9]{3}))$/\n\n return {\n pattern,\n patternErrorMessage: `Enter a valid OS grid reference for ${this.title} like TQ123456`\n }\n }\n\n protected getErrorTemplates() {\n return [\n {\n type: 'pattern',\n template:\n 'Enter a valid OS grid reference for [short description] like TQ123456'\n }\n ]\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors() {\n const instance = Object.create(OsGridRefField.prototype) as OsGridRefField\n return instance.getAllPossibleErrors()\n }\n}\n"],"mappings":"AAEA,SAASA,iBAAiB;AAE1B,OAAO,MAAMC,cAAc,SAASD,iBAAiB,CAAC;EAG1CE,mBAAmBA,CAAA,EAAG;IAC9B;IACA;IACA;IACA,MAAMC,OAAO,GACX,qIAAqI;IAEvI,OAAO;MACLA,OAAO;MACPC,mBAAmB,EAAE,uCAAuC,IAAI,CAACC,KAAK;IACxE,CAAC;EACH;EAEUC,iBAAiBA,CAAA,EAAG;IAC5B,OAAO,CACL;MACEC,IAAI,EAAE,SAAS;MACfC,QAAQ,EACN;IACJ,CAAC,CACF;EACH;;EAEA;AACF;AACA;EACE,OAAOC,oBAAoBA,CAAA,EAAG;IAC5B,MAAMC,QAAQ,GAAGC,MAAM,CAACC,MAAM,CAACX,cAAc,CAACY,SAAS,CAAmB;IAC1E,OAAOH,QAAQ,CAACD,oBAAoB,CAAC,CAAC;EACxC;AACF","ignoreList":[]}
@@ -1,5 +1,5 @@
1
1
  import { SchemaVersion, type ComponentDef, type ConditionWrapper, type ConditionWrapperV2, type ConditionsModelData, type Engine, type FormDefinition, type List, type Page } from '@defra/forms-model';
2
- import { Parser, type Value } from 'expr-eval';
2
+ import { Parser, type Value } from 'expr-eval-fork';
3
3
  import joi from 'joi';
4
4
  import { type Component } from '~/src/server/plugins/engine/components/helpers/components.js';
5
5
  import { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js';
@@ -47,7 +47,7 @@ export declare class FormModel {
47
47
  */
48
48
  makeCondition(condition: ConditionWrapper): ExecutableCondition;
49
49
  toConditionContext(evaluationState: FormState, conditions: Partial<Record<string, ExecutableCondition>>): Extract<Value, Record<string, Value>>;
50
- toConditionExpression(value: ConditionsModelData, parser: Parser): import("expr-eval").Expression;
50
+ toConditionExpression(value: ConditionsModelData, parser: Parser): import("expr-eval-fork").Expression;
51
51
  getList(nameOrId: string): List | undefined;
52
52
  /**
53
53
  * Form context for the current page
@@ -1,6 +1,6 @@
1
1
  import { ComponentType, ConditionsModel, ControllerPath, ControllerType, SchemaVersion, convertConditionWrapperFromV2, formDefinitionSchema, formDefinitionV2Schema, generateConditionAlias, hasComponents, hasRepeater, isConditionWrapperV2, yesNoListId, yesNoListName } from '@defra/forms-model';
2
2
  import { add, format } from 'date-fns';
3
- import { Parser } from 'expr-eval';
3
+ import { Parser } from 'expr-eval-fork';
4
4
  import joi from 'joi';
5
5
  import { createLogger } from "../../../common/helpers/logging/logger.js";
6
6
  import "../components/YesNoField.js";
@@ -1 +1 @@
1
- {"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","values","basePath","versionNumber","ordnanceSurveyApiKey","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","emptyState","freeze","getContextValueFromState","key","keys","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","SaveAndExit","startsWith","External","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type Engine,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: {\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.ordnanceSurveyApiKey = options.ordnanceSurveyApiKey\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // Initialise `evaluationState` for all keys using empty state.\n // This is because the current condition evaluation library (eval-expr)\n // will throw if an expression uses a key that is undefined.\n const emptyState = Object.freeze({})\n\n for (const page of this.pages) {\n const { collection, pageDef } = page\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(emptyState)\n )\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action &&\n ![FormAction.Validate, FormAction.SaveAndExit].includes(action) &&\n !action.startsWith(FormAction.External))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","values","basePath","versionNumber","ordnanceSurveyApiKey","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","emptyState","freeze","getContextValueFromState","key","keys","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","SaveAndExit","startsWith","External","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type Engine,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval-fork'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: {\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.ordnanceSurveyApiKey = options.ordnanceSurveyApiKey\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // Initialise `evaluationState` for all keys using empty state.\n // This is because the current condition evaluation library (eval-expr)\n // will throw if an expression uses a key that is undefined.\n const emptyState = Object.freeze({})\n\n for (const page of this.pages) {\n const { collection, pageDef } = page\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(emptyState)\n )\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action &&\n ![FormAction.Validate, FormAction.SaveAndExit].includes(action) &&\n !action.startsWith(FormAction.External))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,5 +1,5 @@
1
1
  import { type ConditionWrapper, type Section } from '@defra/forms-model';
2
- import { type Expression } from 'expr-eval';
2
+ import { type Expression } from 'expr-eval-fork';
3
3
  import { type Field } from '~/src/server/plugins/engine/components/helpers/components.js';
4
4
  import { type RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js';
5
5
  import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js';
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/models/types.ts"],"sourcesContent":["import {\n type ConditionWrapper,\n type FormComponentsDef,\n type Section\n} from '@defra/forms-model'\nimport { type Expression } from 'expr-eval'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FormState,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\n\nexport type ExecutableCondition = ConditionWrapper & {\n expr: Expression\n fn: (evaluationState: FormState) => boolean\n}\n\n/**\n * Used to render a row on a Summary List (check your answers)\n */\nexport interface DetailItemBase {\n /**\n * Name of the component defined in the JSON\n * @see {@link FormComponentsDef.name}\n */\n name: string\n\n /**\n * Field label, used for change link visually hidden text\n * @see {@link FormComponentsDef.title}\n */\n label: string\n\n /**\n * Field change link\n */\n href: string\n\n /**\n * Form submission state (or repeat state for sub items)\n */\n state: FormState\n\n /**\n * Field submission state error, used to flag unanswered questions\n * Shown as 'Complete all unanswered questions before submitting the form'\n */\n error?: FormSubmissionError\n}\n\nexport interface DetailItemField extends DetailItemBase {\n /**\n * Field page controller instance\n */\n page: Exclude<PageControllerClass, RepeatPageController>\n\n /**\n * Check answers summary list key\n * For example, 'Date of birth'\n */\n title: string\n\n /**\n * Check answers summary list value, formatted by {@link getAnswer}\n * For example, date fields formatted as '25 December 2022'\n */\n value: string\n\n /**\n * Field component instance\n */\n field: Field\n}\n\nexport interface DetailItemRepeat extends DetailItemBase {\n /**\n * Repeat page controller instance\n */\n page: RepeatPageController\n\n /**\n * Check answers summary list key\n * For example, 'Pizza' or 'Pizza added'\n */\n title: string\n\n /**\n * Check answers summary list value\n * For example, 'You added 2 Pizzas'\n */\n value: string\n\n /**\n * Repeater field detail items\n */\n subItems: DetailItemField[][]\n}\n\nexport type DetailItem = DetailItemField | DetailItemRepeat\n\n/**\n * Used to render a row on a Summary List (check your answers)\n */\nexport interface Detail {\n name?: Section['name']\n title?: Section['title']\n items: DetailItem[]\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/models/types.ts"],"sourcesContent":["import {\n type ConditionWrapper,\n type FormComponentsDef,\n type Section\n} from '@defra/forms-model'\nimport { type Expression } from 'expr-eval-fork'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FormState,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\n\nexport type ExecutableCondition = ConditionWrapper & {\n expr: Expression\n fn: (evaluationState: FormState) => boolean\n}\n\n/**\n * Used to render a row on a Summary List (check your answers)\n */\nexport interface DetailItemBase {\n /**\n * Name of the component defined in the JSON\n * @see {@link FormComponentsDef.name}\n */\n name: string\n\n /**\n * Field label, used for change link visually hidden text\n * @see {@link FormComponentsDef.title}\n */\n label: string\n\n /**\n * Field change link\n */\n href: string\n\n /**\n * Form submission state (or repeat state for sub items)\n */\n state: FormState\n\n /**\n * Field submission state error, used to flag unanswered questions\n * Shown as 'Complete all unanswered questions before submitting the form'\n */\n error?: FormSubmissionError\n}\n\nexport interface DetailItemField extends DetailItemBase {\n /**\n * Field page controller instance\n */\n page: Exclude<PageControllerClass, RepeatPageController>\n\n /**\n * Check answers summary list key\n * For example, 'Date of birth'\n */\n title: string\n\n /**\n * Check answers summary list value, formatted by {@link getAnswer}\n * For example, date fields formatted as '25 December 2022'\n */\n value: string\n\n /**\n * Field component instance\n */\n field: Field\n}\n\nexport interface DetailItemRepeat extends DetailItemBase {\n /**\n * Repeat page controller instance\n */\n page: RepeatPageController\n\n /**\n * Check answers summary list key\n * For example, 'Pizza' or 'Pizza added'\n */\n title: string\n\n /**\n * Check answers summary list value\n * For example, 'You added 2 Pizzas'\n */\n value: string\n\n /**\n * Repeater field detail items\n */\n subItems: DetailItemField[][]\n}\n\nexport type DetailItem = DetailItemField | DetailItemRepeat\n\n/**\n * Used to render a row on a Summary List (check your answers)\n */\nexport interface Detail {\n name?: Section['name']\n title?: Section['title']\n items: DetailItem[]\n}\n"],"mappings":"","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "4.0.11",
3
+ "version": "4.0.13",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -95,7 +95,7 @@
95
95
  "convict": "^6.2.4",
96
96
  "date-fns": "^4.1.0",
97
97
  "dotenv": "^17.2.1",
98
- "expr-eval": "^2.0.2",
98
+ "expr-eval-fork": "^3.0.0",
99
99
  "govuk-frontend": "^5.11.1",
100
100
  "hapi-pino": "^12.1.0",
101
101
  "hapi-pulse": "^3.0.1",
@@ -203,7 +203,7 @@ pages:
203
203
  path: '/how-many-members-of-staff-will-look-after-the-unicorns'
204
204
  section: susaYr
205
205
  next:
206
- - path: '/summary'
206
+ - path: '/declaration'
207
207
  components:
208
208
  - name: zhJMaM
209
209
  options:
@@ -219,7 +219,7 @@ pages:
219
219
  controller: FileUploadPageController
220
220
  section: Regnsa
221
221
  next:
222
- - path: '/declaration'
222
+ - path: '/how-many-unicorns-do-you-expect-to-breed-each-year'
223
223
  components:
224
224
  - name: dLzALM
225
225
  title: Documents
@@ -556,14 +556,7 @@ describe('EastingNorthingField', () => {
556
556
  easting: 12345.5,
557
557
  northing: 1234567
558
558
  }),
559
- // Two errors expected: decimal input triggers both integer validation
560
- // and length validation ('12345.5' is 7 chars, max is 6)
561
559
  errors: [
562
- expect.objectContaining({
563
- text: expect.stringMatching(
564
- /Easting for .* must be between 1 and 6 digits/
565
- )
566
- }),
567
560
  expect.objectContaining({
568
561
  text: expect.stringMatching(
569
562
  /Easting for .* must be between 1 and 6 digits/
@@ -582,14 +575,7 @@ describe('EastingNorthingField', () => {
582
575
  easting: 12345,
583
576
  northing: 1234567.5
584
577
  }),
585
- // Two errors expected: decimal input triggers both integer validation
586
- // and length validation ('1234567.5' is 9 chars, max is 7)
587
578
  errors: [
588
- expect.objectContaining({
589
- text: expect.stringMatching(
590
- /Northing for .* must be between 1 and 7 digits/
591
- )
592
- }),
593
579
  expect.objectContaining({
594
580
  text: expect.stringMatching(
595
581
  /Northing for .* must be between 1 and 7 digits/
@@ -32,18 +32,6 @@ const DEFAULT_EASTING_MAX = 700000
32
32
  const DEFAULT_NORTHING_MIN = 0
33
33
  const DEFAULT_NORTHING_MAX = 1300000
34
34
 
35
- // Easting length constraints (integer values only, no decimals)
36
- // Min: 1 char for values like "0" or single digit values
37
- // Max: 6 chars for values up to 700000 (British National Grid easting limit)
38
- const EASTING_MIN_LENGTH = 1
39
- const EASTING_MAX_LENGTH = 6
40
-
41
- // Northing length constraints (integer values only, no decimals)
42
- // Min: 1 char for values like "0" or single digit values
43
- // Max: 7 chars for values up to 1300000 (British National Grid northing limit)
44
- const NORTHING_MIN_LENGTH = 1
45
- const NORTHING_MAX_LENGTH = 7
46
-
47
35
  export class EastingNorthingField extends FormComponent {
48
36
  declare options: EastingNorthingFieldComponent['options']
49
37
  declare formSchema: ObjectSchema<FormPayload>
@@ -73,9 +61,7 @@ export class EastingNorthingField extends FormComponent {
73
61
  'number.max': `{{#label}} for ${this.title} must be between ${eastingMin} and {{#limit}}`,
74
62
  'number.precision': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
75
63
  'number.integer': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
76
- 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
77
- 'number.minLength': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
78
- 'number.maxLength': `{{#label}} for ${this.title} must be between 1 and 6 digits`
64
+ 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 6 digits`
79
65
  })
80
66
 
81
67
  const northingValidationMessages: LanguageMessages =
@@ -86,9 +72,7 @@ export class EastingNorthingField extends FormComponent {
86
72
  'number.max': `{{#label}} for ${this.title} must be between ${northingMin} and {{#limit}}`,
87
73
  'number.precision': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
88
74
  'number.integer': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
89
- 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
90
- 'number.minLength': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
91
- 'number.maxLength': `{{#label}} for ${this.title} must be between 1 and 7 digits`
75
+ 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 7 digits`
92
76
  })
93
77
 
94
78
  this.collection = new ComponentCollection(
@@ -100,9 +84,7 @@ export class EastingNorthingField extends FormComponent {
100
84
  schema: {
101
85
  min: eastingMin,
102
86
  max: eastingMax,
103
- precision: 0,
104
- minLength: EASTING_MIN_LENGTH,
105
- maxLength: EASTING_MAX_LENGTH
87
+ precision: 0
106
88
  },
107
89
  options: {
108
90
  required: isRequired,
@@ -118,9 +100,7 @@ export class EastingNorthingField extends FormComponent {
118
100
  schema: {
119
101
  min: northingMin,
120
102
  max: northingMax,
121
- precision: 0,
122
- minLength: NORTHING_MIN_LENGTH,
123
- maxLength: NORTHING_MAX_LENGTH
103
+ precision: 0
124
104
  },
125
105
  options: {
126
106
  required: isRequired,
@@ -149,7 +149,7 @@ describe('LatLongField', () => {
149
149
 
150
150
  const result2 = collection.validate(
151
151
  getFormData({
152
- latitude: '49.1',
152
+ latitude: '50.5',
153
153
  longitude: '-8.9'
154
154
  })
155
155
  )
@@ -578,15 +578,7 @@ describe('LatLongField', () => {
578
578
  value: getFormData({
579
579
  latitude: 52,
580
580
  longitude: -1
581
- }),
582
- errors: [
583
- expect.objectContaining({
584
- text: 'Latitude must have at least 1 decimal place'
585
- }),
586
- expect.objectContaining({
587
- text: 'Longitude must have at least 1 decimal place'
588
- })
589
- ]
581
+ })
590
582
  }
591
583
  },
592
584
  {
@@ -619,7 +611,6 @@ describe('LatLongField', () => {
619
611
  description: 'Length and precision validation',
620
612
  component: createLatLongComponent(),
621
613
  assertions: [
622
- // Latitude too short
623
614
  {
624
615
  input: getFormData({
625
616
  latitude: '52',
@@ -629,12 +620,7 @@ describe('LatLongField', () => {
629
620
  value: getFormData({
630
621
  latitude: 52,
631
622
  longitude: -1.5
632
- }),
633
- errors: [
634
- expect.objectContaining({
635
- text: 'Latitude must have at least 1 decimal place'
636
- })
637
- ]
623
+ })
638
624
  }
639
625
  },
640
626
  // Latitude too long
@@ -655,7 +641,6 @@ describe('LatLongField', () => {
655
641
  ]
656
642
  }
657
643
  },
658
- // Longitude too short
659
644
  {
660
645
  input: getFormData({
661
646
  latitude: '52.1',
@@ -665,12 +650,7 @@ describe('LatLongField', () => {
665
650
  value: getFormData({
666
651
  latitude: 52.1,
667
652
  longitude: -1
668
- }),
669
- errors: [
670
- expect.objectContaining({
671
- text: 'Longitude must have at least 1 decimal place'
672
- })
673
- ]
653
+ })
674
654
  }
675
655
  },
676
656
  // Longitude too long