@defra/forms-engine-plugin 0.1.11 → 0.1.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/.public/javascripts/file-upload.min.js +1 -1
- package/.public/javascripts/file-upload.min.js.map +1 -1
- package/.server/client/javascripts/file-upload.js +45 -4
- package/.server/client/javascripts/file-upload.js.map +1 -1
- package/.server/server/constants.js +2 -0
- package/.server/server/constants.js.map +1 -1
- package/.server/server/index.js +1 -1
- package/.server/server/index.js.map +1 -1
- package/.server/server/plugins/engine/components/AutocompleteField.js +2 -0
- package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
- package/.server/server/plugins/engine/components/CheckboxesField.js +3 -4
- package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
- package/.server/server/plugins/engine/components/ComponentCollection.js +37 -16
- package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
- package/.server/server/plugins/engine/components/DatePartsField.js +36 -2
- package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
- package/.server/server/plugins/engine/components/EmailAddressField.js +19 -3
- package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
- package/.server/server/plugins/engine/components/FileUploadField.js +44 -4
- package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
- package/.server/server/plugins/engine/components/FormComponent.js +14 -2
- package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/ListFormComponent.js +16 -3
- package/.server/server/plugins/engine/components/ListFormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/Markdown.js +24 -0
- package/.server/server/plugins/engine/components/Markdown.js.map +1 -0
- package/.server/server/plugins/engine/components/MonthYearField.js +30 -2
- package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
- package/.server/server/plugins/engine/components/MultilineTextField.js +32 -3
- package/.server/server/plugins/engine/components/MultilineTextField.js.map +1 -1
- package/.server/server/plugins/engine/components/NumberField.js +28 -3
- package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/SelectionControlField.js +14 -0
- package/.server/server/plugins/engine/components/SelectionControlField.js.map +1 -1
- package/.server/server/plugins/engine/components/TelephoneNumberField.js +19 -3
- package/.server/server/plugins/engine/components/TelephoneNumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/TextField.js +22 -3
- package/.server/server/plugins/engine/components/TextField.js.map +1 -1
- package/.server/server/plugins/engine/components/UkAddressField.js +29 -0
- package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
- package/.server/server/plugins/engine/components/YesNoField.js +18 -0
- package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers.js +16 -0
- package/.server/server/plugins/engine/components/helpers.js.map +1 -1
- package/.server/server/plugins/engine/components/index.js +1 -0
- package/.server/server/plugins/engine/components/index.js.map +1 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js +3 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
- package/.server/server/plugins/engine/helpers.js +38 -18
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.js +60 -2
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js +3 -2
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/PageController.js +13 -5
- package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +2 -2
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +19 -5
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/validationOptions.js +6 -11
- package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
- package/.server/server/plugins/engine/plugin.js +5 -4
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/services/notifyService.js +1 -4
- package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
- package/.server/server/plugins/engine/services/uploadService.js +5 -3
- package/.server/server/plugins/engine/services/uploadService.js.map +1 -1
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/components/html.html +1 -1
- package/.server/server/plugins/engine/views/components/markdown.html +5 -0
- package/.server/server/plugins/engine/views/summary.html +7 -1
- package/.server/server/plugins/nunjucks/context.js +6 -5
- package/.server/server/plugins/nunjucks/context.js.map +1 -1
- package/.server/server/plugins/nunjucks/enviroment.test.js +6 -3
- package/.server/server/plugins/nunjucks/enviroment.test.js.map +1 -1
- package/.server/server/utils/type-utils.js +8 -0
- package/.server/server/utils/type-utils.js.map +1 -0
- package/.server/typings/joi/index.d.js.map +1 -1
- package/package.json +3 -3
- package/src/client/javascripts/file-upload.js +60 -4
- package/src/server/constants.js +2 -0
- package/src/server/index.test.ts +34 -29
- package/src/server/index.ts +2 -1
- package/src/server/plugins/engine/components/AutocompleteField.test.ts +71 -3
- package/src/server/plugins/engine/components/AutocompleteField.ts +6 -2
- package/src/server/plugins/engine/components/CheckboxesField.test.ts +40 -8
- package/src/server/plugins/engine/components/CheckboxesField.ts +7 -3
- package/src/server/plugins/engine/components/ComponentCollection.ts +45 -18
- package/src/server/plugins/engine/components/DatePartsField.test.ts +13 -4
- package/src/server/plugins/engine/components/DatePartsField.ts +29 -8
- package/src/server/plugins/engine/components/EmailAddressField.test.ts +51 -1
- package/src/server/plugins/engine/components/EmailAddressField.ts +17 -2
- package/src/server/plugins/engine/components/FileUploadField.test.ts +53 -0
- package/src/server/plugins/engine/components/FileUploadField.ts +52 -3
- package/src/server/plugins/engine/components/FormComponent.ts +24 -2
- package/src/server/plugins/engine/components/ListFormComponent.ts +16 -2
- package/src/server/plugins/engine/components/Markdown.test.ts +48 -0
- package/src/server/plugins/engine/components/Markdown.ts +29 -0
- package/src/server/plugins/engine/components/MonthYearField.test.ts +35 -0
- package/src/server/plugins/engine/components/MonthYearField.ts +34 -9
- package/src/server/plugins/engine/components/MultilineTextField.test.ts +83 -5
- package/src/server/plugins/engine/components/MultilineTextField.ts +37 -2
- package/src/server/plugins/engine/components/NumberField.test.ts +24 -2
- package/src/server/plugins/engine/components/NumberField.ts +23 -3
- package/src/server/plugins/engine/components/RadiosField.test.ts +10 -1
- package/src/server/plugins/engine/components/SelectField.test.ts +2 -1
- package/src/server/plugins/engine/components/SelectionControlField.ts +14 -0
- package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +30 -2
- package/src/server/plugins/engine/components/TelephoneNumberField.ts +17 -2
- package/src/server/plugins/engine/components/TextField.test.ts +33 -1
- package/src/server/plugins/engine/components/TextField.ts +17 -2
- package/src/server/plugins/engine/components/UkAddressField.test.ts +46 -3
- package/src/server/plugins/engine/components/UkAddressField.ts +28 -0
- package/src/server/plugins/engine/components/YesNoField.test.ts +9 -1
- package/src/server/plugins/engine/components/YesNoField.ts +24 -0
- package/src/server/plugins/engine/components/helpers.test.ts +24 -0
- package/src/server/plugins/engine/components/helpers.ts +39 -0
- package/src/server/plugins/engine/components/index.ts +1 -0
- package/src/server/plugins/engine/configureEnginePlugin.ts +13 -3
- package/src/server/plugins/engine/helpers.test.ts +71 -20
- package/src/server/plugins/engine/helpers.ts +46 -19
- package/src/server/plugins/engine/models/FormModel.test.ts +91 -1
- package/src/server/plugins/engine/models/FormModel.ts +86 -3
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +46 -7
- package/src/server/plugins/engine/models/SummaryViewModel.ts +7 -3
- package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +1 -2
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +1 -1
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1 -0
- package/src/server/plugins/engine/pageControllers/PageController.test.ts +9 -6
- package/src/server/plugins/engine/pageControllers/PageController.ts +15 -5
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +2 -2
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +21 -6
- package/src/server/plugins/engine/pageControllers/validationOptions.ts +31 -17
- package/src/server/plugins/engine/plugin.ts +9 -5
- package/src/server/plugins/engine/services/notifyService.ts +1 -2
- package/src/server/plugins/engine/services/uploadService.js +10 -6
- package/src/server/plugins/engine/types.ts +10 -1
- package/src/server/plugins/engine/views/components/html.html +1 -1
- package/src/server/plugins/engine/views/components/markdown.html +5 -0
- package/src/server/plugins/engine/views/summary.html +7 -1
- package/src/server/plugins/nunjucks/context.js +4 -4
- package/src/server/plugins/nunjucks/enviroment.test.js +9 -3
- package/src/server/utils/type-utils.ts +15 -0
- package/src/typings/joi/index.d.ts +8 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from '~/src/server/plugins/engine/components/FormComponent.js'
|
|
8
8
|
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
|
|
9
9
|
import {
|
|
10
|
+
type ErrorMessageTemplateList,
|
|
10
11
|
type FormPayload,
|
|
11
12
|
type FormState,
|
|
12
13
|
type FormStateValue,
|
|
@@ -26,12 +27,12 @@ export class NumberField extends FormComponent {
|
|
|
26
27
|
) {
|
|
27
28
|
super(def, props)
|
|
28
29
|
|
|
29
|
-
const { options, schema
|
|
30
|
+
const { options, schema } = def
|
|
30
31
|
|
|
31
32
|
let formSchema = joi
|
|
32
33
|
.number()
|
|
33
34
|
.custom(getValidatorPrecision(this))
|
|
34
|
-
.label(
|
|
35
|
+
.label(this.label)
|
|
35
36
|
.required()
|
|
36
37
|
|
|
37
38
|
if (options.required === false) {
|
|
@@ -40,7 +41,9 @@ export class NumberField extends FormComponent {
|
|
|
40
41
|
const messages = options.customValidationMessages
|
|
41
42
|
|
|
42
43
|
formSchema = formSchema.empty('').messages({
|
|
43
|
-
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
45
|
+
'any.required':
|
|
46
|
+
messages?.['any.required'] ?? (messageTemplate.required as string)
|
|
44
47
|
})
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -129,6 +132,23 @@ export class NumberField extends FormComponent {
|
|
|
129
132
|
return NumberField.isNumber(value)
|
|
130
133
|
}
|
|
131
134
|
|
|
135
|
+
/**
|
|
136
|
+
* For error preview page that shows all possible errors on a component
|
|
137
|
+
*/
|
|
138
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
139
|
+
return {
|
|
140
|
+
baseErrors: [
|
|
141
|
+
{ type: 'required', template: messageTemplate.required },
|
|
142
|
+
{ type: 'numberInteger', template: messageTemplate.numberInteger }
|
|
143
|
+
],
|
|
144
|
+
advancedSettingsErrors: [
|
|
145
|
+
{ type: 'numberMin', template: messageTemplate.numberMin },
|
|
146
|
+
{ type: 'numberMax', template: messageTemplate.numberMax },
|
|
147
|
+
{ type: 'numberPrecision', template: messageTemplate.numberPrecision }
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
132
152
|
static isNumber(value?: FormStateValue | FormState): value is number {
|
|
133
153
|
return typeof value === 'number'
|
|
134
154
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ComponentType, type RadiosFieldComponent } from '@defra/forms-model'
|
|
2
|
+
import lowerFirst from 'lodash/lowerFirst.js'
|
|
2
3
|
|
|
3
4
|
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
4
5
|
import { RadiosField } from '~/src/server/plugins/engine/components/RadiosField.js'
|
|
@@ -151,7 +152,7 @@ describe.each([
|
|
|
151
152
|
|
|
152
153
|
expect(result.errors).toEqual([
|
|
153
154
|
expect.objectContaining({
|
|
154
|
-
text: `Select ${def.title
|
|
155
|
+
text: `Select ${lowerFirst(def.title)}`
|
|
155
156
|
})
|
|
156
157
|
])
|
|
157
158
|
})
|
|
@@ -284,5 +285,13 @@ describe.each([
|
|
|
284
285
|
expect(items).toEqual([])
|
|
285
286
|
})
|
|
286
287
|
})
|
|
288
|
+
|
|
289
|
+
describe('AllPossibleErrors', () => {
|
|
290
|
+
it('should return errors', () => {
|
|
291
|
+
const errors = field.getAllPossibleErrors()
|
|
292
|
+
expect(errors.baseErrors).not.toBeEmpty()
|
|
293
|
+
expect(errors.advancedSettingsErrors).toBeEmpty()
|
|
294
|
+
})
|
|
295
|
+
})
|
|
287
296
|
})
|
|
288
297
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ComponentType, type SelectFieldComponent } from '@defra/forms-model'
|
|
2
|
+
import lowerFirst from 'lodash/lowerFirst.js'
|
|
2
3
|
|
|
3
4
|
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
4
5
|
import { SelectField } from '~/src/server/plugins/engine/components/SelectField.js'
|
|
@@ -152,7 +153,7 @@ describe.each([
|
|
|
152
153
|
|
|
153
154
|
expect(result.errors).toEqual([
|
|
154
155
|
expect.objectContaining({
|
|
155
|
-
text: `Select ${def.title
|
|
156
|
+
text: `Select ${lowerFirst(def.title)}`
|
|
156
157
|
})
|
|
157
158
|
])
|
|
158
159
|
})
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'
|
|
2
2
|
import { type ListItem } from '~/src/server/plugins/engine/components/types.js'
|
|
3
|
+
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
|
|
3
4
|
import {
|
|
5
|
+
type ErrorMessageTemplateList,
|
|
4
6
|
type FormPayload,
|
|
5
7
|
type FormSubmissionError
|
|
6
8
|
} from '~/src/server/plugins/engine/types.js'
|
|
@@ -40,4 +42,16 @@ export class SelectionControlField extends ListFormComponent {
|
|
|
40
42
|
items
|
|
41
43
|
}
|
|
42
44
|
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* For error preview page that shows all possible errors on a component
|
|
48
|
+
*/
|
|
49
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
50
|
+
return {
|
|
51
|
+
baseErrors: [
|
|
52
|
+
{ type: 'selectRequired', template: messageTemplate.selectRequired }
|
|
53
|
+
],
|
|
54
|
+
advancedSettingsErrors: []
|
|
55
|
+
}
|
|
56
|
+
}
|
|
43
57
|
}
|
|
@@ -29,6 +29,7 @@ describe('TelephoneNumberField', () => {
|
|
|
29
29
|
beforeEach(() => {
|
|
30
30
|
def = {
|
|
31
31
|
title: 'Example telephone number field',
|
|
32
|
+
shortDescription: 'Example telephone number',
|
|
32
33
|
name: 'myComponent',
|
|
33
34
|
type: ComponentType.TelephoneNumberField,
|
|
34
35
|
options: {}
|
|
@@ -39,7 +40,7 @@ describe('TelephoneNumberField', () => {
|
|
|
39
40
|
})
|
|
40
41
|
|
|
41
42
|
describe('Schema', () => {
|
|
42
|
-
it('uses component
|
|
43
|
+
it('uses component short description as label', () => {
|
|
43
44
|
const { formSchema } = collection
|
|
44
45
|
const { keys } = formSchema.describe()
|
|
45
46
|
|
|
@@ -47,7 +48,7 @@ describe('TelephoneNumberField', () => {
|
|
|
47
48
|
'myComponent',
|
|
48
49
|
expect.objectContaining({
|
|
49
50
|
flags: expect.objectContaining({
|
|
50
|
-
label: 'Example telephone number
|
|
51
|
+
label: 'Example telephone number'
|
|
51
52
|
})
|
|
52
53
|
})
|
|
53
54
|
)
|
|
@@ -121,6 +122,25 @@ describe('TelephoneNumberField', () => {
|
|
|
121
122
|
it('adds errors for empty value', () => {
|
|
122
123
|
const result = collection.validate(getFormData(''))
|
|
123
124
|
|
|
125
|
+
expect(result.errors).toEqual([
|
|
126
|
+
expect.objectContaining({
|
|
127
|
+
text: 'Enter example telephone number'
|
|
128
|
+
})
|
|
129
|
+
])
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('adds errors for empty value given no short description exists', () => {
|
|
133
|
+
def = {
|
|
134
|
+
title: 'Example telephone number field',
|
|
135
|
+
name: 'myComponent',
|
|
136
|
+
type: ComponentType.TelephoneNumberField,
|
|
137
|
+
options: {}
|
|
138
|
+
} satisfies TelephoneNumberFieldComponent
|
|
139
|
+
|
|
140
|
+
collection = new ComponentCollection([def], { model })
|
|
141
|
+
|
|
142
|
+
const result = collection.validate(getFormData(''))
|
|
143
|
+
|
|
124
144
|
expect(result.errors).toEqual([
|
|
125
145
|
expect.objectContaining({
|
|
126
146
|
text: 'Enter example telephone number field'
|
|
@@ -215,6 +235,14 @@ describe('TelephoneNumberField', () => {
|
|
|
215
235
|
)
|
|
216
236
|
})
|
|
217
237
|
})
|
|
238
|
+
|
|
239
|
+
describe('AllPossibleErrors', () => {
|
|
240
|
+
it('should return errors', () => {
|
|
241
|
+
const errors = field.getAllPossibleErrors()
|
|
242
|
+
expect(errors.baseErrors).not.toBeEmpty()
|
|
243
|
+
expect(errors.advancedSettingsErrors).toBeEmpty()
|
|
244
|
+
})
|
|
245
|
+
})
|
|
218
246
|
})
|
|
219
247
|
|
|
220
248
|
describe('Validation', () => {
|
|
@@ -3,7 +3,9 @@ import joi, { type StringSchema } from 'joi'
|
|
|
3
3
|
|
|
4
4
|
import { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'
|
|
5
5
|
import { addClassOptionIfNone } from '~/src/server/plugins/engine/components/helpers.js'
|
|
6
|
+
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
|
|
6
7
|
import {
|
|
8
|
+
type ErrorMessageTemplateList,
|
|
7
9
|
type FormPayload,
|
|
8
10
|
type FormSubmissionError
|
|
9
11
|
} from '~/src/server/plugins/engine/types.js'
|
|
@@ -21,13 +23,13 @@ export class TelephoneNumberField extends FormComponent {
|
|
|
21
23
|
) {
|
|
22
24
|
super(def, props)
|
|
23
25
|
|
|
24
|
-
const { options
|
|
26
|
+
const { options } = def
|
|
25
27
|
|
|
26
28
|
let formSchema = joi
|
|
27
29
|
.string()
|
|
28
30
|
.trim()
|
|
29
31
|
.pattern(PATTERN)
|
|
30
|
-
.label(
|
|
32
|
+
.label(this.label)
|
|
31
33
|
.required()
|
|
32
34
|
|
|
33
35
|
if (options.required === false) {
|
|
@@ -64,4 +66,17 @@ export class TelephoneNumberField extends FormComponent {
|
|
|
64
66
|
type: 'tel'
|
|
65
67
|
}
|
|
66
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* For error preview page that shows all possible errors on a component
|
|
72
|
+
*/
|
|
73
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
74
|
+
return {
|
|
75
|
+
baseErrors: [
|
|
76
|
+
{ type: 'required', template: messageTemplate.required },
|
|
77
|
+
{ type: 'format', template: messageTemplate.format }
|
|
78
|
+
],
|
|
79
|
+
advancedSettingsErrors: []
|
|
80
|
+
}
|
|
81
|
+
}
|
|
67
82
|
}
|
|
@@ -37,7 +37,7 @@ describe('TextField', () => {
|
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
describe('Schema', () => {
|
|
40
|
-
it('uses component title as label', () => {
|
|
40
|
+
it('uses component title as label as default', () => {
|
|
41
41
|
const { formSchema } = collection
|
|
42
42
|
const { keys } = formSchema.describe()
|
|
43
43
|
|
|
@@ -196,10 +196,42 @@ describe('TextField', () => {
|
|
|
196
196
|
)
|
|
197
197
|
})
|
|
198
198
|
})
|
|
199
|
+
|
|
200
|
+
describe('AllPossibleErrors', () => {
|
|
201
|
+
it('should return errors', () => {
|
|
202
|
+
const errors = field.getAllPossibleErrors()
|
|
203
|
+
expect(errors.baseErrors).not.toBeEmpty()
|
|
204
|
+
expect(errors.advancedSettingsErrors).not.toBeEmpty()
|
|
205
|
+
})
|
|
206
|
+
})
|
|
199
207
|
})
|
|
200
208
|
|
|
201
209
|
describe('Validation', () => {
|
|
202
210
|
describe.each([
|
|
211
|
+
{
|
|
212
|
+
description: 'Use short description if it exists',
|
|
213
|
+
component: {
|
|
214
|
+
title: 'What is your example text?',
|
|
215
|
+
shortDescription: 'Your example text',
|
|
216
|
+
name: 'myComponent',
|
|
217
|
+
type: ComponentType.TextField,
|
|
218
|
+
options: {},
|
|
219
|
+
schema: {}
|
|
220
|
+
} satisfies TextFieldComponent,
|
|
221
|
+
assertions: [
|
|
222
|
+
{
|
|
223
|
+
input: getFormData(''),
|
|
224
|
+
output: {
|
|
225
|
+
value: getFormData(''),
|
|
226
|
+
errors: [
|
|
227
|
+
expect.objectContaining({
|
|
228
|
+
text: 'Enter your example text'
|
|
229
|
+
})
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
},
|
|
203
235
|
{
|
|
204
236
|
description: 'Trim empty spaces',
|
|
205
237
|
component: {
|
|
@@ -8,7 +8,9 @@ import {
|
|
|
8
8
|
FormComponent,
|
|
9
9
|
isFormValue
|
|
10
10
|
} from '~/src/server/plugins/engine/components/FormComponent.js'
|
|
11
|
+
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
|
|
11
12
|
import {
|
|
13
|
+
type ErrorMessageTemplateList,
|
|
12
14
|
type FormState,
|
|
13
15
|
type FormStateValue,
|
|
14
16
|
type FormSubmissionState
|
|
@@ -30,10 +32,10 @@ export class TextField extends FormComponent {
|
|
|
30
32
|
) {
|
|
31
33
|
super(def, props)
|
|
32
34
|
|
|
33
|
-
const { options
|
|
35
|
+
const { options } = def
|
|
34
36
|
const schema = 'schema' in def ? def.schema : {}
|
|
35
37
|
|
|
36
|
-
let formSchema = joi.string().trim().label(
|
|
38
|
+
let formSchema = joi.string().trim().label(this.label).required()
|
|
37
39
|
|
|
38
40
|
if (options.required === false) {
|
|
39
41
|
formSchema = formSchema.allow('')
|
|
@@ -90,6 +92,19 @@ export class TextField extends FormComponent {
|
|
|
90
92
|
return TextField.isText(value)
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
/**
|
|
96
|
+
* For error preview page that shows all possible errors on a component
|
|
97
|
+
*/
|
|
98
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
99
|
+
return {
|
|
100
|
+
baseErrors: [{ type: 'required', template: messageTemplate.required }],
|
|
101
|
+
advancedSettingsErrors: [
|
|
102
|
+
{ type: 'min', template: messageTemplate.min },
|
|
103
|
+
{ type: 'max', template: messageTemplate.max }
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
93
108
|
static isText(value?: FormStateValue | FormState): value is string {
|
|
94
109
|
return isFormValue(value) && typeof value === 'string'
|
|
95
110
|
}
|
|
@@ -465,6 +465,14 @@ describe('UkAddressField', () => {
|
|
|
465
465
|
})
|
|
466
466
|
})
|
|
467
467
|
})
|
|
468
|
+
|
|
469
|
+
describe('AllPossibleErrors', () => {
|
|
470
|
+
it('should return errors', () => {
|
|
471
|
+
const errors = field.getAllPossibleErrors()
|
|
472
|
+
expect(errors.baseErrors).not.toBeEmpty()
|
|
473
|
+
expect(errors.advancedSettingsErrors).toBeEmpty()
|
|
474
|
+
})
|
|
475
|
+
})
|
|
468
476
|
})
|
|
469
477
|
|
|
470
478
|
describe('Validation', () => {
|
|
@@ -509,7 +517,8 @@ describe('UkAddressField', () => {
|
|
|
509
517
|
postcode: ' WA4 1HT'
|
|
510
518
|
}),
|
|
511
519
|
output: {
|
|
512
|
-
value: getFormData(address)
|
|
520
|
+
value: getFormData(address),
|
|
521
|
+
errors: undefined
|
|
513
522
|
}
|
|
514
523
|
},
|
|
515
524
|
{
|
|
@@ -521,7 +530,8 @@ describe('UkAddressField', () => {
|
|
|
521
530
|
postcode: 'WA4 1HT '
|
|
522
531
|
}),
|
|
523
532
|
output: {
|
|
524
|
-
value: getFormData(address)
|
|
533
|
+
value: getFormData(address),
|
|
534
|
+
errors: undefined
|
|
525
535
|
}
|
|
526
536
|
},
|
|
527
537
|
{
|
|
@@ -533,7 +543,8 @@ describe('UkAddressField', () => {
|
|
|
533
543
|
postcode: ' WA4 1HT \n\n'
|
|
534
544
|
}),
|
|
535
545
|
output: {
|
|
536
|
-
value: getFormData(address)
|
|
546
|
+
value: getFormData(address),
|
|
547
|
+
errors: undefined
|
|
537
548
|
}
|
|
538
549
|
}
|
|
539
550
|
]
|
|
@@ -661,6 +672,35 @@ describe('UkAddressField', () => {
|
|
|
661
672
|
})
|
|
662
673
|
]
|
|
663
674
|
}
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
input: getFormData({
|
|
678
|
+
addressLine1: '',
|
|
679
|
+
addressLine2: '',
|
|
680
|
+
town: '',
|
|
681
|
+
county: '',
|
|
682
|
+
postcode: postcodeInvalid
|
|
683
|
+
}),
|
|
684
|
+
output: {
|
|
685
|
+
value: getFormData({
|
|
686
|
+
addressLine1: '',
|
|
687
|
+
addressLine2: '',
|
|
688
|
+
town: '',
|
|
689
|
+
county: '',
|
|
690
|
+
postcode: postcodeInvalid
|
|
691
|
+
}),
|
|
692
|
+
errors: [
|
|
693
|
+
expect.objectContaining({
|
|
694
|
+
text: 'Enter address line 1'
|
|
695
|
+
}),
|
|
696
|
+
expect.objectContaining({
|
|
697
|
+
text: 'Enter town or city'
|
|
698
|
+
}),
|
|
699
|
+
expect.objectContaining({
|
|
700
|
+
text: 'Enter a valid postcode'
|
|
701
|
+
})
|
|
702
|
+
]
|
|
703
|
+
}
|
|
664
704
|
}
|
|
665
705
|
]
|
|
666
706
|
}
|
|
@@ -676,6 +716,9 @@ describe('UkAddressField', () => {
|
|
|
676
716
|
({ input, output }) => {
|
|
677
717
|
const result = collection.validate(input)
|
|
678
718
|
expect(result).toEqual(output)
|
|
719
|
+
|
|
720
|
+
const errors = collection.getErrors(result.errors)
|
|
721
|
+
expect(errors).toEqual(output.errors)
|
|
679
722
|
}
|
|
680
723
|
)
|
|
681
724
|
})
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { TextField } from '~/src/server/plugins/engine/components/TextField.js'
|
|
10
10
|
import { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
|
|
11
11
|
import {
|
|
12
|
+
type ErrorMessageTemplateList,
|
|
12
13
|
type FormPayload,
|
|
13
14
|
type FormState,
|
|
14
15
|
type FormStateValue,
|
|
@@ -123,6 +124,18 @@ export class UkAddressField extends FormComponent {
|
|
|
123
124
|
return Object.values(value).filter(Boolean)
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Returns one error per child field
|
|
129
|
+
*/
|
|
130
|
+
getViewErrors(
|
|
131
|
+
errors?: FormSubmissionError[]
|
|
132
|
+
): FormSubmissionError[] | undefined {
|
|
133
|
+
return this.getErrors(errors)?.filter(
|
|
134
|
+
(error, index, self) =>
|
|
135
|
+
index === self.findIndex((err) => err.name === error.name)
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
126
139
|
getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {
|
|
127
140
|
const { collection, name, options } = this
|
|
128
141
|
|
|
@@ -163,6 +176,21 @@ export class UkAddressField extends FormComponent {
|
|
|
163
176
|
return UkAddressField.isUkAddress(value)
|
|
164
177
|
}
|
|
165
178
|
|
|
179
|
+
/**
|
|
180
|
+
* For error preview page that shows all possible errors on a component
|
|
181
|
+
*/
|
|
182
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
183
|
+
return {
|
|
184
|
+
baseErrors: [
|
|
185
|
+
{ type: 'required', template: 'Enter address line 1' },
|
|
186
|
+
{ type: 'required', template: 'Enter town or city' },
|
|
187
|
+
{ type: 'required', template: 'Enter postcode' },
|
|
188
|
+
{ type: 'format', template: 'Enter valid postcode' }
|
|
189
|
+
],
|
|
190
|
+
advancedSettingsErrors: []
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
166
194
|
static isUkAddress(
|
|
167
195
|
value?: FormStateValue | FormState
|
|
168
196
|
): value is UkAddressState {
|
|
@@ -121,7 +121,7 @@ describe('YesNoField', () => {
|
|
|
121
121
|
|
|
122
122
|
expect(result.errors).toEqual([
|
|
123
123
|
expect.objectContaining({
|
|
124
|
-
text: '
|
|
124
|
+
text: 'Example yes/no - select yes or no'
|
|
125
125
|
})
|
|
126
126
|
])
|
|
127
127
|
})
|
|
@@ -245,4 +245,12 @@ describe('YesNoField', () => {
|
|
|
245
245
|
)
|
|
246
246
|
})
|
|
247
247
|
})
|
|
248
|
+
|
|
249
|
+
describe('AllPossibleErrors', () => {
|
|
250
|
+
it('should return errors', () => {
|
|
251
|
+
const errors = field.getAllPossibleErrors()
|
|
252
|
+
expect(errors.baseErrors).not.toBeEmpty()
|
|
253
|
+
expect(errors.advancedSettingsErrors).toBeEmpty()
|
|
254
|
+
})
|
|
255
|
+
})
|
|
248
256
|
})
|
|
@@ -2,6 +2,9 @@ import { type YesNoFieldComponent } from '@defra/forms-model'
|
|
|
2
2
|
|
|
3
3
|
import { SelectionControlField } from '~/src/server/plugins/engine/components/SelectionControlField.js'
|
|
4
4
|
import { addClassOptionIfNone } from '~/src/server/plugins/engine/components/helpers.js'
|
|
5
|
+
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
|
|
6
|
+
import { type ErrorMessageTemplateList } from '~/src/server/plugins/engine/types.js'
|
|
7
|
+
import { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* @description
|
|
@@ -25,7 +28,28 @@ export class YesNoField extends SelectionControlField {
|
|
|
25
28
|
formSchema = formSchema.optional()
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
formSchema = formSchema.messages(
|
|
32
|
+
convertToLanguageMessages({
|
|
33
|
+
'any.required': messageTemplate.selectYesNoRequired
|
|
34
|
+
})
|
|
35
|
+
)
|
|
36
|
+
|
|
28
37
|
this.formSchema = formSchema
|
|
29
38
|
this.options = options
|
|
30
39
|
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* For error preview page that shows all possible errors on a component
|
|
43
|
+
*/
|
|
44
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
45
|
+
return {
|
|
46
|
+
baseErrors: [
|
|
47
|
+
{
|
|
48
|
+
type: 'selectYesNoRequired',
|
|
49
|
+
template: messageTemplate.selectYesNoRequired
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
advancedSettingsErrors: []
|
|
53
|
+
}
|
|
54
|
+
}
|
|
31
55
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ComponentDef } from '@defra/forms-model'
|
|
2
|
+
|
|
3
|
+
import { createComponent } from '~/src/server/plugins/engine/components/helpers.js'
|
|
4
|
+
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
5
|
+
import definition from '~/test/form/definitions/basic.js'
|
|
6
|
+
|
|
7
|
+
const formModel = new FormModel(definition, {
|
|
8
|
+
basePath: 'test'
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
describe('helpers tests', () => {
|
|
12
|
+
test('should throw if invalid type', () => {
|
|
13
|
+
expect(() =>
|
|
14
|
+
createComponent(
|
|
15
|
+
{
|
|
16
|
+
type: 'invalid-type'
|
|
17
|
+
} as unknown as ComponentDef,
|
|
18
|
+
{
|
|
19
|
+
model: formModel
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
).toThrow('Component type invalid-type does not exist')
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -55,6 +55,8 @@ export type Component = InstanceType<
|
|
|
55
55
|
// Field component instances only
|
|
56
56
|
export type Field = InstanceType<
|
|
57
57
|
| typeof Components.AutocompleteField
|
|
58
|
+
| typeof Components.RadiosField
|
|
59
|
+
| typeof Components.YesNoField
|
|
58
60
|
| typeof Components.CheckboxesField
|
|
59
61
|
| typeof Components.DatePartsField
|
|
60
62
|
| typeof Components.EmailAddressField
|
|
@@ -72,10 +74,43 @@ export type Field = InstanceType<
|
|
|
72
74
|
export type Guidance = InstanceType<
|
|
73
75
|
| typeof Components.Details
|
|
74
76
|
| typeof Components.Html
|
|
77
|
+
| typeof Components.Markdown
|
|
75
78
|
| typeof Components.InsetText
|
|
76
79
|
| typeof Components.List
|
|
77
80
|
>
|
|
78
81
|
|
|
82
|
+
// List component instances only
|
|
83
|
+
export type ListField = InstanceType<
|
|
84
|
+
| typeof Components.AutocompleteField
|
|
85
|
+
| typeof Components.CheckboxesField
|
|
86
|
+
| typeof Components.RadiosField
|
|
87
|
+
| typeof Components.SelectField
|
|
88
|
+
| typeof Components.YesNoField
|
|
89
|
+
>
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Filter known components with lists
|
|
93
|
+
*/
|
|
94
|
+
export function hasListFormField(
|
|
95
|
+
field?: Partial<Component>
|
|
96
|
+
): field is ListFormComponent {
|
|
97
|
+
return !!field && isListFieldType(field.type)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function isListFieldType(
|
|
101
|
+
type?: ComponentType
|
|
102
|
+
): type is ListField['type'] {
|
|
103
|
+
const allowedTypes = [
|
|
104
|
+
ComponentType.AutocompleteField,
|
|
105
|
+
ComponentType.CheckboxesField,
|
|
106
|
+
ComponentType.RadiosField,
|
|
107
|
+
ComponentType.SelectField,
|
|
108
|
+
ComponentType.YesNoField
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
return !!type && allowedTypes.includes(type)
|
|
112
|
+
}
|
|
113
|
+
|
|
79
114
|
/**
|
|
80
115
|
* Create field instance for each {@link ComponentDef} type
|
|
81
116
|
*/
|
|
@@ -118,6 +153,10 @@ export function createComponent(
|
|
|
118
153
|
component = new Components.List(def, options)
|
|
119
154
|
break
|
|
120
155
|
|
|
156
|
+
case ComponentType.Markdown:
|
|
157
|
+
component = new Components.Markdown(def, options)
|
|
158
|
+
break
|
|
159
|
+
|
|
121
160
|
case ComponentType.MultilineTextField:
|
|
122
161
|
component = new Components.MultilineTextField(def, options)
|
|
123
162
|
break
|
|
@@ -12,6 +12,7 @@ export { EmailAddressField } from '~/src/server/plugins/engine/components/EmailA
|
|
|
12
12
|
export { Html } from '~/src/server/plugins/engine/components/Html.js'
|
|
13
13
|
export { InsetText } from '~/src/server/plugins/engine/components/InsetText.js'
|
|
14
14
|
export { List } from '~/src/server/plugins/engine/components/List.js'
|
|
15
|
+
export { Markdown } from '~/src/server/plugins/engine/components/Markdown.js'
|
|
15
16
|
export { MultilineTextField } from '~/src/server/plugins/engine/components/MultilineTextField.js'
|
|
16
17
|
export { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'
|
|
17
18
|
export { RadiosField } from '~/src/server/plugins/engine/components/RadiosField.js'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { join, parse } from 'node:path'
|
|
2
2
|
|
|
3
3
|
import { type FormDefinition } from '@defra/forms-model'
|
|
4
|
-
import { type ServerRegisterPluginObject } from '@hapi/hapi'
|
|
5
4
|
|
|
5
|
+
import { FORM_PREFIX } from '~/src/server/constants.js'
|
|
6
6
|
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
7
7
|
import {
|
|
8
8
|
plugin,
|
|
@@ -19,14 +19,24 @@ export const configureEnginePlugin = async ({
|
|
|
19
19
|
formFilePath,
|
|
20
20
|
services,
|
|
21
21
|
controllers
|
|
22
|
-
}: RouteConfig = {}): Promise<
|
|
22
|
+
}: RouteConfig = {}): Promise<{
|
|
23
|
+
plugin: typeof plugin
|
|
24
|
+
options: PluginOptions
|
|
25
|
+
}> => {
|
|
23
26
|
let model: FormModel | undefined
|
|
24
27
|
|
|
25
28
|
if (formFileName && formFilePath) {
|
|
26
29
|
const definition = await getForm(join(formFilePath, formFileName))
|
|
27
30
|
const { name } = parse(formFileName)
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
const initialBasePath = `${FORM_PREFIX}${name}`
|
|
33
|
+
|
|
34
|
+
model = new FormModel(
|
|
35
|
+
definition,
|
|
36
|
+
{ basePath: initialBasePath },
|
|
37
|
+
services,
|
|
38
|
+
controllers
|
|
39
|
+
)
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
return {
|