Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
},
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@defra/forms-model": "^3.0.644",
"@defra/forms-model": "^3.0.647",
"@defra/hapi-tracing": "^1.29.0",
"@defra/interactive-map": "^0.0.17-alpha",
"@elastic/ecs-pino-format": "^1.5.0",
Expand Down
57 changes: 56 additions & 1 deletion src/server/plugins/engine/components/CheckboxesField.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,61 @@ describe.each([
)
})

it('is configured with min/max items', () => {
const collectionLimited = new ComponentCollection(
[{ ...def, schema: { min: 2, max: 4 } }],
{ model }
)
const { formSchema } = collectionLimited
const { keys } = formSchema.describe()

expect(keys).toHaveProperty(
'myComponent',
expect.objectContaining({
items: [
{
allow: options.allow,
flags: {
label: def.shortDescription,
only: true
},
type: options.list.type
}
],
rules: [
{ args: { limit: 2 }, name: 'min' },
{ args: { limit: 4 }, name: 'max' }
]
})
)
})

it('is configured with length items', () => {
const collectionLimited = new ComponentCollection(
[{ ...def, schema: { length: 3 } }],
{ model }
)
const { formSchema } = collectionLimited
const { keys } = formSchema.describe()

expect(keys).toHaveProperty(
'myComponent',
expect.objectContaining({
items: [
{
allow: options.allow,
flags: {
label: def.shortDescription,
only: true
},
type: options.list.type
}
],
rules: [{ args: { limit: 3 }, name: 'length' }]
})
)
})

it('adds errors for empty value', () => {
const result = collection.validate(getFormData())

Expand Down Expand Up @@ -386,7 +441,7 @@ describe.each([
it('should return errors', () => {
const errors = field.getAllPossibleErrors()
expect(errors.baseErrors).not.toBeEmpty()
expect(errors.advancedSettingsErrors).toBeEmpty()
expect(errors.advancedSettingsErrors).not.toBeEmpty()
})
})

Expand Down
40 changes: 40 additions & 0 deletions src/server/plugins/engine/components/CheckboxesField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import { isFormValue } from '~/src/server/plugins/engine/components/FormComponen
import { SelectionControlField } from '~/src/server/plugins/engine/components/SelectionControlField.js'
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
import { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
import {
type ErrorMessageTemplateList,
type FormState,
type FormStateValue,
type FormSubmissionState
} from '~/src/server/plugins/engine/types.js'

export class CheckboxesField extends SelectionControlField {
declare options: CheckboxesFieldComponent['options']
declare schema: CheckboxesFieldComponent['schema']
declare formSchema: ArraySchema<string> | ArraySchema<number>
declare stateSchema: ArraySchema<string> | ArraySchema<number>

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

const { listType: type } = this
const { options } = def
const schema = 'schema' in def ? def.schema : {}

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

if (typeof schema?.length === 'number') {
formSchema = formSchema.length(schema.length)
} else {
if (typeof schema?.min === 'number') {
formSchema = formSchema.min(schema.min)
}

if (typeof schema?.max === 'number') {
formSchema = formSchema.max(schema.max)
}
}

this.formSchema = formSchema.default([])
this.stateSchema = formSchema.default(null).allow(null)
this.options = options
Expand Down Expand Up @@ -112,6 +128,30 @@ export class CheckboxesField extends SelectionControlField {
return this.getContextValueFromFormValue(values)
}

/**
* For error preview page that shows all possible errors on a component
*/
getAllPossibleErrors(): ErrorMessageTemplateList {
return CheckboxesField.getAllPossibleErrors()
}

/**
* Static version of getAllPossibleErrors that doesn't require a component instance.
*/
static getAllPossibleErrors(): ErrorMessageTemplateList {
const parentErrors = SelectionControlField.getAllPossibleErrors()

return {
...parentErrors,
advancedSettingsErrors: [
...parentErrors.advancedSettingsErrors,
{ type: 'array.min', template: messageTemplate.arrayMin },
{ type: 'array.max', template: messageTemplate.arrayMax },
{ type: 'array.length', template: messageTemplate.arrayLength }
]
}
}

isValue(value?: FormStateValue | FormState): value is Item['value'][] {
if (!Array.isArray(value)) {
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ export const messageTemplate: Record<string, JoiExpression> = {
) as JoiExpression,
dateFormat: '{{#title}} must be a real date',
dateMin: '{{#title}} must be the same as or after {{#limit}}',
dateMax: '{{#title}} must be the same as or before {{#limit}}'
dateMax: '{{#title}} must be the same as or before {{#limit}}',
arrayMax: 'Only {{#limit}} can be selected from the list',
arrayMin: 'Select at least {{#limit}} options from the list',
arrayLength: 'Select only {{#limit}} options from the list'
}

export const messages: LanguageMessagesExt = {
Expand Down
Loading