@defra/forms-engine-plugin 4.0.11 → 4.0.12
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.
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +2 -2
- package/.server/server/plugins/engine/components/EastingNorthingField.js +4 -24
- package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
- package/.server/server/plugins/engine/components/LatLongField.js +8 -31
- package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
- package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +1 -3
- package/.server/server/plugins/engine/components/LocationFieldBase.js +1 -8
- package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +0 -2
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +7 -18
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/NumberField.d.ts +0 -18
- package/.server/server/plugins/engine/components/NumberField.js +2 -103
- package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/OsGridRefField.d.ts +0 -2
- package/.server/server/plugins/engine/components/OsGridRefField.js +4 -32
- package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
- package/package.json +1 -1
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +2 -2
- package/src/server/plugins/engine/components/EastingNorthingField.test.ts +0 -14
- package/src/server/plugins/engine/components/EastingNorthingField.ts +4 -24
- package/src/server/plugins/engine/components/LatLongField.test.ts +4 -24
- package/src/server/plugins/engine/components/LatLongField.ts +8 -33
- package/src/server/plugins/engine/components/LocationFieldBase.ts +1 -15
- package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +22 -8
- package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +9 -20
- package/src/server/plugins/engine/components/NumberField.test.ts +12 -504
- package/src/server/plugins/engine/components/NumberField.ts +4 -133
- package/src/server/plugins/engine/components/OsGridRefField.test.ts +11 -33
- package/src/server/plugins/engine/components/OsGridRefField.ts +5 -38
|
@@ -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
|
-
// - 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
|
|
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","
|
|
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":[]}
|
package/package.json
CHANGED
|
@@ -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: '/
|
|
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: '/
|
|
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: '
|
|
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
|
|
@@ -26,19 +26,6 @@ import { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'
|
|
|
26
26
|
// Precision constants
|
|
27
27
|
// UK latitude/longitude requires high precision for accurate location (within ~11mm)
|
|
28
28
|
const DECIMAL_PRECISION = 7 // 7 decimal places
|
|
29
|
-
const MIN_DECIMAL_PLACES = 1 // At least 1 decimal place required
|
|
30
|
-
|
|
31
|
-
// Latitude length constraints
|
|
32
|
-
// Min: 3 chars for values like "52.1" (2 digits + decimal + 1 decimal place)
|
|
33
|
-
// Max: 10 chars for values like "59.1234567" (2 digits + decimal + 7 decimal places)
|
|
34
|
-
const LATITUDE_MIN_LENGTH = 3
|
|
35
|
-
const LATITUDE_MAX_LENGTH = 10
|
|
36
|
-
|
|
37
|
-
// Longitude length constraints
|
|
38
|
-
// Min: 2 chars for values like "-1" or single digit with decimal (needs min decimal places)
|
|
39
|
-
// Max: 10 chars for values like "-1.1234567" (minus + 1 digit + decimal + 7 decimal places)
|
|
40
|
-
const LONGITUDE_MIN_LENGTH = 2
|
|
41
|
-
const LONGITUDE_MAX_LENGTH = 10
|
|
42
29
|
|
|
43
30
|
export class LatLongField extends FormComponent {
|
|
44
31
|
declare options: LatLongFieldComponent['options']
|
|
@@ -57,10 +44,10 @@ export class LatLongField extends FormComponent {
|
|
|
57
44
|
const isRequired = options.required !== false
|
|
58
45
|
|
|
59
46
|
// Read schema values from def.schema with fallback defaults
|
|
60
|
-
const latitudeMin = schema?.latitude?.min ?? 49
|
|
61
|
-
const latitudeMax = schema?.latitude?.max ?? 60
|
|
62
|
-
const longitudeMin = schema?.longitude?.min ?? -
|
|
63
|
-
const longitudeMax = schema?.longitude?.max ??
|
|
47
|
+
const latitudeMin = schema?.latitude?.min ?? 49.85
|
|
48
|
+
const latitudeMax = schema?.latitude?.max ?? 60.859
|
|
49
|
+
const longitudeMin = schema?.longitude?.min ?? -13.687
|
|
50
|
+
const longitudeMax = schema?.longitude?.max ?? 1.767
|
|
64
51
|
|
|
65
52
|
const customValidationMessages: LanguageMessages =
|
|
66
53
|
convertToLanguageMessages({
|
|
@@ -68,8 +55,6 @@ export class LatLongField extends FormComponent {
|
|
|
68
55
|
'number.base': messageTemplate.objectMissing,
|
|
69
56
|
'number.precision':
|
|
70
57
|
'{{#label}} must have no more than 7 decimal places',
|
|
71
|
-
'number.minPrecision':
|
|
72
|
-
'{{#label}} must have at least {{#minPrecision}} decimal place',
|
|
73
58
|
'number.unsafe': '{{#label}} must be a valid number'
|
|
74
59
|
})
|
|
75
60
|
|
|
@@ -77,18 +62,14 @@ export class LatLongField extends FormComponent {
|
|
|
77
62
|
...customValidationMessages,
|
|
78
63
|
'number.base': `Enter a valid latitude for ${this.title} like 51.519450`,
|
|
79
64
|
'number.min': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`,
|
|
80
|
-
'number.max': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}
|
|
81
|
-
'number.minLength': `Latitude for ${this.title} must be between 3 and 10 characters`,
|
|
82
|
-
'number.maxLength': `Latitude for ${this.title} must be between 3 and 10 characters`
|
|
65
|
+
'number.max': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`
|
|
83
66
|
})
|
|
84
67
|
|
|
85
68
|
const longitudeMessages: LanguageMessages = convertToLanguageMessages({
|
|
86
69
|
...customValidationMessages,
|
|
87
70
|
'number.base': `Enter a valid longitude for ${this.title} like -0.127758`,
|
|
88
71
|
'number.min': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`,
|
|
89
|
-
'number.max': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}
|
|
90
|
-
'number.minLength': `Longitude for ${this.title} must be between 2 and 10 characters`,
|
|
91
|
-
'number.maxLength': `Longitude for ${this.title} must be between 2 and 10 characters`
|
|
72
|
+
'number.max': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`
|
|
92
73
|
})
|
|
93
74
|
|
|
94
75
|
this.collection = new ComponentCollection(
|
|
@@ -100,10 +81,7 @@ export class LatLongField extends FormComponent {
|
|
|
100
81
|
schema: {
|
|
101
82
|
min: latitudeMin,
|
|
102
83
|
max: latitudeMax,
|
|
103
|
-
precision: DECIMAL_PRECISION
|
|
104
|
-
minPrecision: MIN_DECIMAL_PLACES,
|
|
105
|
-
minLength: LATITUDE_MIN_LENGTH,
|
|
106
|
-
maxLength: LATITUDE_MAX_LENGTH
|
|
84
|
+
precision: DECIMAL_PRECISION
|
|
107
85
|
},
|
|
108
86
|
options: {
|
|
109
87
|
required: isRequired,
|
|
@@ -120,10 +98,7 @@ export class LatLongField extends FormComponent {
|
|
|
120
98
|
schema: {
|
|
121
99
|
min: longitudeMin,
|
|
122
100
|
max: longitudeMax,
|
|
123
|
-
precision: DECIMAL_PRECISION
|
|
124
|
-
minPrecision: MIN_DECIMAL_PLACES,
|
|
125
|
-
minLength: LONGITUDE_MIN_LENGTH,
|
|
126
|
-
maxLength: LONGITUDE_MAX_LENGTH
|
|
101
|
+
precision: DECIMAL_PRECISION
|
|
127
102
|
},
|
|
128
103
|
options: {
|
|
129
104
|
required: isRequired,
|
|
@@ -28,11 +28,6 @@ interface LocationFieldOptions {
|
|
|
28
28
|
interface ValidationConfig {
|
|
29
29
|
pattern: RegExp
|
|
30
30
|
patternErrorMessage: string
|
|
31
|
-
customValidation?: (
|
|
32
|
-
value: string,
|
|
33
|
-
helpers: joi.CustomHelpers
|
|
34
|
-
) => string | joi.ErrorReport
|
|
35
|
-
additionalMessages?: LanguageMessages
|
|
36
31
|
}
|
|
37
32
|
|
|
38
33
|
/**
|
|
@@ -71,14 +66,9 @@ export abstract class LocationFieldBase extends FormComponent {
|
|
|
71
66
|
.required()
|
|
72
67
|
.pattern(config.pattern)
|
|
73
68
|
.messages({
|
|
74
|
-
'string.pattern.base': config.patternErrorMessage
|
|
75
|
-
...config.additionalMessages
|
|
69
|
+
'string.pattern.base': config.patternErrorMessage
|
|
76
70
|
})
|
|
77
71
|
|
|
78
|
-
if (config.customValidation) {
|
|
79
|
-
formSchema = formSchema.custom(config.customValidation)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
72
|
if (locationOptions.required === false) {
|
|
83
73
|
formSchema = formSchema.allow('')
|
|
84
74
|
}
|
|
@@ -91,10 +81,6 @@ export abstract class LocationFieldBase extends FormComponent {
|
|
|
91
81
|
'string.pattern.base'
|
|
92
82
|
]
|
|
93
83
|
|
|
94
|
-
if (config.additionalMessages) {
|
|
95
|
-
messageKeys.push(...Object.keys(config.additionalMessages))
|
|
96
|
-
}
|
|
97
|
-
|
|
98
84
|
const messages = messageKeys.reduce<LanguageMessages>((acc, key) => {
|
|
99
85
|
acc[key] = message
|
|
100
86
|
return acc
|
|
@@ -99,13 +99,27 @@ describe('NationalGridFieldNumberField', () => {
|
|
|
99
99
|
})
|
|
100
100
|
|
|
101
101
|
it('accepts valid values', () => {
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
const
|
|
102
|
+
// Test 8-digit parcel ID format (2x4)
|
|
103
|
+
const result1 = collection.validate(getFormData('TQ12345678'))
|
|
104
|
+
const result2 = collection.validate(getFormData('TQ 1234 5678'))
|
|
105
|
+
|
|
106
|
+
// Test 10-digit OS grid reference format (2x5)
|
|
107
|
+
const result3 = collection.validate(getFormData('SU1234567890'))
|
|
108
|
+
const result4 = collection.validate(getFormData('SU 12345 67890'))
|
|
109
|
+
|
|
110
|
+
expect(result1.errors).toBeUndefined()
|
|
111
|
+
expect(result2.errors).toBeUndefined()
|
|
112
|
+
expect(result3.errors).toBeUndefined()
|
|
113
|
+
expect(result4.errors).toBeUndefined()
|
|
114
|
+
|
|
115
|
+
// Test case-insensitive
|
|
116
|
+
const result5 = collection.validate(getFormData('nt12345678'))
|
|
105
117
|
|
|
106
118
|
expect(result1.errors).toBeUndefined()
|
|
107
119
|
expect(result2.errors).toBeUndefined()
|
|
108
120
|
expect(result3.errors).toBeUndefined()
|
|
121
|
+
expect(result4.errors).toBeUndefined()
|
|
122
|
+
expect(result5.errors).toBeUndefined()
|
|
109
123
|
})
|
|
110
124
|
|
|
111
125
|
it('formats values with spaces per GDS guidance', () => {
|
|
@@ -114,8 +128,8 @@ describe('NationalGridFieldNumberField', () => {
|
|
|
114
128
|
const result3 = collection.validate(getFormData('NG12345,678'))
|
|
115
129
|
|
|
116
130
|
expect(result1.value.myComponent).toBe('NG 1234 5678')
|
|
117
|
-
expect(result2.value.myComponent).toBe('
|
|
118
|
-
expect(result3.value.myComponent).toBe('
|
|
131
|
+
expect(result2.value.myComponent).toBe('NG12345678')
|
|
132
|
+
expect(result3.value.myComponent).toBe('NG12345,678')
|
|
119
133
|
})
|
|
120
134
|
|
|
121
135
|
it('adds errors for empty value', () => {
|
|
@@ -258,15 +272,15 @@ describe('NationalGridFieldNumberField', () => {
|
|
|
258
272
|
assertions: [
|
|
259
273
|
{
|
|
260
274
|
input: getFormData(' NG12345678'),
|
|
261
|
-
output: { value: getFormData('
|
|
275
|
+
output: { value: getFormData('NG12345678') }
|
|
262
276
|
},
|
|
263
277
|
{
|
|
264
278
|
input: getFormData('NG12345678 '),
|
|
265
|
-
output: { value: getFormData('
|
|
279
|
+
output: { value: getFormData('NG12345678') }
|
|
266
280
|
},
|
|
267
281
|
{
|
|
268
282
|
input: getFormData(' NG12345678 \n\n'),
|
|
269
|
-
output: { value: getFormData('
|
|
283
|
+
output: { value: getFormData('NG12345678') }
|
|
270
284
|
}
|
|
271
285
|
]
|
|
272
286
|
},
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type NationalGridFieldNumberFieldComponent } from '@defra/forms-model'
|
|
2
|
-
import type joi from 'joi'
|
|
3
2
|
|
|
4
3
|
import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'
|
|
5
4
|
|
|
@@ -7,26 +6,16 @@ export class NationalGridFieldNumberField extends LocationFieldBase {
|
|
|
7
6
|
declare options: NationalGridFieldNumberFieldComponent['options']
|
|
8
7
|
|
|
9
8
|
protected getValidationConfig() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const cleanValue = value.replace(/[\s,]/g, '')
|
|
17
|
-
|
|
18
|
-
// Check if it matches the exact pattern after cleaning
|
|
19
|
-
if (!/^[A-Z]{2}\d{8}$/i.test(cleanValue)) {
|
|
20
|
-
return helpers.error('string.pattern.base')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Format with spaces per GDS guidance: NG 1234 5678
|
|
24
|
-
const letters = cleanValue.substring(0, 2)
|
|
25
|
-
const numbers = cleanValue.substring(2)
|
|
26
|
-
const formattedValue = `${letters} ${numbers.substring(0, 4)} ${numbers.substring(4)}`
|
|
9
|
+
// Regex for OS grid references and parcel IDs
|
|
10
|
+
// Validates specific valid OS grid letter combinations with:
|
|
11
|
+
// - 2 letters & 8 digits in 2 blocks of 4 (parcel ID) e.g., ST 6789 6789
|
|
12
|
+
// - 2 letters & 10 digits in 2 blocks of 5 (OS grid reference) e.g., SO 12345 12345
|
|
13
|
+
const pattern =
|
|
14
|
+
/^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\s?(([0-9]{4})\s?([0-9]{4})|([0-9]{5})\s?([0-9]{5}))$/
|
|
27
15
|
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
return {
|
|
17
|
+
pattern,
|
|
18
|
+
patternErrorMessage: `Enter a valid National Grid field number for ${this.title} like NG 1234 5678`
|
|
30
19
|
}
|
|
31
20
|
}
|
|
32
21
|
|