@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
|
@@ -5,9 +5,11 @@ import {
|
|
|
5
5
|
FormComponent,
|
|
6
6
|
isUploadState
|
|
7
7
|
} from '~/src/server/plugins/engine/components/FormComponent.js'
|
|
8
|
+
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
|
|
8
9
|
import {
|
|
9
10
|
FileStatus,
|
|
10
11
|
UploadStatus,
|
|
12
|
+
type ErrorMessageTemplateList,
|
|
11
13
|
type FileState,
|
|
12
14
|
type FileUpload,
|
|
13
15
|
type FileUploadMetadata,
|
|
@@ -104,9 +106,13 @@ export class FileUploadField extends FormComponent {
|
|
|
104
106
|
) {
|
|
105
107
|
super(def, props)
|
|
106
108
|
|
|
107
|
-
const { options, schema
|
|
109
|
+
const { options, schema } = def
|
|
108
110
|
|
|
109
|
-
let formSchema = joi
|
|
111
|
+
let formSchema = joi
|
|
112
|
+
.array<FileState>()
|
|
113
|
+
.label(this.label)
|
|
114
|
+
.single()
|
|
115
|
+
.required()
|
|
110
116
|
|
|
111
117
|
if (options.required === false) {
|
|
112
118
|
formSchema = formSchema.optional()
|
|
@@ -231,7 +237,7 @@ export class FileUploadField extends FormComponent {
|
|
|
231
237
|
})
|
|
232
238
|
|
|
233
239
|
// Set up the `accept` attribute
|
|
234
|
-
if ('accept' in options) {
|
|
240
|
+
if ('accept' in options && options.accept) {
|
|
235
241
|
attributes.accept = options.accept
|
|
236
242
|
}
|
|
237
243
|
|
|
@@ -259,4 +265,47 @@ export class FileUploadField extends FormComponent {
|
|
|
259
265
|
isValue(value?: FormStateValue | FormState): value is UploadState {
|
|
260
266
|
return isUploadState(value)
|
|
261
267
|
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* For error preview page that shows all possible errors on a component
|
|
271
|
+
*/
|
|
272
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
273
|
+
return {
|
|
274
|
+
baseErrors: [
|
|
275
|
+
{ type: 'selectRequired', template: messageTemplate.selectRequired },
|
|
276
|
+
{
|
|
277
|
+
type: 'filesMimes',
|
|
278
|
+
template: 'The selected file must be a {{#limit}}'
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
type: 'filesSize',
|
|
282
|
+
template: 'The selected file must be smaller than 100MB'
|
|
283
|
+
},
|
|
284
|
+
{ type: 'filesEmpty', template: 'The selected file is empty' },
|
|
285
|
+
{ type: 'filesVirus', template: 'The selected file contains a virus' },
|
|
286
|
+
{
|
|
287
|
+
type: 'filesPartial',
|
|
288
|
+
template: 'The selected file has not fully uploaded'
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
type: 'filesError',
|
|
292
|
+
template: 'The selected file could not be uploaded – try again'
|
|
293
|
+
}
|
|
294
|
+
],
|
|
295
|
+
advancedSettingsErrors: [
|
|
296
|
+
{
|
|
297
|
+
type: 'filesMin',
|
|
298
|
+
template: 'You must upload {{#limit}} files or more'
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
type: 'filesMax',
|
|
302
|
+
template: 'You can only upload {{#limit}} files or less'
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
type: 'filesExact',
|
|
306
|
+
template: 'You must upload exactly {{#limit}} files'
|
|
307
|
+
}
|
|
308
|
+
]
|
|
309
|
+
}
|
|
310
|
+
}
|
|
262
311
|
}
|
|
@@ -3,6 +3,7 @@ import { type FormComponentsDef, type Item } from '@defra/forms-model'
|
|
|
3
3
|
import { ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'
|
|
4
4
|
import { optionalText } from '~/src/server/plugins/engine/components/constants.js'
|
|
5
5
|
import {
|
|
6
|
+
type ErrorMessageTemplateList,
|
|
6
7
|
type FileState,
|
|
7
8
|
type FormPayload,
|
|
8
9
|
type FormState,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
export class FormComponent extends ComponentBase {
|
|
19
20
|
type: FormComponentsDef['type']
|
|
20
21
|
hint: FormComponentsDef['hint']
|
|
22
|
+
label: string
|
|
21
23
|
|
|
22
24
|
isFormComponent = true
|
|
23
25
|
|
|
@@ -31,6 +33,10 @@ export class FormComponent extends ComponentBase {
|
|
|
31
33
|
|
|
32
34
|
this.type = type
|
|
33
35
|
this.hint = hint
|
|
36
|
+
this.label =
|
|
37
|
+
'shortDescription' in def && def.shortDescription
|
|
38
|
+
? def.shortDescription
|
|
39
|
+
: def.title
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
get keys() {
|
|
@@ -100,10 +106,19 @@ export class FormComponent extends ComponentBase {
|
|
|
100
106
|
return list
|
|
101
107
|
}
|
|
102
108
|
|
|
103
|
-
|
|
109
|
+
getFirstError(
|
|
110
|
+
errors?: FormSubmissionError[]
|
|
111
|
+
): FormSubmissionError | undefined {
|
|
104
112
|
return this.getErrors(errors)?.[0]
|
|
105
113
|
}
|
|
106
114
|
|
|
115
|
+
getViewErrors(
|
|
116
|
+
errors?: FormSubmissionError[]
|
|
117
|
+
): FormSubmissionError[] | undefined {
|
|
118
|
+
const firstError = this.getFirstError(errors)
|
|
119
|
+
return firstError && [firstError]
|
|
120
|
+
}
|
|
121
|
+
|
|
107
122
|
getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {
|
|
108
123
|
const { hint, name, options = {}, title, viewModel } = this
|
|
109
124
|
|
|
@@ -119,7 +134,7 @@ export class FormComponent extends ComponentBase {
|
|
|
119
134
|
|
|
120
135
|
// Filter component errors only
|
|
121
136
|
const componentErrors = this.getErrors(errors)
|
|
122
|
-
const componentError = this.
|
|
137
|
+
const componentError = this.getFirstError(componentErrors)
|
|
123
138
|
|
|
124
139
|
if (componentErrors) {
|
|
125
140
|
viewModel.errors = componentErrors
|
|
@@ -175,6 +190,13 @@ export class FormComponent extends ComponentBase {
|
|
|
175
190
|
isState(value?: FormStateValue | FormState): value is FormState {
|
|
176
191
|
return isFormState(value)
|
|
177
192
|
}
|
|
193
|
+
|
|
194
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
195
|
+
return {
|
|
196
|
+
baseErrors: [],
|
|
197
|
+
advancedSettingsErrors: []
|
|
198
|
+
}
|
|
199
|
+
}
|
|
178
200
|
}
|
|
179
201
|
|
|
180
202
|
/**
|
|
@@ -14,7 +14,9 @@ import joi, {
|
|
|
14
14
|
|
|
15
15
|
import { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'
|
|
16
16
|
import { type ListItem } from '~/src/server/plugins/engine/components/types.js'
|
|
17
|
+
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
|
|
17
18
|
import {
|
|
19
|
+
type ErrorMessageTemplateList,
|
|
18
20
|
type FormPayload,
|
|
19
21
|
type FormSubmissionError,
|
|
20
22
|
type FormSubmissionState
|
|
@@ -61,7 +63,7 @@ export class ListFormComponent extends FormComponent {
|
|
|
61
63
|
) {
|
|
62
64
|
super(def, props)
|
|
63
65
|
|
|
64
|
-
const { options
|
|
66
|
+
const { options } = def
|
|
65
67
|
const { model } = props
|
|
66
68
|
|
|
67
69
|
if ('list' in def) {
|
|
@@ -71,7 +73,7 @@ export class ListFormComponent extends FormComponent {
|
|
|
71
73
|
|
|
72
74
|
let formSchema = joi[this.listType]()
|
|
73
75
|
.valid(...this.values)
|
|
74
|
-
.label(
|
|
76
|
+
.label(this.label)
|
|
75
77
|
.required()
|
|
76
78
|
|
|
77
79
|
if (options.customValidationMessages) {
|
|
@@ -137,4 +139,16 @@ export class ListFormComponent extends FormComponent {
|
|
|
137
139
|
items
|
|
138
140
|
}
|
|
139
141
|
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* For error preview page that shows all possible errors on a component
|
|
145
|
+
*/
|
|
146
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
147
|
+
return {
|
|
148
|
+
baseErrors: [
|
|
149
|
+
{ type: 'selectRequired', template: messageTemplate.selectRequired }
|
|
150
|
+
],
|
|
151
|
+
advancedSettingsErrors: []
|
|
152
|
+
}
|
|
153
|
+
}
|
|
140
154
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ComponentType, type MarkdownComponent } from '@defra/forms-model'
|
|
2
|
+
|
|
3
|
+
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
4
|
+
import { type Guidance } from '~/src/server/plugins/engine/components/helpers.js'
|
|
5
|
+
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
6
|
+
import definition from '~/test/form/definitions/basic.js'
|
|
7
|
+
|
|
8
|
+
describe('Markdown', () => {
|
|
9
|
+
let model: FormModel
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
model = new FormModel(definition, {
|
|
13
|
+
basePath: 'test'
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('Defaults', () => {
|
|
18
|
+
let def: MarkdownComponent
|
|
19
|
+
let collection: ComponentCollection
|
|
20
|
+
let guidance: Guidance
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
def = {
|
|
24
|
+
title: 'Markdown guidance',
|
|
25
|
+
name: 'myComponent',
|
|
26
|
+
type: ComponentType.Markdown,
|
|
27
|
+
content: '# Heading 1 ## Heading 2',
|
|
28
|
+
options: {}
|
|
29
|
+
} satisfies MarkdownComponent
|
|
30
|
+
|
|
31
|
+
collection = new ComponentCollection([def], { model })
|
|
32
|
+
guidance = collection.guidance[0]
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('View model', () => {
|
|
36
|
+
it('sets Nunjucks component defaults', () => {
|
|
37
|
+
const viewModel = guidance.getViewModel()
|
|
38
|
+
|
|
39
|
+
expect(viewModel).toEqual(
|
|
40
|
+
expect.objectContaining({
|
|
41
|
+
attributes: {},
|
|
42
|
+
content: def.content
|
|
43
|
+
})
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type MarkdownComponent } from '@defra/forms-model'
|
|
2
|
+
|
|
3
|
+
import { ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'
|
|
4
|
+
|
|
5
|
+
export class Markdown extends ComponentBase {
|
|
6
|
+
declare options: MarkdownComponent['options']
|
|
7
|
+
content: MarkdownComponent['content']
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
def: MarkdownComponent,
|
|
11
|
+
props: ConstructorParameters<typeof ComponentBase>[1]
|
|
12
|
+
) {
|
|
13
|
+
super(def, props)
|
|
14
|
+
|
|
15
|
+
const { content, options } = def
|
|
16
|
+
|
|
17
|
+
this.content = content
|
|
18
|
+
this.options = options
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getViewModel() {
|
|
22
|
+
const { content, viewModel } = this
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
...viewModel,
|
|
26
|
+
content
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -31,6 +31,7 @@ describe('MonthYearField', () => {
|
|
|
31
31
|
beforeEach(() => {
|
|
32
32
|
def = {
|
|
33
33
|
title: 'Example month/year field',
|
|
34
|
+
shortDescription: 'Example month/year',
|
|
34
35
|
name: 'myComponent',
|
|
35
36
|
type: ComponentType.MonthYearField,
|
|
36
37
|
options: {}
|
|
@@ -168,6 +169,32 @@ describe('MonthYearField', () => {
|
|
|
168
169
|
})
|
|
169
170
|
)
|
|
170
171
|
|
|
172
|
+
expect(result.errors).toEqual([
|
|
173
|
+
expect.objectContaining({
|
|
174
|
+
text: 'Example month/year must include a month'
|
|
175
|
+
}),
|
|
176
|
+
expect.objectContaining({
|
|
177
|
+
text: 'Example month/year must include a year'
|
|
178
|
+
})
|
|
179
|
+
])
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('adds errors for empty value given no short desc exists', () => {
|
|
183
|
+
def = {
|
|
184
|
+
title: 'Example month/year field',
|
|
185
|
+
name: 'myComponent',
|
|
186
|
+
type: ComponentType.MonthYearField,
|
|
187
|
+
options: {}
|
|
188
|
+
} satisfies MonthYearFieldComponent
|
|
189
|
+
|
|
190
|
+
collection = new ComponentCollection([def], { model })
|
|
191
|
+
const result = collection.validate(
|
|
192
|
+
getFormData({
|
|
193
|
+
month: '',
|
|
194
|
+
year: ''
|
|
195
|
+
})
|
|
196
|
+
)
|
|
197
|
+
|
|
171
198
|
expect(result.errors).toEqual([
|
|
172
199
|
expect.objectContaining({
|
|
173
200
|
text: 'Example month/year field must include a month'
|
|
@@ -347,6 +374,14 @@ describe('MonthYearField', () => {
|
|
|
347
374
|
})
|
|
348
375
|
})
|
|
349
376
|
})
|
|
377
|
+
|
|
378
|
+
describe('AllPossibleErrors', () => {
|
|
379
|
+
it('should return errors', () => {
|
|
380
|
+
const errors = field.getAllPossibleErrors()
|
|
381
|
+
expect(errors.baseErrors).not.toBeEmpty()
|
|
382
|
+
expect(errors.advancedSettingsErrors).not.toBeEmpty()
|
|
383
|
+
})
|
|
384
|
+
})
|
|
350
385
|
})
|
|
351
386
|
|
|
352
387
|
describe('Validation', () => {
|
|
@@ -17,12 +17,14 @@ import { NumberField } from '~/src/server/plugins/engine/components/NumberField.
|
|
|
17
17
|
import { type DateInputItem } from '~/src/server/plugins/engine/components/types.js'
|
|
18
18
|
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
|
|
19
19
|
import {
|
|
20
|
+
type ErrorMessageTemplateList,
|
|
20
21
|
type FormPayload,
|
|
21
22
|
type FormState,
|
|
22
23
|
type FormStateValue,
|
|
23
24
|
type FormSubmissionError,
|
|
24
25
|
type FormSubmissionState
|
|
25
26
|
} from '~/src/server/plugins/engine/types.js'
|
|
27
|
+
import { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'
|
|
26
28
|
|
|
27
29
|
export class MonthYearField extends FormComponent {
|
|
28
30
|
declare options: MonthYearFieldComponent['options']
|
|
@@ -40,15 +42,18 @@ export class MonthYearField extends FormComponent {
|
|
|
40
42
|
|
|
41
43
|
const isRequired = options.required !== false
|
|
42
44
|
|
|
43
|
-
const customValidationMessages: LanguageMessages =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
const customValidationMessages: LanguageMessages =
|
|
46
|
+
convertToLanguageMessages({
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
48
|
+
'any.required': messageTemplate.objectMissing,
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
50
|
+
'number.base': messageTemplate.objectMissing,
|
|
51
|
+
'number.precision': messageTemplate.dateFormat,
|
|
52
|
+
'number.integer': messageTemplate.dateFormat,
|
|
53
|
+
'number.unsafe': messageTemplate.dateFormat,
|
|
54
|
+
'number.min': messageTemplate.dateFormat,
|
|
55
|
+
'number.max': messageTemplate.dateFormat
|
|
56
|
+
})
|
|
52
57
|
|
|
53
58
|
this.collection = new ComponentCollection(
|
|
54
59
|
[
|
|
@@ -180,6 +185,26 @@ export class MonthYearField extends FormComponent {
|
|
|
180
185
|
return MonthYearField.isMonthYear(value)
|
|
181
186
|
}
|
|
182
187
|
|
|
188
|
+
/**
|
|
189
|
+
* For error preview page that shows all possible errors on a component
|
|
190
|
+
*/
|
|
191
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
192
|
+
return {
|
|
193
|
+
baseErrors: [
|
|
194
|
+
{ type: 'required', template: messageTemplate.required },
|
|
195
|
+
{
|
|
196
|
+
type: 'dateFormatMonth',
|
|
197
|
+
template: '{{#label}} must include a month'
|
|
198
|
+
},
|
|
199
|
+
{ type: 'dateFormatYear', template: '{{#label}} must include a year' }
|
|
200
|
+
],
|
|
201
|
+
advancedSettingsErrors: [
|
|
202
|
+
{ type: 'dateMin', template: messageTemplate.dateMin },
|
|
203
|
+
{ type: 'dateMax', template: messageTemplate.dateMax }
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
183
208
|
static isMonthYear(
|
|
184
209
|
value?: FormStateValue | FormState
|
|
185
210
|
): value is MonthYearState {
|
|
@@ -29,7 +29,8 @@ describe('MultilineTextField', () => {
|
|
|
29
29
|
|
|
30
30
|
beforeEach(() => {
|
|
31
31
|
def = {
|
|
32
|
-
title: 'Example textarea',
|
|
32
|
+
title: 'Example textarea title',
|
|
33
|
+
shortDescription: 'Example textarea',
|
|
33
34
|
name: 'myComponent',
|
|
34
35
|
type: ComponentType.MultilineTextField,
|
|
35
36
|
options: {},
|
|
@@ -41,7 +42,7 @@ describe('MultilineTextField', () => {
|
|
|
41
42
|
})
|
|
42
43
|
|
|
43
44
|
describe('Schema', () => {
|
|
44
|
-
it('uses component
|
|
45
|
+
it('uses component short description as label', () => {
|
|
45
46
|
const { formSchema } = collection
|
|
46
47
|
const { keys } = formSchema.describe()
|
|
47
48
|
|
|
@@ -117,6 +118,25 @@ describe('MultilineTextField', () => {
|
|
|
117
118
|
])
|
|
118
119
|
})
|
|
119
120
|
|
|
121
|
+
it('adds errors for empty value given no short description', () => {
|
|
122
|
+
def = {
|
|
123
|
+
title: 'Example textarea title',
|
|
124
|
+
name: 'myComponent',
|
|
125
|
+
type: ComponentType.MultilineTextField,
|
|
126
|
+
options: {},
|
|
127
|
+
schema: {}
|
|
128
|
+
} satisfies MultilineTextFieldComponent
|
|
129
|
+
|
|
130
|
+
collection = new ComponentCollection([def], { model })
|
|
131
|
+
const result = collection.validate(getFormData(''))
|
|
132
|
+
|
|
133
|
+
expect(result.errors).toEqual([
|
|
134
|
+
expect.objectContaining({
|
|
135
|
+
text: 'Enter example textarea title'
|
|
136
|
+
})
|
|
137
|
+
])
|
|
138
|
+
})
|
|
139
|
+
|
|
120
140
|
it('adds errors for invalid values', () => {
|
|
121
141
|
const result1 = collection.validate(getFormData(['invalid']))
|
|
122
142
|
const result2 = collection.validate(
|
|
@@ -234,6 +254,14 @@ describe('MultilineTextField', () => {
|
|
|
234
254
|
)
|
|
235
255
|
})
|
|
236
256
|
})
|
|
257
|
+
|
|
258
|
+
describe('AllPossibleErrors', () => {
|
|
259
|
+
it('should return errors', () => {
|
|
260
|
+
const errors = field.getAllPossibleErrors()
|
|
261
|
+
expect(errors.baseErrors).not.toBeEmpty()
|
|
262
|
+
expect(errors.advancedSettingsErrors).not.toBeEmpty()
|
|
263
|
+
})
|
|
264
|
+
})
|
|
237
265
|
})
|
|
238
266
|
|
|
239
267
|
describe('Validation', () => {
|
|
@@ -294,7 +322,57 @@ describe('MultilineTextField', () => {
|
|
|
294
322
|
]
|
|
295
323
|
},
|
|
296
324
|
{
|
|
297
|
-
description: 'Schema min
|
|
325
|
+
description: 'Schema min',
|
|
326
|
+
component: {
|
|
327
|
+
title: 'Example textarea',
|
|
328
|
+
name: 'myComponent',
|
|
329
|
+
type: ComponentType.MultilineTextField,
|
|
330
|
+
options: {},
|
|
331
|
+
schema: {
|
|
332
|
+
min: 5
|
|
333
|
+
}
|
|
334
|
+
} satisfies MultilineTextFieldComponent,
|
|
335
|
+
assertions: [
|
|
336
|
+
{
|
|
337
|
+
input: getFormData('Text'),
|
|
338
|
+
output: {
|
|
339
|
+
value: getFormData('Text'),
|
|
340
|
+
errors: [
|
|
341
|
+
expect.objectContaining({
|
|
342
|
+
text: 'Example textarea must be 5 characters or more'
|
|
343
|
+
})
|
|
344
|
+
]
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
description: 'Schema max',
|
|
351
|
+
component: {
|
|
352
|
+
title: 'Example textarea',
|
|
353
|
+
name: 'myComponent',
|
|
354
|
+
type: ComponentType.MultilineTextField,
|
|
355
|
+
options: {},
|
|
356
|
+
schema: {
|
|
357
|
+
max: 8
|
|
358
|
+
}
|
|
359
|
+
} satisfies MultilineTextFieldComponent,
|
|
360
|
+
assertions: [
|
|
361
|
+
{
|
|
362
|
+
input: getFormData('Text too long'),
|
|
363
|
+
output: {
|
|
364
|
+
value: getFormData('Text too long'),
|
|
365
|
+
errors: [
|
|
366
|
+
expect.objectContaining({
|
|
367
|
+
text: 'Example textarea must be 8 characters or less'
|
|
368
|
+
})
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
]
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
description: 'Schema min and max together',
|
|
298
376
|
component: {
|
|
299
377
|
title: 'Example textarea',
|
|
300
378
|
name: 'myComponent',
|
|
@@ -312,7 +390,7 @@ describe('MultilineTextField', () => {
|
|
|
312
390
|
value: getFormData('Text'),
|
|
313
391
|
errors: [
|
|
314
392
|
expect.objectContaining({
|
|
315
|
-
text: 'Example textarea must be 5
|
|
393
|
+
text: 'Example textarea must be between 5 and 8 characters'
|
|
316
394
|
})
|
|
317
395
|
]
|
|
318
396
|
}
|
|
@@ -323,7 +401,7 @@ describe('MultilineTextField', () => {
|
|
|
323
401
|
value: getFormData('Textarea too long'),
|
|
324
402
|
errors: [
|
|
325
403
|
expect.objectContaining({
|
|
326
|
-
text: 'Example textarea must be 8 characters
|
|
404
|
+
text: 'Example textarea must be between 5 and 8 characters'
|
|
327
405
|
})
|
|
328
406
|
]
|
|
329
407
|
}
|
|
@@ -3,7 +3,9 @@ import Joi, { type CustomValidator, type StringSchema } from 'joi'
|
|
|
3
3
|
|
|
4
4
|
import { type ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'
|
|
5
5
|
import { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.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'
|
|
@@ -22,9 +24,9 @@ export class MultilineTextField extends FormComponent {
|
|
|
22
24
|
) {
|
|
23
25
|
super(def, props)
|
|
24
26
|
|
|
25
|
-
const { schema, options
|
|
27
|
+
const { schema, options } = def
|
|
26
28
|
|
|
27
|
-
let formSchema = Joi.string().trim().label(
|
|
29
|
+
let formSchema = Joi.string().trim().label(this.label).required()
|
|
28
30
|
|
|
29
31
|
if (options.required === false) {
|
|
30
32
|
formSchema = formSchema.allow('')
|
|
@@ -71,6 +73,15 @@ export class MultilineTextField extends FormComponent {
|
|
|
71
73
|
})
|
|
72
74
|
} else if (options.customValidationMessages) {
|
|
73
75
|
formSchema = formSchema.messages(options.customValidationMessages)
|
|
76
|
+
} else if (
|
|
77
|
+
typeof schema.max === 'number' &&
|
|
78
|
+
typeof schema.min === 'number'
|
|
79
|
+
) {
|
|
80
|
+
const minMaxErrorText = this.buildMinMaxText(schema.min, schema.max)
|
|
81
|
+
formSchema = formSchema.ruleset
|
|
82
|
+
.min(schema.min)
|
|
83
|
+
.max(schema.max)
|
|
84
|
+
.message(minMaxErrorText)
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
this.formSchema = formSchema.default('')
|
|
@@ -105,6 +116,30 @@ export class MultilineTextField extends FormComponent {
|
|
|
105
116
|
rows
|
|
106
117
|
}
|
|
107
118
|
}
|
|
119
|
+
|
|
120
|
+
buildMinMaxText(min?: number, max?: number): string {
|
|
121
|
+
const minMaxError = messageTemplate.minMax as string
|
|
122
|
+
return minMaxError
|
|
123
|
+
.replace('{{#min}}', min ? min.toString() : '[min length]')
|
|
124
|
+
.replace('{{#max}}', max ? max.toString() : '[max length]')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* For error preview page that shows all possible errors on a component
|
|
129
|
+
*/
|
|
130
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
131
|
+
return {
|
|
132
|
+
baseErrors: [{ type: 'required', template: messageTemplate.required }],
|
|
133
|
+
advancedSettingsErrors: [
|
|
134
|
+
{ type: 'min', template: messageTemplate.min },
|
|
135
|
+
{ type: 'max', template: messageTemplate.max },
|
|
136
|
+
{
|
|
137
|
+
type: 'minMax',
|
|
138
|
+
template: this.buildMinMaxText(this.schema.min, this.schema.max)
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
108
143
|
}
|
|
109
144
|
|
|
110
145
|
function getValidatorMaxWords(component: MultilineTextField) {
|
|
@@ -27,6 +27,7 @@ describe('NumberField', () => {
|
|
|
27
27
|
beforeEach(() => {
|
|
28
28
|
def = {
|
|
29
29
|
title: 'Example number field',
|
|
30
|
+
shortDescription: 'Example number',
|
|
30
31
|
name: 'myComponent',
|
|
31
32
|
type: ComponentType.NumberField,
|
|
32
33
|
options: {},
|
|
@@ -46,7 +47,7 @@ describe('NumberField', () => {
|
|
|
46
47
|
'myComponent',
|
|
47
48
|
expect.objectContaining({
|
|
48
49
|
flags: expect.objectContaining({
|
|
49
|
-
label: 'Example number
|
|
50
|
+
label: 'Example number'
|
|
50
51
|
})
|
|
51
52
|
})
|
|
52
53
|
)
|
|
@@ -113,9 +114,22 @@ describe('NumberField', () => {
|
|
|
113
114
|
|
|
114
115
|
expect(result.errors).toEqual([
|
|
115
116
|
expect.objectContaining({
|
|
116
|
-
text: 'Enter example number
|
|
117
|
+
text: 'Enter example number'
|
|
117
118
|
})
|
|
118
119
|
])
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('adds errors for empty value given no short description exists', () => {
|
|
123
|
+
def = {
|
|
124
|
+
title: 'Example number field',
|
|
125
|
+
name: 'myComponent',
|
|
126
|
+
type: ComponentType.NumberField,
|
|
127
|
+
options: {},
|
|
128
|
+
schema: {}
|
|
129
|
+
} satisfies NumberFieldComponent
|
|
130
|
+
|
|
131
|
+
collection = new ComponentCollection([def], { model })
|
|
132
|
+
const result = collection.validate(getFormData(''))
|
|
119
133
|
|
|
120
134
|
expect(result.errors).toEqual([
|
|
121
135
|
expect.objectContaining({
|
|
@@ -258,6 +272,14 @@ describe('NumberField', () => {
|
|
|
258
272
|
|
|
259
273
|
expect(viewModel).toHaveProperty('value', 'AA')
|
|
260
274
|
})
|
|
275
|
+
|
|
276
|
+
describe('AllPossibleErrors', () => {
|
|
277
|
+
it('should return errors', () => {
|
|
278
|
+
const errors = field.getAllPossibleErrors()
|
|
279
|
+
expect(errors.baseErrors).not.toBeEmpty()
|
|
280
|
+
expect(errors.advancedSettingsErrors).not.toBeEmpty()
|
|
281
|
+
})
|
|
282
|
+
})
|
|
261
283
|
})
|
|
262
284
|
|
|
263
285
|
describe('Validation', () => {
|