@defra/forms-engine-plugin 4.0.23 → 4.0.24

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 (28) hide show
  1. package/.server/server/plugins/engine/components/EastingNorthingField.js +7 -6
  2. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  3. package/.server/server/plugins/engine/components/LatLongField.js +7 -6
  4. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
  5. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +4 -4
  6. package/.server/server/plugins/engine/components/LocationFieldBase.js +3 -2
  7. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  8. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +3 -3
  9. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +5 -3
  10. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
  11. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +3 -3
  12. package/.server/server/plugins/engine/components/OsGridRefField.js +5 -3
  13. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
  14. package/.server/server/plugins/engine/components/helpers/index.d.ts +10 -0
  15. package/.server/server/plugins/engine/components/helpers/index.js +18 -0
  16. package/.server/server/plugins/engine/components/helpers/index.js.map +1 -1
  17. package/package.json +1 -1
  18. package/src/server/plugins/engine/components/EastingNorthingField.test.ts +30 -1
  19. package/src/server/plugins/engine/components/EastingNorthingField.ts +19 -6
  20. package/src/server/plugins/engine/components/LatLongField.test.ts +30 -1
  21. package/src/server/plugins/engine/components/LatLongField.ts +19 -6
  22. package/src/server/plugins/engine/components/LocationFieldBase.ts +11 -6
  23. package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +4 -4
  24. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +11 -4
  25. package/src/server/plugins/engine/components/OsGridRefField.test.ts +4 -4
  26. package/src/server/plugins/engine/components/OsGridRefField.ts +11 -3
  27. package/src/server/plugins/engine/components/helpers/helpers.test.ts +40 -0
  28. package/src/server/plugins/engine/components/helpers/index.ts +18 -0
@@ -4,6 +4,7 @@ import { ComponentCollection } from "./ComponentCollection.js";
4
4
  import { FormComponent, isFormState } from "./FormComponent.js";
5
5
  import { deduplicateErrorsByHref, getLocationFieldViewModel } from "./LocationFieldHelpers.js";
6
6
  import { NumberField } from "./NumberField.js";
7
+ import { createLowerFirstExpression } from "./helpers/index.js";
7
8
  import { messageTemplate } from "../pageControllers/validationOptions.js";
8
9
  import { convertToLanguageMessages } from "../../../utils/type-utils.js";
9
10
 
@@ -142,23 +143,23 @@ export class EastingNorthingField extends FormComponent {
142
143
  template: messageTemplate.required
143
144
  }, {
144
145
  type: 'eastingFormat',
145
- template: 'Easting for {{#title}} must be between 1 and 6 digits'
146
+ template: createLowerFirstExpression('Easting for {{lowerFirst(#title)}} must be between 1 and 6 digits')
146
147
  }, {
147
148
  type: 'northingFormat',
148
- template: 'Northing for {{#title}} must be between 1 and 7 digits'
149
+ template: createLowerFirstExpression('Northing for {{lowerFirst(#title)}} must be between 1 and 7 digits')
149
150
  }],
