@defra/forms-engine-plugin 4.6.1 → 4.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.server/server/plugins/engine/components/CheckboxesField.d.ts +10 -1
- package/.server/server/plugins/engine/components/CheckboxesField.js +44 -1
- package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
- package/package.json +2 -2
- package/src/server/plugins/engine/components/CheckboxesField.test.ts +56 -1
- package/src/server/plugins/engine/components/CheckboxesField.ts +45 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type CheckboxesFieldComponent, type Item } from '@defra/forms-model';
|
|
2
2
|
import { type ArraySchema } from 'joi';
|
|
3
3
|
import { SelectionControlField } from '~/src/server/plugins/engine/components/SelectionControlField.js';
|
|
4
|
-
import { type FormState, type FormStateValue, type FormSubmissionState } from '~/src/server/plugins/engine/types.js';
|
|
4
|
+
import { type ErrorMessageTemplateList, type FormState, type FormStateValue, type FormSubmissionState } from '~/src/server/plugins/engine/types.js';
|
|
5
5
|
export declare class CheckboxesField extends SelectionControlField {
|
|
6
6
|
options: CheckboxesFieldComponent['options'];
|
|
7
|
+
schema: CheckboxesFieldComponent['schema'];
|
|
7
8
|
formSchema: ArraySchema<string> | ArraySchema<number>;
|
|
8
9
|
stateSchema: ArraySchema<string> | ArraySchema<number>;
|
|
9
10
|
constructor(def: CheckboxesFieldComponent, props: ConstructorParameters<typeof SelectionControlField>[1]);
|
|
@@ -13,5 +14,13 @@ export declare class CheckboxesField extends SelectionControlField {
|
|
|
13
14
|
getContextValueFromFormValue(values: (string | number | boolean)[] | undefined): (string | number | boolean)[];
|
|
14
15
|
getDisplayStringFromState(state: FormSubmissionState): string;
|
|
15
16
|
getContextValueFromState(state: FormSubmissionState): (string | number | boolean)[];
|
|
17
|
+
/**
|
|
18
|
+
* For error preview page that shows all possible errors on a component
|
|
19
|
+
*/
|
|
20
|
+
getAllPossibleErrors(): ErrorMessageTemplateList;
|
|
21
|
+
/**
|
|
22
|
+
* Static version of getAllPossibleErrors that doesn't require a component instance.
|
|
23
|
+
*/
|
|
24
|
+
static getAllPossibleErrors(): ErrorMessageTemplateList;
|
|
16
25
|
isValue(value?: FormStateValue | FormState): value is Item['value'][];
|
|
17
26
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import joi from 'joi';
|
|
2
2
|
import { isFormValue } from "./FormComponent.js";
|
|
3
3
|
import { SelectionControlField } from "./SelectionControlField.js";
|
|
4
|
+
import { messageTemplate } from "../pageControllers/validationOptions.js";
|
|
4
5
|
export class CheckboxesField extends SelectionControlField {
|
|
5
6
|
constructor(def, props) {
|
|
6
7
|
super(def, props);
|
|
@@ -10,12 +11,27 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
10
11
|
const {
|
|
11
12
|
options
|
|
12
13
|
} = def;
|
|
14
|
+
const schema = 'schema' in def ? def.schema : {};
|
|
13
15
|
let formSchema = type === 'string' ? joi.array() : joi.array();
|
|
14
16
|
const itemsSchema = joi[type]().valid(...this.values).label(this.label);
|
|
15
|
-
formSchema = formSchema.items(itemsSchema).single().label(this.label).required()
|
|
17
|
+
formSchema = formSchema.items(itemsSchema).single().label(this.label).required().messages({
|
|
18
|
+
'array.min': 'Select at least {{#limit}} options from the list',
|
|
19
|
+
'array.max': 'Only {{#limit}} can be selected from the list',
|
|
20
|
+
'array.length': 'Select only {{#limit}} options from the list'
|
|
21
|
+
});
|
|
16
22
|
if (options.required === false) {
|
|
17
23
|
formSchema = formSchema.optional();
|
|
18
24
|
}
|
|
25
|
+
if (typeof schema?.length === 'number') {
|
|
26
|
+
formSchema = formSchema.length(schema.length);
|
|
27
|
+
} else {
|
|
28
|
+
if (typeof schema?.min === 'number') {
|
|
29
|
+
formSchema = formSchema.min(schema.min);
|
|
30
|
+
}
|
|
31
|
+
if (typeof schema?.max === 'number') {
|
|
32
|
+
formSchema = formSchema.max(schema.max);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
19
35
|
this.formSchema = formSchema.default([]);
|
|
20
36
|
this.stateSchema = formSchema.default(null).allow(null);
|
|
21
37
|
this.options = options;
|
|
@@ -72,6 +88,33 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
72
88
|
const values = this.getFormValueFromState(state);
|
|
73
89
|
return this.getContextValueFromFormValue(values);
|
|
74
90
|
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* For error preview page that shows all possible errors on a component
|
|
94
|
+
*/
|
|
95
|
+
getAllPossibleErrors() {
|
|
96
|
+
return CheckboxesField.getAllPossibleErrors();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Static version of getAllPossibleErrors that doesn't require a component instance.
|
|
101
|
+
*/
|
|
102
|
+
static getAllPossibleErrors() {
|
|
103
|
+
const parentErrors = SelectionControlField.getAllPossibleErrors();
|
|
104
|
+
return {
|
|
105
|
+
...parentErrors,
|
|
106
|
+
advancedSettingsErrors: [...parentErrors.advancedSettingsErrors, {
|
|
107
|
+
type: 'array.min',
|
|
108
|
+
template: messageTemplate.arrayMin
|
|
109
|
+
}, {
|
|
110
|
+
type: 'array.max',
|
|
111
|
+
template: messageTemplate.arrayMax
|
|
112
|
+
}, {
|
|
113
|
+
type: 'array.length',
|
|
114
|
+
template: messageTemplate.arrayLength
|
|
115
|
+
}]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
75
118
|
isValue(value) {
|
|
76
119
|
if (!Array.isArray(value)) {
|
|
77
120
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckboxesField.js","names":["joi","isFormValue","SelectionControlField","CheckboxesField","constructor","def","props","listType","type","options","formSchema","array","itemsSchema","valid","values","label","items","single","required","optional","default","stateSchema","allow","getFormValueFromState","state","name","getFormValue","selected","filter","item","includes","value","map","
|
|
1
|
+
{"version":3,"file":"CheckboxesField.js","names":["joi","isFormValue","SelectionControlField","messageTemplate","CheckboxesField","constructor","def","props","listType","type","options","schema","formSchema","array","itemsSchema","valid","values","label","items","single","required","messages","optional","length","min","max","default","stateSchema","allow","getFormValueFromState","state","name","getFormValue","selected","filter","item","includes","value","map","undefined","isValue","getDisplayStringFromFormValue","text","join","getContextValueFromFormValue","getDisplayStringFromState","getContextValueFromState","getAllPossibleErrors","parentErrors","advancedSettingsErrors","template","arrayMin","arrayMax","arrayLength","Array","isArray","every"],"sources":["../../../../../src/server/plugins/engine/components/CheckboxesField.ts"],"sourcesContent":["import { type CheckboxesFieldComponent, type Item } from '@defra/forms-model'\nimport joi, { type ArraySchema } from 'joi'\n\nimport { isFormValue } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { SelectionControlField } from '~/src/server/plugins/engine/components/SelectionControlField.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type ErrorMessageTemplateList,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nexport class CheckboxesField extends SelectionControlField {\n declare options: CheckboxesFieldComponent['options']\n declare schema: CheckboxesFieldComponent['schema']\n declare formSchema: ArraySchema<string> | ArraySchema<number>\n declare stateSchema: ArraySchema<string> | ArraySchema<number>\n\n constructor(\n def: CheckboxesFieldComponent,\n props: ConstructorParameters<typeof SelectionControlField>[1]\n ) {\n super(def, props)\n\n const { listType: type } = this\n const { options } = def\n const schema = 'schema' in def ? def.schema : {}\n\n let formSchema =\n type === 'string' ? joi.array<string>() : joi.array<number>()\n\n const itemsSchema = joi[type]()\n .valid(...this.values)\n .label(this.label)\n\n formSchema = formSchema\n .items(itemsSchema)\n .single()\n .label(this.label)\n .required()\n .messages({\n 'array.min': 'Select at least {{#limit}} options from the list',\n 'array.max': 'Only {{#limit}} can be selected from the list',\n 'array.length': 'Select only {{#limit}} options from the list'\n })\n\n if (options.required === false) {\n formSchema = formSchema.optional()\n }\n\n if (typeof schema?.length === 'number') {\n formSchema = formSchema.length(schema.length)\n } else {\n if (typeof schema?.min === 'number') {\n formSchema = formSchema.min(schema.min)\n }\n\n if (typeof schema?.max === 'number') {\n formSchema = formSchema.max(schema.max)\n }\n }\n\n this.formSchema = formSchema.default([])\n this.stateSchema = formSchema.default(null).allow(null)\n this.options = options\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const { items, name } = this\n\n // State checkbox values\n const values = this.getFormValue(state[name]) ?? []\n\n // Map (or discard) state values to item values\n const selected = items\n .filter((item) => values.includes(item.value))\n .map((item) => item.value)\n\n return selected.length ? selected : undefined\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isValue(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(\n selected: (string | number | boolean)[] | undefined\n ) {\n const { items } = this\n\n if (!selected) {\n return ''\n }\n\n // Map selected values to text\n return items\n .filter((item) => selected.includes(item.value))\n .map((item) => item.text)\n .join(', ')\n }\n\n getContextValueFromFormValue(\n values: (string | number | boolean)[] | undefined\n ): (string | number | boolean)[] {\n /**\n * For evaluation context purposes, optional {@link CheckboxesField}\n * with an undefined value (i.e. nothing selected) should default to [].\n * This way conditions are not evaluated against `undefined` which throws errors.\n * Currently these errors are caught and the evaluation returns default `false`.\n * @see {@link QuestionPageController.getNextPath} for `undefined` return value\n * @see {@link FormModel.makeCondition} for try/catch block with default `false`\n * For negative conditions this is a problem because E.g.\n * The condition: 'selectedchecks' does not contain 'someval'\n * should return true IF 'selectedchecks' is undefined, not throw and return false.\n */\n return values ?? []\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n // Selected checkbox values\n const selected = this.getFormValueFromState(state) ?? []\n\n // Map selected values to text\n return this.getDisplayStringFromFormValue(selected)\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const values = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(values)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return CheckboxesField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n const parentErrors = SelectionControlField.getAllPossibleErrors()\n\n return {\n ...parentErrors,\n advancedSettingsErrors: [\n ...parentErrors.advancedSettingsErrors,\n { type: 'array.min', template: messageTemplate.arrayMin },\n { type: 'array.max', template: messageTemplate.arrayMax },\n { type: 'array.length', template: messageTemplate.arrayLength }\n ]\n }\n }\n\n isValue(value?: FormStateValue | FormState): value is Item['value'][] {\n if (!Array.isArray(value)) {\n return false\n }\n\n // Skip checks when empty\n if (!value.length) {\n return true\n }\n\n return value.every(isFormValue)\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.1",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
},
|
|
84
84
|
"license": "SEE LICENSE IN LICENSE",
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@defra/forms-model": "^3.0.
|
|
86
|
+
"@defra/forms-model": "^3.0.647",
|
|
87
87
|
"@defra/hapi-tracing": "^1.29.0",
|
|
88
88
|
"@defra/interactive-map": "^0.0.17-alpha",
|
|
89
89
|
"@elastic/ecs-pino-format": "^1.5.0",
|
|
@@ -173,6 +173,61 @@ describe.each([
|
|
|
173
173
|
)
|
|
174
174
|
})
|
|
175
175
|
|
|
176
|
+
it('is configured with min/max items', () => {
|
|
177
|
+
const collectionLimited = new ComponentCollection(
|
|
178
|
+
[{ ...def, schema: { min: 2, max: 4 } }],
|
|
179
|
+
{ model }
|
|
180
|
+
)
|
|
181
|
+
const { formSchema } = collectionLimited
|
|
182
|
+
const { keys } = formSchema.describe()
|
|
183
|
+
|
|
184
|
+
expect(keys).toHaveProperty(
|
|
185
|
+
'myComponent',
|
|
186
|
+
expect.objectContaining({
|
|
187
|
+
items: [
|
|
188
|
+
{
|
|
189
|
+
allow: options.allow,
|
|
190
|
+
flags: {
|
|
191
|
+
label: def.shortDescription,
|
|
192
|
+
only: true
|
|
193
|
+
},
|
|
194
|
+
type: options.list.type
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
rules: [
|
|
198
|
+
{ args: { limit: 2 }, name: 'min' },
|
|
199
|
+
{ args: { limit: 4 }, name: 'max' }
|
|
200
|
+
]
|
|
201
|
+
})
|
|
202
|
+
)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('is configured with length items', () => {
|
|
206
|
+
const collectionLimited = new ComponentCollection(
|
|
207
|
+
[{ ...def, schema: { length: 3 } }],
|
|
208
|
+
{ model }
|
|
209
|
+
)
|
|
210
|
+
const { formSchema } = collectionLimited
|
|
211
|
+
const { keys } = formSchema.describe()
|
|
212
|
+
|
|
213
|
+
expect(keys).toHaveProperty(
|
|
214
|
+
'myComponent',
|
|
215
|
+
expect.objectContaining({
|
|
216
|
+
items: [
|
|
217
|
+
{
|
|
218
|
+
allow: options.allow,
|
|
219
|
+
flags: {
|
|
220
|
+
label: def.shortDescription,
|
|
221
|
+
only: true
|
|
222
|
+
},
|
|
223
|
+
type: options.list.type
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
rules: [{ args: { limit: 3 }, name: 'length' }]
|
|
227
|
+
})
|
|
228
|
+
)
|
|
229
|
+
})
|
|
230
|
+
|
|
176
231
|
it('adds errors for empty value', () => {
|
|
177
232
|
const result = collection.validate(getFormData())
|
|
178
233
|
|
|
@@ -386,7 +441,7 @@ describe.each([
|
|
|
386
441
|
it('should return errors', () => {
|
|
387
442
|
const errors = field.getAllPossibleErrors()
|
|
388
443
|
expect(errors.baseErrors).not.toBeEmpty()
|
|
389
|
-
expect(errors.advancedSettingsErrors).toBeEmpty()
|
|
444
|
+
expect(errors.advancedSettingsErrors).not.toBeEmpty()
|
|
390
445
|
})
|
|
391
446
|
})
|
|
392
447
|
|
|
@@ -5,7 +5,9 @@ import { isFormValue } from '~/src/server/plugins/engine/components/FormComponen
|
|
|
5
5
|
import { SelectionControlField } from '~/src/server/plugins/engine/components/SelectionControlField.js'
|
|
6
6
|
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
7
7
|
import { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
|
|
8
|
+
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
|
|
8
9
|
import {
|
|
10
|
+
type ErrorMessageTemplateList,
|
|
9
11
|
type FormState,
|
|
10
12
|
type FormStateValue,
|
|
11
13
|
type FormSubmissionState
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
|
|
14
16
|
export class CheckboxesField extends SelectionControlField {
|
|
15
17
|
declare options: CheckboxesFieldComponent['options']
|
|
18
|
+
declare schema: CheckboxesFieldComponent['schema']
|
|
16
19
|
declare formSchema: ArraySchema<string> | ArraySchema<number>
|
|
17
20
|
declare stateSchema: ArraySchema<string> | ArraySchema<number>
|
|
18
21
|
|
|
@@ -24,6 +27,7 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
24
27
|
|
|
25
28
|
const { listType: type } = this
|
|
26
29
|
const { options } = def
|
|
30
|
+
const schema = 'schema' in def ? def.schema : {}
|
|
27
31
|
|
|
28
32
|
let formSchema =
|
|
29
33
|
type === 'string' ? joi.array<string>() : joi.array<number>()
|
|
@@ -37,11 +41,28 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
37
41
|
.single()
|
|
38
42
|
.label(this.label)
|
|
39
43
|
.required()
|
|
44
|
+
.messages({
|
|
45
|
+
'array.min': 'Select at least {{#limit}} options from the list',
|
|
46
|
+
'array.max': 'Only {{#limit}} can be selected from the list',
|
|
47
|
+
'array.length': 'Select only {{#limit}} options from the list'
|
|
48
|
+
})
|
|
40
49
|
|
|
41
50
|
if (options.required === false) {
|
|
42
51
|
formSchema = formSchema.optional()
|
|
43
52
|
}
|
|
44
53
|
|
|
54
|
+
if (typeof schema?.length === 'number') {
|
|
55
|
+
formSchema = formSchema.length(schema.length)
|
|
56
|
+
} else {
|
|
57
|
+
if (typeof schema?.min === 'number') {
|
|
58
|
+
formSchema = formSchema.min(schema.min)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof schema?.max === 'number') {
|
|
62
|
+
formSchema = formSchema.max(schema.max)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
this.formSchema = formSchema.default([])
|
|
46
67
|
this.stateSchema = formSchema.default(null).allow(null)
|
|
47
68
|
this.options = options
|
|
@@ -112,6 +133,30 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
112
133
|
return this.getContextValueFromFormValue(values)
|
|
113
134
|
}
|
|
114
135
|
|
|
136
|
+
/**
|
|
137
|
+
* For error preview page that shows all possible errors on a component
|
|
138
|
+
*/
|
|
139
|
+
getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
140
|
+
return CheckboxesField.getAllPossibleErrors()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Static version of getAllPossibleErrors that doesn't require a component instance.
|
|
145
|
+
*/
|
|
146
|
+
static getAllPossibleErrors(): ErrorMessageTemplateList {
|
|
147
|
+
const parentErrors = SelectionControlField.getAllPossibleErrors()
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
...parentErrors,
|
|
151
|
+
advancedSettingsErrors: [
|
|
152
|
+
...parentErrors.advancedSettingsErrors,
|
|
153
|
+
{ type: 'array.min', template: messageTemplate.arrayMin },
|
|
154
|
+
{ type: 'array.max', template: messageTemplate.arrayMax },
|
|
155
|
+
{ type: 'array.length', template: messageTemplate.arrayLength }
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
115
160
|
isValue(value?: FormStateValue | FormState): value is Item['value'][] {
|
|
116
161
|
if (!Array.isArray(value)) {
|
|
117
162
|
return false
|