Skip to content

Commit 2ac494a

Browse files
Add min, max and length constraints to CheckboxesField (#368)
* Add min, max and length constraints to CheckboxesField * Bump @defra/forms-model to v3.0.647 * Update the error messages for Checkboxes schema contraints
1 parent a4b58b4 commit 2ac494a

File tree

5 files changed

+105
-7
lines changed

5 files changed

+105
-7
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
},
8484
"license": "SEE LICENSE IN LICENSE",
8585
"dependencies": {
86-
"@defra/forms-model": "^3.0.644",
86+
"@defra/forms-model": "^3.0.647",
8787
"@defra/hapi-tracing": "^1.29.0",
8888
"@defra/interactive-map": "^0.0.17-alpha",
8989
"@elastic/ecs-pino-format": "^1.5.0",

src/server/plugins/engine/components/CheckboxesField.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,61 @@ describe.each([
173173
)
174174
})
175175

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+
176231
it('adds errors for empty value', () => {
177232
const result = collection.validate(getFormData())
178233

@@ -386,7 +441,7 @@ describe.each([
386441
it('should return errors', () => {
387442
const errors = field.getAllPossibleErrors()
388443
expect(errors.baseErrors).not.toBeEmpty()
389-
expect(errors.advancedSettingsErrors).toBeEmpty()
444+
expect(errors.advancedSettingsErrors).not.toBeEmpty()
390445
})
391446
})
392447

src/server/plugins/engine/components/CheckboxesField.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ import { isFormValue } from '~/src/server/plugins/engine/components/FormComponen
55
import { SelectionControlField } from '~/src/server/plugins/engine/components/SelectionControlField.js'
66
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
77
import { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
8+
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
89
import {
10+
type ErrorMessageTemplateList,
911
type FormState,
1012
type FormStateValue,
1113
type FormSubmissionState
1214
} from '~/src/server/plugins/engine/types.js'
1315

1416
export class CheckboxesField extends SelectionControlField {
1517
declare options: CheckboxesFieldComponent['options']
18+
declare schema: CheckboxesFieldComponent['schema']
1619
declare formSchema: ArraySchema<string> | ArraySchema<number>
1720
declare stateSchema: ArraySchema<string> | ArraySchema<number>
1821

@@ -24,6 +27,7 @@ export class CheckboxesField extends SelectionControlField {
2427

2528
const { listType: type } = this
2629
const { options } = def
30+
const schema = 'schema' in def ? def.schema : {}
2731

2832
let formSchema =
2933
type === 'string' ? joi.array<string>() : joi.array<number>()
@@ -42,6 +46,18 @@ export class CheckboxesField extends SelectionControlField {
4246
formSchema = formSchema.optional()
4347
}
4448

49+
if (typeof schema?.length === 'number') {
50+
formSchema = formSchema.length(schema.length)
51+
} else {
52+
if (typeof schema?.min === 'number') {
53+
formSchema = formSchema.min(schema.min)
54+
}
55+
56+
if (typeof schema?.max === 'number') {
57+
formSchema = formSchema.max(schema.max)
58+
}
59+
}
60+
4561
this.formSchema = formSchema.default([])
4662
this.stateSchema = formSchema.default(null).allow(null)
4763
this.options = options
@@ -112,6 +128,30 @@ export class CheckboxesField extends SelectionControlField {
112128
return this.getContextValueFromFormValue(values)
113129
}
114130

131+
/**
132+
* For error preview page that shows all possible errors on a component
133+
*/
134+
getAllPossibleErrors(): ErrorMessageTemplateList {
135+
return CheckboxesField.getAllPossibleErrors()
136+
}
137+
138+
/**
139+
* Static version of getAllPossibleErrors that doesn't require a component instance.
140+
*/
141+
static getAllPossibleErrors(): ErrorMessageTemplateList {
142+
const parentErrors = SelectionControlField.getAllPossibleErrors()
143+
144+
return {
145+
...parentErrors,
146+
advancedSettingsErrors: [
147+
...parentErrors.advancedSettingsErrors,
148+
{ type: 'array.min', template: messageTemplate.arrayMin },
149+
{ type: 'array.max', template: messageTemplate.arrayMax },
150+
{ type: 'array.length', template: messageTemplate.arrayLength }
151+
]
152+
}
153+
}
154+
115155
isValue(value?: FormStateValue | FormState): value is Item['value'][] {
116156
if (!Array.isArray(value)) {
117157
return false

src/server/plugins/engine/pageControllers/validationOptions.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ export const messageTemplate: Record<string, JoiExpression> = {
6161
) as JoiExpression,
6262
dateFormat: '{{#title}} must be a real date',
6363
dateMin: '{{#title}} must be the same as or after {{#limit}}',
64-
dateMax: '{{#title}} must be the same as or before {{#limit}}'
64+
dateMax: '{{#title}} must be the same as or before {{#limit}}',
65+
arrayMax: 'Only {{#limit}} can be selected from the list',
66+
arrayMin: 'Select at least {{#limit}} options from the list',
67+
arrayLength: 'Select only {{#limit}} options from the list'
6568
}
6669

6770
export const messages: LanguageMessagesExt = {

0 commit comments

Comments
 (0)