150
151
  advancedSettingsErrors: [{
151
152
  type: 'eastingMin',
152
- template: `Easting for {{#title}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
153
+ template: createLowerFirstExpression(`Easting for {{lowerFirst(#title)}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`)
153
154
  }, {
154
155
  type: 'eastingMax',
155
- template: `Easting for {{#title}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
156
+ template: createLowerFirstExpression(`Easting for {{lowerFirst(#title)}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`)
156
157
  }, {
157
158
  type: 'northingMin',
158
- template: `Northing for {{#title}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
159
+ template: createLowerFirstExpression(`Northing for {{lowerFirst(#title)}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`)
159
160
  }, {
160
161
  type: 'northingMax',
161
- template: `Northing for {{#title}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
162
+ template: createLowerFirstExpression(`Northing for {{lowerFirst(#title)}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`)
162
163
  }]
163
164
  };
164
165
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EastingNorthingField.js","names":["ComponentType","lowerFirst","ComponentCollection","FormComponent","isFormState","deduplicateErrorsByHref","getLocationFieldViewModel","NumberField","messageTemplate","convertToLanguageMessages","DEFAULT_EASTING_MIN","DEFAULT_EASTING_MAX","DEFAULT_NORTHING_MIN","DEFAULT_NORTHING_MAX","EastingNorthingField","constructor","def","props","name","options","schema","isRequired","required","eastingMin","easting","min","eastingMax","max","northingMin","northing","northingMax","eastingRequired","northingRequired","eastingDigitsMessage","label","northingDigitsMessage","customValidationMessages","northingValidationMessages","collection","type","title","precision","optionalText","classes","parent","peers","formSchema","stateSchema","getFormValueFromState","state","value","isEastingNorthing","undefined","getDisplayStringFromFormValue","getDisplayStringFromState","getContextValueFromFormValue","getContextValueFromState","getViewModel","payload","errors","viewModel","getViewErrors","allErrors","getErrors","isState","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","isNumber"],"sources":["../../../../../src/server/plugins/engine/components/EastingNorthingField.ts"],"sourcesContent":["import {\n ComponentType,\n type EastingNorthingFieldComponent\n} from '@defra/forms-model'\nimport { type LanguageMessages, type ObjectSchema } from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n deduplicateErrorsByHref,\n getLocationFieldViewModel\n} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport { type EastingNorthingState } from '~/src/server/plugins/engine/components/types.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'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\n// British National Grid coordinate limits\nconst DEFAULT_EASTING_MIN = 0\nconst DEFAULT_EASTING_MAX = 700000\nconst DEFAULT_NORTHING_MIN = 0\nconst DEFAULT_NORTHING_MAX = 1300000\n\nexport class EastingNorthingField extends FormComponent {\n declare options: EastingNorthingFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: EastingNorthingFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options, schema } = def\n\n const isRequired = options.required !== false\n\n const eastingMin = schema?.easting?.min ?? DEFAULT_EASTING_MIN\n const eastingMax = schema?.easting?.max ?? DEFAULT_EASTING_MAX\n const northingMin = schema?.northing?.min ?? DEFAULT_NORTHING_MIN\n const northingMax = schema?.northing?.max ?? DEFAULT_NORTHING_MAX\n\n const eastingRequired = 'Enter easting'\n const northingRequired = 'Enter northing'\n\n const eastingDigitsMessage = `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 6 digits`\n const northingDigitsMessage = `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 7 digits`\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': eastingRequired,\n 'number.base': eastingRequired,\n 'number.min': `{{#label}} for ${lowerFirst(this.label)} must be between {{#limit}} and ${eastingMax}`,\n 'number.max': `{{#label}} for ${lowerFirst(this.label)} must be between ${eastingMin} and {{#limit}}`,\n 'number.precision': eastingDigitsMessage,\n 'number.integer': eastingDigitsMessage,\n 'number.unsafe': eastingDigitsMessage\n })\n\n const northingValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': northingRequired,\n 'number.base': northingRequired,\n 'number.min': `{{#label}} for ${lowerFirst(this.label)} must be between {{#limit}} and ${northingMax}`,\n 'number.max': `{{#label}} for ${lowerFirst(this.label)} must be between ${northingMin} and {{#limit}}`,\n 'number.precision': northingDigitsMessage,\n 'number.integer': northingDigitsMessage,\n 'number.unsafe': northingDigitsMessage\n })\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__easting`,\n title: 'Easting',\n schema: {\n min: eastingMin,\n max: eastingMax,\n precision: 0\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n customValidationMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__northing`,\n title: 'Northing',\n schema: {\n min: northingMin,\n max: northingMax,\n precision: 0\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n customValidationMessages: northingValidationMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n peers: [`${name}__easting`, `${name}__northing`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return EastingNorthingField.isEastingNorthing(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(\n value: EastingNorthingState | undefined\n ): string {\n if (!value) {\n return ''\n }\n\n // CYA page format: <<eastingvalue, northingvalue>>\n return `${value.easting}, ${value.northing}`\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value)\n }\n\n getContextValueFromFormValue(\n value: EastingNorthingState | undefined\n ): string | null {\n if (!value) {\n return null\n }\n\n return `Easting: ${value.easting}\\nNorthing: ${value.northing}`\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n return getLocationFieldViewModel(this, viewModel, payload, errors)\n }\n\n getViewErrors(\n errors?: FormSubmissionError[]\n ): FormSubmissionError[] | undefined {\n const allErrors = this.getErrors(errors)\n return deduplicateErrorsByHref(allErrors)\n }\n\n isState(value?: FormStateValue | FormState) {\n return EastingNorthingField.isEastingNorthing(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return EastingNorthingField.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 {\n type: 'eastingFormat',\n template: 'Easting for {{#title}} must be between 1 and 6 digits'\n },\n {\n type: 'northingFormat',\n template: 'Northing for {{#title}} must be between 1 and 7 digits'\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'eastingMin',\n template: `Easting for {{#title}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`\n },\n {\n type: 'eastingMax',\n template: `Easting for {{#title}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`\n },\n {\n type: 'northingMin',\n template: `Northing for {{#title}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`\n },\n {\n type: 'northingMax',\n template: `Northing for {{#title}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`\n }\n ]\n }\n }\n\n static isEastingNorthing(\n value?: FormStateValue | FormState\n ): value is EastingNorthingState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.easting) &&\n NumberField.isNumber(value.northing)\n )\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"EastingNorthingField.js","names":["ComponentType","lowerFirst","ComponentCollection","FormComponent","isFormState","deduplicateErrorsByHref","getLocationFieldViewModel","NumberField","createLowerFirstExpression","messageTemplate","convertToLanguageMessages","DEFAULT_EASTING_MIN","DEFAULT_EASTING_MAX","DEFAULT_NORTHING_MIN","DEFAULT_NORTHING_MAX","EastingNorthingField","constructor","def","props","name","options","schema","isRequired","required","eastingMin","easting","min","eastingMax","max","northingMin","northing","northingMax","eastingRequired","northingRequired","eastingDigitsMessage","label","northingDigitsMessage","customValidationMessages","northingValidationMessages","collection","type","title","precision","optionalText","classes","parent","peers","formSchema","stateSchema","getFormValueFromState","state","value","isEastingNorthing","undefined","getDisplayStringFromFormValue","getDisplayStringFromState","getContextValueFromFormValue","getContextValueFromState","getViewModel","payload","errors","viewModel","getViewErrors","allErrors","getErrors","isState","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","isNumber"],"sources":["../../../../../src/server/plugins/engine/components/EastingNorthingField.ts"],"sourcesContent":["import {\n ComponentType,\n type EastingNorthingFieldComponent\n} from '@defra/forms-model'\nimport { type LanguageMessages, type ObjectSchema } from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n deduplicateErrorsByHref,\n getLocationFieldViewModel\n} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport { type EastingNorthingState } from '~/src/server/plugins/engine/components/types.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'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\n// British National Grid coordinate limits\nconst DEFAULT_EASTING_MIN = 0\nconst DEFAULT_EASTING_MAX = 700000\nconst DEFAULT_NORTHING_MIN = 0\nconst DEFAULT_NORTHING_MAX = 1300000\n\nexport class EastingNorthingField extends FormComponent {\n declare options: EastingNorthingFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: EastingNorthingFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options, schema } = def\n\n const isRequired = options.required !== false\n\n const eastingMin = schema?.easting?.min ?? DEFAULT_EASTING_MIN\n const eastingMax = schema?.easting?.max ?? DEFAULT_EASTING_MAX\n const northingMin = schema?.northing?.min ?? DEFAULT_NORTHING_MIN\n const northingMax = schema?.northing?.max ?? DEFAULT_NORTHING_MAX\n\n const eastingRequired = 'Enter easting'\n const northingRequired = 'Enter northing'\n\n const eastingDigitsMessage = `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 6 digits`\n const northingDigitsMessage = `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 7 digits`\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': eastingRequired,\n 'number.base': eastingRequired,\n 'number.min': `{{#label}} for ${lowerFirst(this.label)} must be between {{#limit}} and ${eastingMax}`,\n 'number.max': `{{#label}} for ${lowerFirst(this.label)} must be between ${eastingMin} and {{#limit}}`,\n 'number.precision': eastingDigitsMessage,\n 'number.integer': eastingDigitsMessage,\n 'number.unsafe': eastingDigitsMessage\n })\n\n const northingValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': northingRequired,\n 'number.base': northingRequired,\n 'number.min': `{{#label}} for ${lowerFirst(this.label)} must be between {{#limit}} and ${northingMax}`,\n 'number.max': `{{#label}} for ${lowerFirst(this.label)} must be between ${northingMin} and {{#limit}}`,\n 'number.precision': northingDigitsMessage,\n 'number.integer': northingDigitsMessage,\n 'number.unsafe': northingDigitsMessage\n })\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__easting`,\n title: 'Easting',\n schema: {\n min: eastingMin,\n max: eastingMax,\n precision: 0\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n customValidationMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__northing`,\n title: 'Northing',\n schema: {\n min: northingMin,\n max: northingMax,\n precision: 0\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n customValidationMessages: northingValidationMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n peers: [`${name}__easting`, `${name}__northing`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return EastingNorthingField.isEastingNorthing(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(\n value: EastingNorthingState | undefined\n ): string {\n if (!value) {\n return ''\n }\n\n // CYA page format: <<eastingvalue, northingvalue>>\n return `${value.easting}, ${value.northing}`\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value)\n }\n\n getContextValueFromFormValue(\n value: EastingNorthingState | undefined\n ): string | null {\n if (!value) {\n return null\n }\n\n return `Easting: ${value.easting}\\nNorthing: ${value.northing}`\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n return getLocationFieldViewModel(this, viewModel, payload, errors)\n }\n\n getViewErrors(\n errors?: FormSubmissionError[]\n ): FormSubmissionError[] | undefined {\n const allErrors = this.getErrors(errors)\n return deduplicateErrorsByHref(allErrors)\n }\n\n isState(value?: FormStateValue | FormState) {\n return EastingNorthingField.isEastingNorthing(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return EastingNorthingField.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 {\n type: 'eastingFormat',\n template: createLowerFirstExpression(\n 'Easting for {{lowerFirst(#title)}} must be between 1 and 6 digits'\n )\n },\n {\n type: 'northingFormat',\n template: createLowerFirstExpression(\n 'Northing for {{lowerFirst(#title)}} must be between 1 and 7 digits'\n )\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'eastingMin',\n template: createLowerFirstExpression(\n `Easting for {{lowerFirst(#title)}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`\n )\n },\n {\n type: 'eastingMax',\n template: createLowerFirstExpression(\n `Easting for {{lowerFirst(#title)}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`\n )\n },\n {\n type: 'northingMin',\n template: createLowerFirstExpression(\n `Northing for {{lowerFirst(#title)}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`\n )\n },\n {\n type: 'northingMax',\n template: createLowerFirstExpression(\n `Northing for {{lowerFirst(#title)}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`\n )\n }\n ]\n }\n }\n\n static isEastingNorthing(\n value?: FormStateValue | FormState\n ): value is EastingNorthingState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.easting) &&\n NumberField.isNumber(value.northing)\n )\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -4,6 +4,7 @@ import { ComponentCollection } from "./ComponentCollection.js";
4
4
  import { FormComponent, isFormState } from "./FormComponent.js";
5
5
  import { deduplicateErrorsByHref, getLocationFieldViewModel } from "./LocationFieldHelpers.js";
6
6
  import { NumberField } from "./NumberField.js";
7
+ import { createLowerFirstExpression } from "./helpers/index.js";
7
8
  import { messageTemplate } from "../pageControllers/validationOptions.js";
8
9
  import { convertToLanguageMessages } from "../../../utils/type-utils.js";
9
10
 
@@ -147,23 +148,23 @@ export class LatLongField extends FormComponent {
147
148
  template: messageTemplate.required
148
149
  }, {
149
150
  type: 'latitudeFormat',
150
- template: 'Enter a valid latitude for {{#title}} like 51.519450'
151
+ template: createLowerFirstExpression('Enter a valid latitude for {{lowerFirst(#title)}} like 51.519450')
151
152
  }, {
152
153
  type: 'longitudeFormat',
153
- template: 'Enter a valid longitude for {{#title}} like -0.127758'
154
+ template: createLowerFirstExpression('Enter a valid longitude for {{lowerFirst(#title)}} like -0.127758')
154
155
  }],
155
156
  advancedSettingsErrors: [{
156
157
  type: 'latitudeMin',
157
- template: 'Latitude for {{#title}} must be between 49 and 60'
158
+ template: createLowerFirstExpression('Latitude for {{lowerFirst(#title)}} must be between 49 and 60')
158
159
  }, {
159
160
  type: 'latitudeMax',
160
- template: 'Latitude for {{#title}} must be between 49 and 60'
161
+ template: createLowerFirstExpression('Latitude for {{lowerFirst(#title)}} must be between 49 and 60')
161
162
  }, {
162
163
  type: 'longitudeMin',
163
- template: 'Longitude for {{#title}} must be between -9 and 2'
164
+ template: createLowerFirstExpression('Longitude for {{lowerFirst(#title)}} must be between -9 and 2')
164
165
  }, {
165
166
  type: 'longitudeMax',
166
- template: 'Longitude for {{#title}} must be between -9 and 2'
167
+ template: createLowerFirstExpression('Longitude for {{lowerFirst(#title)}} must be between -9 and 2')
167
168
  }]
168
169
  };
169
170
  }
@@ -1 +1 @@
1
- {"version":3,"file":"LatLongField.js","names":["ComponentType","lowerFirst","ComponentCollection","FormComponent","isFormState","deduplicateErrorsByHref","getLocationFieldViewModel","NumberField","messageTemplate","convertToLanguageMessages","DECIMAL_PRECISION","LatLongField","constructor","def","props","name","options","schema","isRequired","required","latitudeMin","latitude","min","latitudeMax","max","longitudeMin","longitude","longitudeMax","latitudeRequired","longitudeRequired","customValidationMessages","latitudeRangeMessage","label","longitudeRangeMessage","latitudeMessages","longitudeMessages","collection","type","title","precision","optionalText","classes","suffix","parent","peers","formSchema","stateSchema","getFormValueFromState","state","value","isLatLong","undefined","getDisplayStringFromFormValue","getDisplayStringFromState","getContextValueFromFormValue","getContextValueFromState","getViewModel","payload","errors","viewModel","getViewErrors","allErrors","getErrors","isState","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","isNumber"],"sources":["../../../../../src/server/plugins/engine/components/LatLongField.ts"],"sourcesContent":["import { ComponentType, type LatLongFieldComponent } from '@defra/forms-model'\nimport { type LanguageMessages, type ObjectSchema } from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n deduplicateErrorsByHref,\n getLocationFieldViewModel\n} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport { type LatLongState } from '~/src/server/plugins/engine/components/types.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'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\n// Precision constants\n// UK latitude/longitude requires high precision for accurate location (within ~11mm)\nconst DECIMAL_PRECISION = 7 // 7 decimal places\n\nexport class LatLongField extends FormComponent {\n declare options: LatLongFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: LatLongFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options, schema } = def\n\n const isRequired = options.required !== false\n\n // Read schema values from def.schema with fallback defaults\n const latitudeMin = schema?.latitude?.min ?? 49.85\n const latitudeMax = schema?.latitude?.max ?? 60.859\n const longitudeMin = schema?.longitude?.min ?? -13.687\n const longitudeMax = schema?.longitude?.max ?? 1.767\n\n const latitudeRequired = 'Enter latitude'\n const longitudeRequired = 'Enter longitude'\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'number.precision':\n '{{#label}} must have no more than 7 decimal places',\n 'number.unsafe': '{{#label}} must be a valid number'\n })\n\n const latitudeRangeMessage = `Latitude for ${lowerFirst(this.label)} must be between ${latitudeMin} and ${latitudeMax}`\n const longitudeRangeMessage = `Longitude for ${lowerFirst(this.label)} must be between ${longitudeMin} and ${longitudeMax}`\n\n const latitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'any.required': latitudeRequired,\n 'number.base': `Enter a valid latitude for ${lowerFirst(this.label)} like 51.519450`,\n 'number.min': latitudeRangeMessage,\n 'number.max': latitudeRangeMessage\n })\n\n const longitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'any.required': longitudeRequired,\n 'number.base': `Enter a valid longitude for ${lowerFirst(this.label)} like -0.127758`,\n 'number.min': longitudeRangeMessage,\n 'number.max': longitudeRangeMessage\n })\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__latitude`,\n title: 'Latitude',\n schema: {\n min: latitudeMin,\n max: latitudeMax,\n precision: DECIMAL_PRECISION\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: latitudeMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__longitude`,\n title: 'Longitude',\n schema: {\n min: longitudeMin,\n max: longitudeMax,\n precision: DECIMAL_PRECISION\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: longitudeMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n peers: [`${name}__latitude`, `${name}__longitude`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return LatLongField.isLatLong(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(value: LatLongState | undefined): string {\n if (!value) {\n return ''\n }\n\n // CYA page format: <<latvalue, langvalue>>\n return `${value.latitude}, ${value.longitude}`\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value)\n }\n\n getContextValueFromFormValue(value: LatLongState | undefined): string | null {\n if (!value) {\n return null\n }\n\n // Output format: Latitude: <<entry>>\\nLongitude: <<entry>>\n return `Latitude: ${value.latitude}\\nLongitude: ${value.longitude}`\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n return getLocationFieldViewModel(this, viewModel, payload, errors)\n }\n\n getViewErrors(\n errors?: FormSubmissionError[]\n ): FormSubmissionError[] | undefined {\n const allErrors = this.getErrors(errors)\n return deduplicateErrorsByHref(allErrors)\n }\n\n isState(value?: FormStateValue | FormState) {\n return LatLongField.isLatLong(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return LatLongField.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 {\n type: 'latitudeFormat',\n template: 'Enter a valid latitude for {{#title}} like 51.519450'\n },\n {\n type: 'longitudeFormat',\n template: 'Enter a valid longitude for {{#title}} like -0.127758'\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'latitudeMin',\n template: 'Latitude for {{#title}} must be between 49 and 60'\n },\n {\n type: 'latitudeMax',\n template: 'Latitude for {{#title}} must be between 49 and 60'\n },\n {\n type: 'longitudeMin',\n template: 'Longitude for {{#title}} must be between -9 and 2'\n },\n {\n type: 'longitudeMax',\n template: 'Longitude for {{#title}} must be between -9 and 2'\n }\n ]\n }\n }\n\n static isLatLong(value?: FormStateValue | FormState): value is LatLongState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.latitude) &&\n NumberField.isNumber(value.longitude)\n )\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"LatLongField.js","names":["ComponentType","lowerFirst","ComponentCollection","FormComponent","isFormState","deduplicateErrorsByHref","getLocationFieldViewModel","NumberField","createLowerFirstExpression","messageTemplate","convertToLanguageMessages","DECIMAL_PRECISION","LatLongField","constructor","def","props","name","options","schema","isRequired","required","latitudeMin","latitude","min","latitudeMax","max","longitudeMin","longitude","longitudeMax","latitudeRequired","longitudeRequired","customValidationMessages","latitudeRangeMessage","label","longitudeRangeMessage","latitudeMessages","longitudeMessages","collection","type","title","precision","optionalText","classes","suffix","parent","peers","formSchema","stateSchema","getFormValueFromState","state","value","isLatLong","undefined","getDisplayStringFromFormValue","getDisplayStringFromState","getContextValueFromFormValue","getContextValueFromState","getViewModel","payload","errors","viewModel","getViewErrors","allErrors","getErrors","isState","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","isNumber"],"sources":["../../../../../src/server/plugins/engine/components/LatLongField.ts"],"sourcesContent":["import { ComponentType, type LatLongFieldComponent } from '@defra/forms-model'\nimport { type LanguageMessages, type ObjectSchema } from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n deduplicateErrorsByHref,\n getLocationFieldViewModel\n} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport { type LatLongState } from '~/src/server/plugins/engine/components/types.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'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\n// Precision constants\n// UK latitude/longitude requires high precision for accurate location (within ~11mm)\nconst DECIMAL_PRECISION = 7 // 7 decimal places\n\nexport class LatLongField extends FormComponent {\n declare options: LatLongFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: LatLongFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options, schema } = def\n\n const isRequired = options.required !== false\n\n // Read schema values from def.schema with fallback defaults\n const latitudeMin = schema?.latitude?.min ?? 49.85\n const latitudeMax = schema?.latitude?.max ?? 60.859\n const longitudeMin = schema?.longitude?.min ?? -13.687\n const longitudeMax = schema?.longitude?.max ?? 1.767\n\n const latitudeRequired = 'Enter latitude'\n const longitudeRequired = 'Enter longitude'\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'number.precision':\n '{{#label}} must have no more than 7 decimal places',\n 'number.unsafe': '{{#label}} must be a valid number'\n })\n\n const latitudeRangeMessage = `Latitude for ${lowerFirst(this.label)} must be between ${latitudeMin} and ${latitudeMax}`\n const longitudeRangeMessage = `Longitude for ${lowerFirst(this.label)} must be between ${longitudeMin} and ${longitudeMax}`\n\n const latitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'any.required': latitudeRequired,\n 'number.base': `Enter a valid latitude for ${lowerFirst(this.label)} like 51.519450`,\n 'number.min': latitudeRangeMessage,\n 'number.max': latitudeRangeMessage\n })\n\n const longitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'any.required': longitudeRequired,\n 'number.base': `Enter a valid longitude for ${lowerFirst(this.label)} like -0.127758`,\n 'number.min': longitudeRangeMessage,\n 'number.max': longitudeRangeMessage\n })\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__latitude`,\n title: 'Latitude',\n schema: {\n min: latitudeMin,\n max: latitudeMax,\n precision: DECIMAL_PRECISION\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: latitudeMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__longitude`,\n title: 'Longitude',\n schema: {\n min: longitudeMin,\n max: longitudeMax,\n precision: DECIMAL_PRECISION\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: longitudeMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n peers: [`${name}__latitude`, `${name}__longitude`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return LatLongField.isLatLong(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(value: LatLongState | undefined): string {\n if (!value) {\n return ''\n }\n\n // CYA page format: <<latvalue, langvalue>>\n return `${value.latitude}, ${value.longitude}`\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value)\n }\n\n getContextValueFromFormValue(value: LatLongState | undefined): string | null {\n if (!value) {\n return null\n }\n\n // Output format: Latitude: <<entry>>\\nLongitude: <<entry>>\n return `Latitude: ${value.latitude}\\nLongitude: ${value.longitude}`\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n return getLocationFieldViewModel(this, viewModel, payload, errors)\n }\n\n getViewErrors(\n errors?: FormSubmissionError[]\n ): FormSubmissionError[] | undefined {\n const allErrors = this.getErrors(errors)\n return deduplicateErrorsByHref(allErrors)\n }\n\n isState(value?: FormStateValue | FormState) {\n return LatLongField.isLatLong(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return LatLongField.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 {\n type: 'latitudeFormat',\n template: createLowerFirstExpression(\n 'Enter a valid latitude for {{lowerFirst(#title)}} like 51.519450'\n )\n },\n {\n type: 'longitudeFormat',\n template: createLowerFirstExpression(\n 'Enter a valid longitude for {{lowerFirst(#title)}} like -0.127758'\n )\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'latitudeMin',\n template: createLowerFirstExpression(\n 'Latitude for {{lowerFirst(#title)}} must be between 49 and 60'\n )\n },\n {\n type: 'latitudeMax',\n template: createLowerFirstExpression(\n 'Latitude for {{lowerFirst(#title)}} must be between 49 and 60'\n )\n },\n {\n type: 'longitudeMin',\n template: createLowerFirstExpression(\n 'Longitude for {{lowerFirst(#title)}} must be between -9 and 2'\n )\n },\n {\n type: 'longitudeMax',\n template: createLowerFirstExpression(\n 'Longitude for {{lowerFirst(#title)}} must be between -9 and 2'\n )\n }\n ]\n }\n }\n\n static isLatLong(value?: FormStateValue | FormState): value is LatLongState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.latitude) &&\n NumberField.isNumber(value.longitude)\n )\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,5 +1,5 @@
1
1
  import { type FormComponentsDef } from '@defra/forms-model';
2
- import { type LanguageMessages, type StringSchema } from 'joi';
2
+ import { type JoiExpression, type LanguageMessages, type StringSchema } from 'joi';
3
3
  import { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js';
4
4
  import { type ErrorMessageTemplateList, type FormPayload, type FormState, type FormStateValue, type FormSubmissionError, type FormSubmissionState } from '~/src/server/plugins/engine/types.js';
5
5
  interface LocationFieldOptions {
@@ -11,8 +11,8 @@ interface LocationFieldOptions {
11
11
  }
12
12
  interface ValidationConfig {
13
13
  pattern: RegExp;
14
- patternErrorMessage: string;
15
- requiredMessage?: string;
14
+ patternErrorMessage: JoiExpression;
15
+ requiredMessage?: JoiExpression;
16
16
  }
17
17
  /**
18
18
  * Abstract base class for location-based field components
@@ -25,7 +25,7 @@ export declare abstract class LocationFieldBase extends FormComponent {
25
25
  protected abstract getValidationConfig(): ValidationConfig;
26
26
  protected abstract getErrorTemplates(): {
27
27
  type: string;
28
- template: string;
28
+ template: JoiExpression;
29
29
  }[];
30
30
  constructor(def: FormComponentsDef, props: ConstructorParameters<typeof FormComponent>[1]);
31
31
  getFormValueFromState(state: FormSubmissionState): string | undefined;
@@ -2,6 +2,7 @@ import joi from 'joi';
2
2
  import { FormComponent, isFormValue } from "./FormComponent.js";
3
3
  import { addClassOptionIfNone } from "./helpers/index.js";
4
4
  import { messageTemplate } from "../pageControllers/validationOptions.js";
5
+ import { convertToLanguageMessages } from "../../../utils/type-utils.js";
5
6
  /**
6
7
  * Abstract base class for location-based field components
7
8
  */
@@ -17,11 +18,11 @@ export class LocationFieldBase extends FormComponent {
17
18
  addClassOptionIfNone(locationOptions, 'govuk-input--width-10');
18
19
  const config = this.getValidationConfig();
19
20
  const requiredMessage = config.requiredMessage ?? messageTemplate.required;
20
- const messages = {
21
+ const messages = convertToLanguageMessages({
21
22
  'any.required': requiredMessage,
22
23
  'string.empty': requiredMessage,
23
24
  'string.pattern.base': config.patternErrorMessage
24
- };
25
+ });
25
26
  let formSchema = joi.string().trim().label(this.label).required().pattern(config.pattern).messages(messages);
26
27
  if (locationOptions.required === false) {
27
28
  formSchema = formSchema.allow('');
@@ -1 +1 @@
1
- {"version":3,"file":"LocationFieldBase.js","names":["joi","FormComponent","isFormValue","addClassOptionIfNone","messageTemplate","LocationFieldBase","instructionText","constructor","def","props","options","locationOptions","config","getValidationConfig","requiredMessage","required","messages","patternErrorMessage","formSchema","string","trim","label","pattern","allow","customValidationMessage","message","messageKeys","reduce","acc","key","customValidationMessages","default","stateSchema","getFormValueFromState","state","name","getFormValue","value","isValue","undefined","isText","getViewModel","payload","errors","viewModel","getAllPossibleErrors","baseErrors","type","template","getErrorTemplates","advancedSettingsErrors"],"sources":["../../../../../src/server/plugins/engine/components/LocationFieldBase.ts"],"sourcesContent":["import { type FormComponentsDef } from '@defra/forms-model'\nimport joi, { type LanguageMessages, type StringSchema } from 'joi'\n\nimport {\n FormComponent,\n isFormValue\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { addClassOptionIfNone } from '~/src/server/plugins/engine/components/helpers/index.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\ninterface LocationFieldOptions {\n instructionText?: string\n required?: boolean\n customValidationMessage?: string\n customValidationMessages?: LanguageMessages\n classes?: string\n}\n\ninterface ValidationConfig {\n pattern: RegExp\n patternErrorMessage: string\n requiredMessage?: string\n}\n\n/**\n * Abstract base class for location-based field components\n */\nexport abstract class LocationFieldBase extends FormComponent {\n declare options: LocationFieldOptions\n declare formSchema: StringSchema\n declare stateSchema: StringSchema\n instructionText?: string\n\n protected abstract getValidationConfig(): ValidationConfig\n protected abstract getErrorTemplates(): {\n type: string\n template: string\n }[]\n\n constructor(\n def: FormComponentsDef,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { options } = def\n const locationOptions = options as LocationFieldOptions\n this.instructionText = locationOptions.instructionText\n\n addClassOptionIfNone(locationOptions, 'govuk-input--width-10')\n\n const config = this.getValidationConfig()\n const requiredMessage =\n config.requiredMessage ?? (messageTemplate.required as string)\n\n const messages: LanguageMessages = {\n 'any.required': requiredMessage,\n 'string.empty': requiredMessage,\n 'string.pattern.base': config.patternErrorMessage\n }\n\n let formSchema = joi\n .string()\n .trim()\n .label(this.label)\n .required()\n .pattern(config.pattern)\n .messages(messages)\n\n if (locationOptions.required === false) {\n formSchema = formSchema.allow('')\n }\n\n if (locationOptions.customValidationMessage) {\n const message = locationOptions.customValidationMessage\n const messageKeys = [\n 'any.required',\n 'string.empty',\n 'string.pattern.base'\n ]\n\n const messages = messageKeys.reduce<LanguageMessages>((acc, key) => {\n acc[key] = message\n return acc\n }, {})\n\n formSchema = formSchema.messages(messages)\n } else if (locationOptions.customValidationMessages) {\n formSchema = formSchema.messages(locationOptions.customValidationMessages)\n }\n\n this.formSchema = formSchema.default('')\n this.stateSchema = formSchema.default(null).allow(null)\n this.options = locationOptions\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 isValue(value?: FormStateValue | FormState): value is string {\n return LocationFieldBase.isText(value)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n\n if (this.instructionText) {\n return {\n ...viewModel,\n instructionText: this.instructionText\n }\n }\n\n return viewModel\n }\n\n getAllPossibleErrors(): ErrorMessageTemplateList {\n const config = this.getValidationConfig()\n\n return {\n baseErrors: [\n {\n type: 'required',\n template:\n config.requiredMessage ?? (messageTemplate.required as string)\n },\n ...this.getErrorTemplates()\n ],\n advancedSettingsErrors: []\n }\n }\n\n static isText(value?: FormStateValue | FormState): value is string {\n return isFormValue(value) && typeof value === 'string'\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"LocationFieldBase.js","names":["joi","FormComponent","isFormValue","addClassOptionIfNone","messageTemplate","convertToLanguageMessages","LocationFieldBase","instructionText","constructor","def","props","options","locationOptions","config","getValidationConfig","requiredMessage","required","messages","patternErrorMessage","formSchema","string","trim","label","pattern","allow","customValidationMessage","message","messageKeys","reduce","acc","key","customValidationMessages","default","stateSchema","getFormValueFromState","state","name","getFormValue","value","isValue","undefined","isText","getViewModel","payload","errors","viewModel","getAllPossibleErrors","baseErrors","type","template","getErrorTemplates","advancedSettingsErrors"],"sources":["../../../../../src/server/plugins/engine/components/LocationFieldBase.ts"],"sourcesContent":["import { type FormComponentsDef } from '@defra/forms-model'\nimport joi, {\n type JoiExpression,\n type LanguageMessages,\n type StringSchema\n} from 'joi'\n\nimport {\n FormComponent,\n isFormValue\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { addClassOptionIfNone } from '~/src/server/plugins/engine/components/helpers/index.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'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\ninterface LocationFieldOptions {\n instructionText?: string\n required?: boolean\n customValidationMessage?: string\n customValidationMessages?: LanguageMessages\n classes?: string\n}\n\ninterface ValidationConfig {\n pattern: RegExp\n patternErrorMessage: JoiExpression\n requiredMessage?: JoiExpression\n}\n\n/**\n * Abstract base class for location-based field components\n */\nexport abstract class LocationFieldBase extends FormComponent {\n declare options: LocationFieldOptions\n declare formSchema: StringSchema\n declare stateSchema: StringSchema\n instructionText?: string\n\n protected abstract getValidationConfig(): ValidationConfig\n protected abstract getErrorTemplates(): {\n type: string\n template: JoiExpression\n }[]\n\n constructor(\n def: FormComponentsDef,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { options } = def\n const locationOptions = options as LocationFieldOptions\n this.instructionText = locationOptions.instructionText\n\n addClassOptionIfNone(locationOptions, 'govuk-input--width-10')\n\n const config = this.getValidationConfig()\n const requiredMessage =\n config.requiredMessage ?? (messageTemplate.required as string)\n\n const messages = convertToLanguageMessages({\n 'any.required': requiredMessage,\n 'string.empty': requiredMessage,\n 'string.pattern.base': config.patternErrorMessage\n })\n\n let formSchema = joi\n .string()\n .trim()\n .label(this.label)\n .required()\n .pattern(config.pattern)\n .messages(messages)\n\n if (locationOptions.required === false) {\n formSchema = formSchema.allow('')\n }\n\n if (locationOptions.customValidationMessage) {\n const message = locationOptions.customValidationMessage\n const messageKeys = [\n 'any.required',\n 'string.empty',\n 'string.pattern.base'\n ]\n\n const messages = messageKeys.reduce<LanguageMessages>((acc, key) => {\n acc[key] = message\n return acc\n }, {})\n\n formSchema = formSchema.messages(messages)\n } else if (locationOptions.customValidationMessages) {\n formSchema = formSchema.messages(locationOptions.customValidationMessages)\n }\n\n this.formSchema = formSchema.default('')\n this.stateSchema = formSchema.default(null).allow(null)\n this.options = locationOptions\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 isValue(value?: FormStateValue | FormState): value is string {\n return LocationFieldBase.isText(value)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n\n if (this.instructionText) {\n return {\n ...viewModel,\n instructionText: this.instructionText\n }\n }\n\n return viewModel\n }\n\n getAllPossibleErrors(): ErrorMessageTemplateList {\n const config = this.getValidationConfig()\n\n return {\n baseErrors: [\n {\n type: 'required',\n template:\n config.requiredMessage ?? (messageTemplate.required as string)\n },\n ...this.getErrorTemplates()\n ],\n advancedSettingsErrors: []\n }\n }\n\n static isText(value?: FormStateValue | FormState): value is string {\n return isFormValue(value) && typeof value === 'string'\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -4,12 +4,12 @@ export declare class NationalGridFieldNumberField extends LocationFieldBase {
4
4
  options: NationalGridFieldNumberFieldComponent['options'];
5
5
  protected getValidationConfig(): {
6
6
  pattern: RegExp;
7
- patternErrorMessage: string;
8
- requiredMessage: string;
7
+ patternErrorMessage: import("joi").JoiExpression;
8
+ requiredMessage: import("joi").JoiExpression;
9
9
  };
10
10
  protected getErrorTemplates(): {
11
11
  type: string;
12
- template: string;
12
+ template: import("joi").JoiExpression;
13
13
  }[];
14
14
  /**
15
15
  * Static version of getAllPossibleErrors that doesn't require a component instance.
@@ -1,20 +1,22 @@
1
1
  import { LocationFieldBase } from "./LocationFieldBase.js";
2
+ import { createLowerFirstExpression } from "./helpers/index.js";
2
3
  export class NationalGridFieldNumberField extends LocationFieldBase {
3
4
  getValidationConfig() {
4
5
  // Regex for OS national grid field references (NGFR)
5
6
  // Validates specific valid OS grid letter combinations with:
6
7
  // - 2 letters & 8 digits in 2 blocks of 4 e.g. ST 6789 6789
7
8
  const pattern = /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\s?([0-9]{4})\s?([0-9]{4})$/;
9
+ const patternTemplate = 'Enter a valid National Grid field number for {{lowerFirst(#title)}} like NG 1234 5678';
8
10
  return {
9
11
  pattern,
10
- patternErrorMessage: `Enter a valid National Grid field number for {{#title}} like NG 1234 5678`,
11
- requiredMessage: 'Enter {{#title}}'
12
+ patternErrorMessage: createLowerFirstExpression(patternTemplate),
13
+ requiredMessage: createLowerFirstExpression('Enter {{lowerFirst(#title)}}')
12
14
  };
13
15
  }
14
16
  getErrorTemplates() {
15
17
  return [{
16
18
  type: 'pattern',
17
- template: 'Enter a valid National Grid field number for {{#title}} like NG 1234 5678'
19
+ template: createLowerFirstExpression('Enter a valid National Grid field number for {{lowerFirst(#title)}} like NG 1234 5678')
18
20
  }];
19
21
  }
20
22
 
@@ -1 +1 @@
1
- {"version":3,"file":"NationalGridFieldNumberField.js","names":["LocationFieldBase","NationalGridFieldNumberField","getValidationConfig","pattern","patternErrorMessage","requiredMessage","getErrorTemplates","type","template","getAllPossibleErrors","instance","Object","create","prototype"],"sources":["../../../../../src/server/plugins/engine/components/NationalGridFieldNumberField.ts"],"sourcesContent":["import { type NationalGridFieldNumberFieldComponent } from '@defra/forms-model'\n\nimport { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'\n\nexport class NationalGridFieldNumberField extends LocationFieldBase {\n declare options: NationalGridFieldNumberFieldComponent['options']\n\n protected getValidationConfig() {\n // Regex for OS national grid field references (NGFR)\n // Validates specific valid OS grid letter combinations with:\n // - 2 letters & 8 digits in 2 blocks of 4 e.g. ST 6789 6789\n const pattern =\n /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\\s?([0-9]{4})\\s?([0-9]{4})$/\n\n return {\n pattern,\n patternErrorMessage: `Enter a valid National Grid field number for {{#title}} like NG 1234 5678`,\n requiredMessage: 'Enter {{#title}}'\n }\n }\n\n protected getErrorTemplates() {\n return [\n {\n type: 'pattern',\n template:\n 'Enter a valid National Grid field number for {{#title}} like NG 1234 5678'\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(\n NationalGridFieldNumberField.prototype\n ) as NationalGridFieldNumberField\n return instance.getAllPossibleErrors()\n }\n}\n"],"mappings":"AAEA,SAASA,iBAAiB;AAE1B,OAAO,MAAMC,4BAA4B,SAASD,iBAAiB,CAAC;EAGxDE,mBAAmBA,CAAA,EAAG;IAC9B;IACA;IACA;IACA,MAAMC,OAAO,GACX,mIAAmI;IAErI,OAAO;MACLA,OAAO;MACPC,mBAAmB,EAAE,2EAA2E;MAChGC,eAAe,EAAE;IACnB,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,CAC5BX,4BAA4B,CAACY,SAC/B,CAAiC;IACjC,OAAOH,QAAQ,CAACD,oBAAoB,CAAC,CAAC;EACxC;AACF","ignoreList":[]}
1
+ {"version":3,"file":"NationalGridFieldNumberField.js","names":["LocationFieldBase","createLowerFirstExpression","NationalGridFieldNumberField","getValidationConfig","pattern","patternTemplate","patternErrorMessage","requiredMessage","getErrorTemplates","type","template","getAllPossibleErrors","instance","Object","create","prototype"],"sources":["../../../../../src/server/plugins/engine/components/NationalGridFieldNumberField.ts"],"sourcesContent":["import { type NationalGridFieldNumberFieldComponent } from '@defra/forms-model'\n\nimport { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'\nimport { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'\n\nexport class NationalGridFieldNumberField extends LocationFieldBase {\n declare options: NationalGridFieldNumberFieldComponent['options']\n\n protected getValidationConfig() {\n // Regex for OS national grid field references (NGFR)\n // Validates specific valid OS grid letter combinations with:\n // - 2 letters & 8 digits in 2 blocks of 4 e.g. ST 6789 6789\n const pattern =\n /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\\s?([0-9]{4})\\s?([0-9]{4})$/\n\n const patternTemplate =\n 'Enter a valid National Grid field number for {{lowerFirst(#title)}} like NG 1234 5678'\n\n return {\n pattern,\n patternErrorMessage: createLowerFirstExpression(patternTemplate),\n requiredMessage: createLowerFirstExpression(\n 'Enter {{lowerFirst(#title)}}'\n )\n }\n }\n\n protected getErrorTemplates() {\n return [\n {\n type: 'pattern',\n template: createLowerFirstExpression(\n 'Enter a valid National Grid field number for {{lowerFirst(#title)}} like NG 1234 5678'\n )\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(\n NationalGridFieldNumberField.prototype\n ) as NationalGridFieldNumberField\n return instance.getAllPossibleErrors()\n }\n}\n"],"mappings":"AAEA,SAASA,iBAAiB;AAC1B,SAASC,0BAA0B;AAEnC,OAAO,MAAMC,4BAA4B,SAASF,iBAAiB,CAAC;EAGxDG,mBAAmBA,CAAA,EAAG;IAC9B;IACA;IACA;IACA,MAAMC,OAAO,GACX,mIAAmI;IAErI,MAAMC,eAAe,GACnB,uFAAuF;IAEzF,OAAO;MACLD,OAAO;MACPE,mBAAmB,EAAEL,0BAA0B,CAACI,eAAe,CAAC;MAChEE,eAAe,EAAEN,0BAA0B,CACzC,8BACF;IACF,CAAC;EACH;EAEUO,iBAAiBA,CAAA,EAAG;IAC5B,OAAO,CACL;MACEC,IAAI,EAAE,SAAS;MACfC,QAAQ,EAAET,0BAA0B,CAClC,uFACF;IACF,CAAC,CACF;EACH;;EAEA;AACF;AACA;EACE,OAAOU,oBAAoBA,CAAA,EAAG;IAC5B,MAAMC,QAAQ,GAAGC,MAAM,CAACC,MAAM,CAC5BZ,4BAA4B,CAACa,SAC/B,CAAiC;IACjC,OAAOH,QAAQ,CAACD,oBAAoB,CAAC,CAAC;EACxC;AACF","ignoreList":[]}
@@ -4,12 +4,12 @@ export declare class OsGridRefField extends LocationFieldBase {
4
4
  options: OsGridRefFieldComponent['options'];
5
5
  protected getValidationConfig(): {
6
6
  pattern: RegExp;
7
- patternErrorMessage: string;
8
- requiredMessage: string;
7
+ patternErrorMessage: import("joi").JoiExpression;
8
+ requiredMessage: import("joi").JoiExpression;
9
9
  };
10
10
  protected getErrorTemplates(): {
11
11
  type: string;
12
- template: string;
12
+ template: import("joi").JoiExpression;
13
13
  }[];
14
14
  /**
15
15
  * Static version of getAllPossibleErrors that doesn't require a component instance.
@@ -1,4 +1,5 @@
1
1
  import { LocationFieldBase } from "./LocationFieldBase.js";
2
+ import { createLowerFirstExpression } from "./helpers/index.js";
2
3
  export class OsGridRefField extends LocationFieldBase {
3
4
  getValidationConfig() {
4
5
  // Regex for OS national grid references (NGR)
@@ -8,16 +9,17 @@ export class OsGridRefField extends LocationFieldBase {
8
9
  // - 2 letters & 10 digits in 2 blocks of 5 e.g. SO 12345 12345
9
10
  // Optional spaces between each block
10
11
  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})|([0-9]{4})\s?([0-9]{4})|([0-9]{5})\s?([0-9]{5}))$/;
12
+ const patternTemplate = 'Enter a valid OS grid reference for {{lowerFirst(#title)}} like TQ123456';
11
13
  return {
12
14
  pattern,
13
- patternErrorMessage: `Enter a valid OS grid reference for {{#title}} like TQ123456`,
14
- requiredMessage: 'Enter {{#title}}'
15
+ patternErrorMessage: createLowerFirstExpression(patternTemplate),
16
+ requiredMessage: createLowerFirstExpression('Enter {{lowerFirst(#title)}}')
15
17
  };
16
18
  }
17
19
  getErrorTemplates() {
18
20
  return [{
19
21
  type: 'pattern',
20
- template: 'Enter a valid OS grid reference for {{#title}} like TQ123456'
22
+ template: createLowerFirstExpression('Enter a valid OS grid reference for {{lowerFirst(#title)}} like TQ123456')
21
23
  }];
22
24
  }
23
25
 
@@ -1 +1 @@
1
- {"version":3,"file":"OsGridRefField.js","names":["LocationFieldBase","OsGridRefField","getValidationConfig","pattern","patternErrorMessage","requiredMessage","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 national grid references (NGR)\n // Validates specific valid OS grid letter combinations with:\n // - 2 letters & 6 digits in 2 blocks of 3 e.g. ST 678 678\n // - 2 letters & 8 digits in 2 blocks of 4 e.g. ST 6789 6789\n // - 2 letters & 10 digits in 2 blocks of 5 e.g. SO 12345 12345\n // Optional spaces between each block\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})|([0-9]{4})\\s?([0-9]{4})|([0-9]{5})\\s?([0-9]{5}))$/\n\n return {\n pattern,\n patternErrorMessage: `Enter a valid OS grid reference for {{#title}} like TQ123456`,\n requiredMessage: 'Enter {{#title}}'\n }\n }\n\n protected getErrorTemplates() {\n return [\n {\n type: 'pattern',\n template: 'Enter a valid OS grid reference for {{#title}} 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;IACA;IACA;IACA,MAAMC,OAAO,GACX,qLAAqL;IAEvL,OAAO;MACLA,OAAO;MACPC,mBAAmB,EAAE,8DAA8D;MACnFC,eAAe,EAAE;IACnB,CAAC;EACH;EAEUC,iBAAiBA,CAAA,EAAG;IAC5B,OAAO,CACL;MACEC,IAAI,EAAE,SAAS;MACfC,QAAQ,EAAE;IACZ,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
+ {"version":3,"file":"OsGridRefField.js","names":["LocationFieldBase","createLowerFirstExpression","OsGridRefField","getValidationConfig","pattern","patternTemplate","patternErrorMessage","requiredMessage","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'\nimport { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'\n\nexport class OsGridRefField extends LocationFieldBase {\n declare options: OsGridRefFieldComponent['options']\n\n protected getValidationConfig() {\n // Regex for OS national grid references (NGR)\n // Validates specific valid OS grid letter combinations with:\n // - 2 letters & 6 digits in 2 blocks of 3 e.g. ST 678 678\n // - 2 letters & 8 digits in 2 blocks of 4 e.g. ST 6789 6789\n // - 2 letters & 10 digits in 2 blocks of 5 e.g. SO 12345 12345\n // Optional spaces between each block\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})|([0-9]{4})\\s?([0-9]{4})|([0-9]{5})\\s?([0-9]{5}))$/\n\n const patternTemplate =\n 'Enter a valid OS grid reference for {{lowerFirst(#title)}} like TQ123456'\n\n return {\n pattern,\n patternErrorMessage: createLowerFirstExpression(patternTemplate),\n requiredMessage: createLowerFirstExpression(\n 'Enter {{lowerFirst(#title)}}'\n )\n }\n }\n\n protected getErrorTemplates() {\n return [\n {\n type: 'pattern',\n template: createLowerFirstExpression(\n 'Enter a valid OS grid reference for {{lowerFirst(#title)}} like TQ123456'\n )\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;AAC1B,SAASC,0BAA0B;AAEnC,OAAO,MAAMC,cAAc,SAASF,iBAAiB,CAAC;EAG1CG,mBAAmBA,CAAA,EAAG;IAC9B;IACA;IACA;IACA;IACA;IACA;IACA,MAAMC,OAAO,GACX,qLAAqL;IAEvL,MAAMC,eAAe,GACnB,0EAA0E;IAE5E,OAAO;MACLD,OAAO;MACPE,mBAAmB,EAAEL,0BAA0B,CAACI,eAAe,CAAC;MAChEE,eAAe,EAAEN,0BAA0B,CACzC,8BACF;IACF,CAAC;EACH;EAEUO,iBAAiBA,CAAA,EAAG;IAC5B,OAAO,CACL;MACEC,IAAI,EAAE,SAAS;MACfC,QAAQ,EAAET,0BAA0B,CAClC,0EACF;IACF,CAAC,CACF;EACH;;EAEA;AACF;AACA;EACE,OAAOU,oBAAoBA,CAAA,EAAG;IAC5B,MAAMC,QAAQ,GAAGC,MAAM,CAACC,MAAM,CAACZ,cAAc,CAACa,SAAS,CAAmB;IAC1E,OAAOH,QAAQ,CAACD,oBAAoB,CAAC,CAAC;EACxC;AACF","ignoreList":[]}
@@ -1,4 +1,5 @@
1
1
  import { type ComponentDef } from '@defra/forms-model';
2
+ import { type JoiExpression, type ReferenceOptions } from 'joi';
2
3
  /**
3
4
  * Prevent Markdown formatting
4
5
  * @see {@link https://pandoc.org/chunkedhtml-demo/8.11-backslash-escapes.html}
@@ -9,3 +10,12 @@ export declare const addClassOptionIfNone: (options: Extract<ComponentDef, {
9
10
  classes?: string;
10
11
  };
11
12
  }>["options"], className: string) => void;
13
+ /**
14
+ * Configuration for Joi expressions that use lowerFirst function
15
+ */
16
+ export declare const lowerFirstExpressionOptions: ReferenceOptions;
17
+ /**
18
+ * Creates a Joi expression with lowerFirst function support
19
+ * Used for error messages in location field components
20
+ */
21
+ export declare const createLowerFirstExpression: (template: string) => JoiExpression;
@@ -1,3 +1,6 @@
1
+ import joi from 'joi';
2
+ import lowerFirst from 'lodash/lowerFirst.js';
3
+
1
4
  /**
2
5
  * Prevent Markdown formatting
3
6
  * @see {@link https://pandoc.org/chunkedhtml-demo/8.11-backslash-escapes.html}
@@ -12,4 +15,19 @@ export function escapeMarkdown(answer) {
12
15
  export const addClassOptionIfNone = (options, className) => {
13
16
  options.classes ??= className;
14
17
  };
18
+
19
+ /**
20
+ * Configuration for Joi expressions that use lowerFirst function
21
+ */
22
+ export const lowerFirstExpressionOptions = {
23
+ functions: {
24
+ lowerFirst
25
+ }
26
+ };
27
+
28
+ /**
29
+ * Creates a Joi expression with lowerFirst function support
30
+ * Used for error messages in location field components
31
+ */
32
+ export const createLowerFirstExpression = template => joi.expression(template, lowerFirstExpressionOptions);
15
33
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["escapeMarkdown","answer","punctuation","character","replaceAll","addClassOptionIfNone","options","className","classes"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/index.ts"],"sourcesContent":["import { type ComponentDef } from '@defra/forms-model'\n\n/**\n * Prevent Markdown formatting\n * @see {@link https://pandoc.org/chunkedhtml-demo/8.11-backslash-escapes.html}\n */\nexport function escapeMarkdown(answer: string) {\n const punctuation = [\n '`',\n \"'\",\n '*',\n '_',\n '{',\n '}',\n '[',\n ']',\n '(',\n ')',\n '#',\n '+',\n '-',\n '.',\n '!'\n ]\n\n for (const character of punctuation) {\n answer = answer.replaceAll(character, `\\\\${character}`)\n }\n\n return answer\n}\n\nexport const addClassOptionIfNone = (\n options: Extract<ComponentDef, { options: { classes?: string } }>['options'],\n className: string\n) => {\n options.classes ??= className\n}\n"],"mappings":"AAEA;AACA;AACA;AACA;AACA,OAAO,SAASA,cAAcA,CAACC,MAAc,EAAE;EAC7C,MAAMC,WAAW,GAAG,CAClB,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,CACJ;EAED,KAAK,MAAMC,SAAS,IAAID,WAAW,EAAE;IACnCD,MAAM,GAAGA,MAAM,CAACG,UAAU,CAACD,SAAS,EAAE,KAAKA,SAAS,EAAE,CAAC;EACzD;EAEA,OAAOF,MAAM;AACf;AAEA,OAAO,MAAMI,oBAAoB,GAAGA,CAClCC,OAA4E,EAC5EC,SAAiB,KACd;EACHD,OAAO,CAACE,OAAO,KAAKD,SAAS;AAC/B,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["joi","lowerFirst","escapeMarkdown","answer","punctuation","character","replaceAll","addClassOptionIfNone","options","className","classes","lowerFirstExpressionOptions","functions","createLowerFirstExpression","template","expression"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/index.ts"],"sourcesContent":["import { type ComponentDef } from '@defra/forms-model'\nimport joi, { type JoiExpression, type ReferenceOptions } from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\n/**\n * Prevent Markdown formatting\n * @see {@link https://pandoc.org/chunkedhtml-demo/8.11-backslash-escapes.html}\n */\nexport function escapeMarkdown(answer: string) {\n const punctuation = [\n '`',\n \"'\",\n '*',\n '_',\n '{',\n '}',\n '[',\n ']',\n '(',\n ')',\n '#',\n '+',\n '-',\n '.',\n '!'\n ]\n\n for (const character of punctuation) {\n answer = answer.replaceAll(character, `\\\\${character}`)\n }\n\n return answer\n}\n\nexport const addClassOptionIfNone = (\n options: Extract<ComponentDef, { options: { classes?: string } }>['options'],\n className: string\n) => {\n options.classes ??= className\n}\n\n/**\n * Configuration for Joi expressions that use lowerFirst function\n */\nexport const lowerFirstExpressionOptions = {\n functions: {\n lowerFirst\n }\n} as ReferenceOptions\n\n/**\n * Creates a Joi expression with lowerFirst function support\n * Used for error messages in location field components\n */\nexport const createLowerFirstExpression = (template: string): JoiExpression =>\n joi.expression(template, lowerFirstExpressionOptions) as JoiExpression\n"],"mappings":"AACA,OAAOA,GAAG,MAAqD,KAAK;AACpE,OAAOC,UAAU,MAAM,sBAAsB;;AAE7C;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAACC,MAAc,EAAE;EAC7C,MAAMC,WAAW,GAAG,CAClB,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,CACJ;EAED,KAAK,MAAMC,SAAS,IAAID,WAAW,EAAE;IACnCD,MAAM,GAAGA,MAAM,CAACG,UAAU,CAACD,SAAS,EAAE,KAAKA,SAAS,EAAE,CAAC;EACzD;EAEA,OAAOF,MAAM;AACf;AAEA,OAAO,MAAMI,oBAAoB,GAAGA,CAClCC,OAA4E,EAC5EC,SAAiB,KACd;EACHD,OAAO,CAACE,OAAO,KAAKD,SAAS;AAC/B,CAAC;;AAED;AACA;AACA;AACA,OAAO,MAAME,2BAA2B,GAAG;EACzCC,SAAS,EAAE;IACTX;EACF;AACF,CAAqB;;AAErB;AACA;AACA;AACA;AACA,OAAO,MAAMY,0BAA0B,GAAIC,QAAgB,IACzDd,GAAG,CAACe,UAAU,CAACD,QAAQ,EAAEH,2BAA2B,CAAkB","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "4.0.23",
3
+ "version": "4.0.24",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -416,7 +416,36 @@ describe('EastingNorthingField', () => {
416
416
  const staticResult = EastingNorthingField.getAllPossibleErrors()
417
417
  const instanceResult = field.getAllPossibleErrors()
418
418
 
419
- expect(instanceResult).toEqual(staticResult)
419
+ // Compare structure and content
420
+ expect(instanceResult.baseErrors).toHaveLength(
421
+ staticResult.baseErrors.length
422
+ )
423
+ expect(instanceResult.advancedSettingsErrors).toHaveLength(
424
+ staticResult.advancedSettingsErrors.length
425
+ )
426
+
427
+ // Compare error types
428
+ expect(instanceResult.baseErrors.map((e) => e.type)).toEqual(
429
+ staticResult.baseErrors.map((e) => e.type)
430
+ )
431
+ expect(
432
+ instanceResult.advancedSettingsErrors.map((e) => e.type)
433
+ ).toEqual(staticResult.advancedSettingsErrors.map((e) => e.type))
434
+
435
+ // Compare rendered templates
436
+ expect(
437
+ instanceResult.baseErrors.map((e) =>
438
+ typeof e.template === 'object' && 'rendered' in e.template
439
+ ? e.template.rendered
440
+ : e.template
441
+ )
442
+ ).toEqual(
443
+ staticResult.baseErrors.map((e) =>
444
+ typeof e.template === 'object' && 'rendered' in e.template
445
+ ? e.template.rendered
446
+ : e.template
447
+ )
448
+ )
420
449
  })
421
450
  })
422
451
  })
@@ -15,6 +15,7 @@ import {
15
15
  getLocationFieldViewModel
16
16
  } from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'
17
17
  import { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'
18
+ import { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'
18
19
  import { type EastingNorthingState } from '~/src/server/plugins/engine/components/types.js'
19
20
  import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
20
21
  import {
@@ -198,29 +199,41 @@ export class EastingNorthingField extends FormComponent {
198
199
  { type: 'required', template: messageTemplate.required },
199
200
  {
200
201
  type: 'eastingFormat',
201
- template: 'Easting for {{#title}} must be between 1 and 6 digits'
202
+ template: createLowerFirstExpression(
203
+ 'Easting for {{lowerFirst(#title)}} must be between 1 and 6 digits'
204
+ )
202
205
  },
203
206
  {
204
207
  type: 'northingFormat',
205
- template: 'Northing for {{#title}} must be between 1 and 7 digits'
208
+ template: createLowerFirstExpression(
209
+ 'Northing for {{lowerFirst(#title)}} must be between 1 and 7 digits'
210
+ )
206
211
  }
207
212
  ],
208
213
  advancedSettingsErrors: [
209
214
  {
210
215
  type: 'eastingMin',
211
- template: `Easting for {{#title}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
216
+ template: createLowerFirstExpression(
217
+ `Easting for {{lowerFirst(#title)}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
218
+ )
212
219
  },
213
220
  {
214
221
  type: 'eastingMax',
215
- template: `Easting for {{#title}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
222
+ template: createLowerFirstExpression(
223
+ `Easting for {{lowerFirst(#title)}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
224
+ )
216
225
  },
217
226
  {
218
227
  type: 'northingMin',
219
- template: `Northing for {{#title}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
228
+ template: createLowerFirstExpression(
229
+ `Northing for {{lowerFirst(#title)}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
230
+ )
220
231
  },
221
232
  {
222
233
  type: 'northingMax',
223
- template: `Northing for {{#title}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
234
+ template: createLowerFirstExpression(
235
+ `Northing for {{lowerFirst(#title)}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
236
+ )
224
237
  }
225
238
  ]
226
239
  }
@@ -404,7 +404,36 @@ describe('LatLongField', () => {
404
404
  const staticResult = LatLongField.getAllPossibleErrors()
405
405
  const instanceResult = field.getAllPossibleErrors()
406
406
 
407
- expect(instanceResult).toEqual(staticResult)
407
+ // Compare structure and content
408
+ expect(instanceResult.baseErrors).toHaveLength(
409
+ staticResult.baseErrors.length
410
+ )
411
+ expect(instanceResult.advancedSettingsErrors).toHaveLength(
412
+ staticResult.advancedSettingsErrors.length
413
+ )
414
+
415
+ // Compare error types
416
+ expect(instanceResult.baseErrors.map((e) => e.type)).toEqual(
417
+ staticResult.baseErrors.map((e) => e.type)
418
+ )
419
+ expect(
420
+ instanceResult.advancedSettingsErrors.map((e) => e.type)
421
+ ).toEqual(staticResult.advancedSettingsErrors.map((e) => e.type))
422
+
423
+ // Compare rendered templates
424
+ expect(
425
+ instanceResult.baseErrors.map((e) =>
426
+ typeof e.template === 'object' && 'rendered' in e.template
427
+ ? e.template.rendered
428
+ : e.template
429
+ )
430
+ ).toEqual(
431
+ staticResult.baseErrors.map((e) =>
432
+ typeof e.template === 'object' && 'rendered' in e.template
433
+ ? e.template.rendered
434
+ : e.template
435
+ )
436
+ )
408
437
  })
409
438
  })
410
439
  })
@@ -12,6 +12,7 @@ import {
12
12
  getLocationFieldViewModel
13
13
  } from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'
14
14
  import { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'
15
+ import { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'
15
16
  import { type LatLongState } from '~/src/server/plugins/engine/components/types.js'
16
17
  import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
17
18
  import {
@@ -194,29 +195,41 @@ export class LatLongField extends FormComponent {
194
195
  { type: 'required', template: messageTemplate.required },
195
196
  {
196
197
  type: 'latitudeFormat',
197
- template: 'Enter a valid latitude for {{#title}} like 51.519450'
198
+ template: createLowerFirstExpression(
199
+ 'Enter a valid latitude for {{lowerFirst(#title)}} like 51.519450'
200
+ )
198
201
  },
199
202
  {
200
203
  type: 'longitudeFormat',
201
- template: 'Enter a valid longitude for {{#title}} like -0.127758'
204
+ template: createLowerFirstExpression(
205
+ 'Enter a valid longitude for {{lowerFirst(#title)}} like -0.127758'
206
+ )
202
207
  }
203
208
  ],
204
209
  advancedSettingsErrors: [
205
210
  {
206
211
  type: 'latitudeMin',
207
- template: 'Latitude for {{#title}} must be between 49 and 60'
212
+ template: createLowerFirstExpression(
213
+ 'Latitude for {{lowerFirst(#title)}} must be between 49 and 60'
214
+ )
208
215
  },
209
216
  {
210
217
  type: 'latitudeMax',
211
- template: 'Latitude for {{#title}} must be between 49 and 60'
218
+ template: createLowerFirstExpression(
219
+ 'Latitude for {{lowerFirst(#title)}} must be between 49 and 60'
220
+ )
212
221
  },
213
222
  {
214
223
  type: 'longitudeMin',
215
- template: 'Longitude for {{#title}} must be between -9 and 2'
224
+ template: createLowerFirstExpression(
225
+ 'Longitude for {{lowerFirst(#title)}} must be between -9 and 2'
226
+ )
216
227
  },
217
228
  {
218
229
  type: 'longitudeMax',
219
- template: 'Longitude for {{#title}} must be between -9 and 2'
230
+ template: createLowerFirstExpression(
231
+ 'Longitude for {{lowerFirst(#title)}} must be between -9 and 2'
232
+ )
220
233
  }
221
234
  ]
222
235
  }
@@ -1,5 +1,9 @@
1
1
  import { type FormComponentsDef } from '@defra/forms-model'
2
- import joi, { type LanguageMessages, type StringSchema } from 'joi'
2
+ import joi, {
3
+ type JoiExpression,
4
+ type LanguageMessages,
5
+ type StringSchema
6
+ } from 'joi'
3
7
 
4
8
  import {
5
9
  FormComponent,
@@ -15,6 +19,7 @@ import {
15
19
  type FormSubmissionError,
16
20
  type FormSubmissionState
17
21
  } from '~/src/server/plugins/engine/types.js'
22
+ import { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'
18
23
 
19
24
  interface LocationFieldOptions {
20
25
  instructionText?: string
@@ -26,8 +31,8 @@ interface LocationFieldOptions {
26
31
 
27
32
  interface ValidationConfig {
28
33
  pattern: RegExp
29
- patternErrorMessage: string
30
- requiredMessage?: string
34
+ patternErrorMessage: JoiExpression
35
+ requiredMessage?: JoiExpression
31
36
  }
32
37
 
33
38
  /**
@@ -42,7 +47,7 @@ export abstract class LocationFieldBase extends FormComponent {
42
47
  protected abstract getValidationConfig(): ValidationConfig
43
48
  protected abstract getErrorTemplates(): {
44
49
  type: string
45
- template: string
50
+ template: JoiExpression
46
51
  }[]
47
52
 
48
53
  constructor(
@@ -61,11 +66,11 @@ export abstract class LocationFieldBase extends FormComponent {
61
66
  const requiredMessage =
62
67
  config.requiredMessage ?? (messageTemplate.required as string)
63
68
 
64
- const messages: LanguageMessages = {
69
+ const messages = convertToLanguageMessages({
65
70
  'any.required': requiredMessage,
66
71
  'string.empty': requiredMessage,
67
72
  'string.pattern.base': config.patternErrorMessage
68
- }
73
+ })
69
74
 
70
75
  let formSchema = joi
71
76
  .string()
@@ -129,7 +129,7 @@ describe('NationalGridFieldNumberField', () => {
129
129
 
130
130
  expect(result.errors).toEqual([
131
131
  expect.objectContaining({
132
- text: 'Enter Example National Grid field number'
132
+ text: 'Enter example National Grid field number'
133
133
  })
134
134
  ])
135
135
  })
@@ -293,7 +293,7 @@ describe('NationalGridFieldNumberField', () => {
293
293
  value: getFormData('NG1234567'),
294
294
  errors: expect.arrayContaining([
295
295
  expect.objectContaining({
296
- text: 'Enter a valid National Grid field number for Grid field like NG 1234 5678'
296
+ text: 'Enter a valid National Grid field number for grid field like NG 1234 5678'
297
297
  })
298
298
  ])
299
299
  }
@@ -304,7 +304,7 @@ describe('NationalGridFieldNumberField', () => {
304
304
  value: getFormData('N123456789'),
305
305
  errors: expect.arrayContaining([
306
306
  expect.objectContaining({
307
- text: 'Enter a valid National Grid field number for Grid field like NG 1234 5678'
307
+ text: 'Enter a valid National Grid field number for grid field like NG 1234 5678'
308
308
  })
309
309
  ])
310
310
  }
@@ -315,7 +315,7 @@ describe('NationalGridFieldNumberField', () => {
315
315
  value: getFormData('NGABCDEFGH'),
316
316
  errors: expect.arrayContaining([
317
317
  expect.objectContaining({
318
- text: 'Enter a valid National Grid field number for Grid field like NG 1234 5678'
318
+ text: 'Enter a valid National Grid field number for grid field like NG 1234 5678'
319
319
  })
320
320
  ])
321
321
  }
@@ -1,6 +1,7 @@
1
1
  import { type NationalGridFieldNumberFieldComponent } from '@defra/forms-model'
2
2
 
3
3
  import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'
4
+ import { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'
4
5
 
5
6
  export class NationalGridFieldNumberField extends LocationFieldBase {
6
7
  declare options: NationalGridFieldNumberFieldComponent['options']
@@ -12,10 +13,15 @@ export class NationalGridFieldNumberField extends LocationFieldBase {
12
13
  const pattern =
13
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})$/
14
15
 
16
+ const patternTemplate =
17
+ 'Enter a valid National Grid field number for {{lowerFirst(#title)}} like NG 1234 5678'
18
+
15
19
  return {
16
20
  pattern,
17
- patternErrorMessage: `Enter a valid National Grid field number for {{#title}} like NG 1234 5678`,
18
- requiredMessage: 'Enter {{#title}}'
21
+ patternErrorMessage: createLowerFirstExpression(patternTemplate),
22
+ requiredMessage: createLowerFirstExpression(
23
+ 'Enter {{lowerFirst(#title)}}'
24
+ )
19
25
  }
20
26
  }
21
27
 
@@ -23,8 +29,9 @@ export class NationalGridFieldNumberField extends LocationFieldBase {
23
29
  return [
24
30
  {
25
31
  type: 'pattern',
26
- template:
27
- 'Enter a valid National Grid field number for {{#title}} like NG 1234 5678'
32
+ template: createLowerFirstExpression(
33
+ 'Enter a valid National Grid field number for {{lowerFirst(#title)}} like NG 1234 5678'
34
+ )
28
35
  }
29
36
  ]
30
37
  }
@@ -135,7 +135,7 @@ describe('OsGridRefField', () => {
135
135
 
136
136
  expect(result.errors).toEqual([
137
137
  expect.objectContaining({
138
- text: 'Enter Example OS grid reference'
138
+ text: 'Enter example OS grid reference'
139
139
  })
140
140
  ])
141
141
  })
@@ -308,7 +308,7 @@ describe('OsGridRefField', () => {
308
308
  value: getFormData('TQ12345'),
309
309
  errors: expect.arrayContaining([
310
310
  expect.objectContaining({
311
- text: 'Enter a valid OS grid reference for Grid reference like TQ123456'
311
+ text: 'Enter a valid OS grid reference for grid reference like TQ123456'
312
312
  })
313
313
  ])
314
314
  }
@@ -319,7 +319,7 @@ describe('OsGridRefField', () => {
319
319
  value: getFormData('AA1234567'),
320
320
  errors: expect.arrayContaining([
321
321
  expect.objectContaining({
322
- text: 'Enter a valid OS grid reference for Grid reference like TQ123456'
322
+ text: 'Enter a valid OS grid reference for grid reference like TQ123456'
323
323
  })
324
324
  ])
325
325
  }
@@ -330,7 +330,7 @@ describe('OsGridRefField', () => {
330
330
  value: getFormData('TQABCDEF'),
331
331
  errors: expect.arrayContaining([
332
332
  expect.objectContaining({
333
- text: 'Enter a valid OS grid reference for Grid reference like TQ123456'
333
+ text: 'Enter a valid OS grid reference for grid reference like TQ123456'
334
334
  })
335
335
  ])
336
336
  }
@@ -1,6 +1,7 @@
1
1
  import { type OsGridRefFieldComponent } from '@defra/forms-model'
2
2
 
3
3
  import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'
4
+ import { createLowerFirstExpression } from '~/src/server/plugins/engine/components/helpers/index.js'
4
5
 
5
6
  export class OsGridRefField extends LocationFieldBase {
6
7
  declare options: OsGridRefFieldComponent['options']
@@ -15,10 +16,15 @@ export class OsGridRefField extends LocationFieldBase {
15
16
  const pattern =
16
17
  /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\s?(([0-9]{3})\s?([0-9]{3})|([0-9]{4})\s?([0-9]{4})|([0-9]{5})\s?([0-9]{5}))$/
17
18
 
19
+ const patternTemplate =
20
+ 'Enter a valid OS grid reference for {{lowerFirst(#title)}} like TQ123456'
21
+
18
22
  return {
19
23
  pattern,
20
- patternErrorMessage: `Enter a valid OS grid reference for {{#title}} like TQ123456`,
21
- requiredMessage: 'Enter {{#title}}'
24
+ patternErrorMessage: createLowerFirstExpression(patternTemplate),
25
+ requiredMessage: createLowerFirstExpression(
26
+ 'Enter {{lowerFirst(#title)}}'
27
+ )
22
28
  }
23
29
  }
24
30
 
@@ -26,7 +32,9 @@ export class OsGridRefField extends LocationFieldBase {
26
32
  return [
27
33
  {
28
34
  type: 'pattern',
29
- template: 'Enter a valid OS grid reference for {{#title}} like TQ123456'
35
+ template: createLowerFirstExpression(
36
+ 'Enter a valid OS grid reference for {{lowerFirst(#title)}} like TQ123456'
37
+ )
30
38
  }
31
39
  ]
32
40
  }
@@ -6,6 +6,10 @@ import { LatLongField } from '~/src/server/plugins/engine/components/LatLongFiel
6
6
  import { NationalGridFieldNumberField } from '~/src/server/plugins/engine/components/NationalGridFieldNumberField.js'
7
7
  import { OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'
8
8
  import { createComponent } from '~/src/server/plugins/engine/components/helpers/components.js'
9
+ import {
10
+ createLowerFirstExpression,
11
+ lowerFirstExpressionOptions
12
+ } from '~/src/server/plugins/engine/components/helpers/index.js'
9
13
  import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
10
14
  import definition from '~/test/form/definitions/basic.js'
11
15
 
@@ -123,3 +127,39 @@ describe('ComponentBase tests', () => {
123
127
  expect(component.title).toBe('Context Field')
124
128
  })
125
129
  })
130
+
131
+ describe('lowerFirst expression helpers', () => {
132
+ test('lowerFirstExpressionOptions should have lowerFirst function', () => {
133
+ expect(lowerFirstExpressionOptions).toHaveProperty('functions')
134
+ expect(lowerFirstExpressionOptions).toHaveProperty(
135
+ 'functions.lowerFirst',
136
+ expect.any(Function)
137
+ )
138
+ })
139
+
140
+ test('createLowerFirstExpression should create a Joi expression', () => {
141
+ const template = 'Enter {{lowerFirst(#title)}}'
142
+ const expression = createLowerFirstExpression(template)
143
+
144
+ expect(expression).toBeDefined()
145
+ expect(typeof expression).toBe('object')
146
+ expect(expression).toHaveProperty('_template')
147
+ })
148
+
149
+ test('createLowerFirstExpression should render template with lowerFirst', () => {
150
+ const template = 'Enter {{lowerFirst(#title)}}'
151
+ const expression = createLowerFirstExpression(template)
152
+
153
+ // Check the rendered template is stored
154
+ expect(expression).toHaveProperty('rendered', template)
155
+ })
156
+
157
+ test('createLowerFirstExpression should support multiple interpolations', () => {
158
+ const template =
159
+ 'Easting for {{lowerFirst(#title)}} must be between {{#min}} and {{#max}}'
160
+ const expression = createLowerFirstExpression(template)
161
+
162
+ expect(expression).toBeDefined()
163
+ expect(expression).toHaveProperty('rendered', template)
164
+ })
165
+ })
@@ -1,4 +1,6 @@
1
1
  import { type ComponentDef } from '@defra/forms-model'
2
+ import joi, { type JoiExpression, type ReferenceOptions } from 'joi'
3
+ import lowerFirst from 'lodash/lowerFirst.js'
2
4
 
3
5
  /**
4
6
  * Prevent Markdown formatting
@@ -36,3 +38,19 @@ export const addClassOptionIfNone = (
36
38
  ) => {
37
39
  options.classes ??= className
38
40
  }
41
+
42
+ /**
43
+ * Configuration for Joi expressions that use lowerFirst function
44
+ */
45
+ export const lowerFirstExpressionOptions = {
46
+ functions: {
47
+ lowerFirst
48
+ }
49
+ } as ReferenceOptions
50
+
51
+ /**
52
+ * Creates a Joi expression with lowerFirst function support
53
+ * Used for error messages in location field components
54
+ */
55
+ export const createLowerFirstExpression = (template: string): JoiExpression =>
56
+ joi.expression(template, lowerFirstExpressionOptions) as JoiExpression