@defra/forms-engine-plugin 4.0.32 → 4.0.33
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 +1 -1
- package/.server/server/plugins/engine/components/CheckboxesField.js +3 -0
- package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
- package/package.json +1 -1
- package/src/server/plugins/engine/components/CheckboxesField.test.ts +38 -18
- package/src/server/plugins/engine/components/CheckboxesField.ts +7 -1
|
@@ -9,7 +9,7 @@ export declare class CheckboxesField extends SelectionControlField {
|
|
|
9
9
|
constructor(def: CheckboxesFieldComponent, props: ConstructorParameters<typeof SelectionControlField>[1]);
|
|
10
10
|
getFormValueFromState(state: FormSubmissionState): (string | number | boolean)[] | undefined;
|
|
11
11
|
getFormValue(value?: FormStateValue | FormState): (string | number | boolean)[] | undefined;
|
|
12
|
-
getDisplayStringFromFormValue(selected: (string | number | boolean)[]): string;
|
|
12
|
+
getDisplayStringFromFormValue(selected: (string | number | boolean)[] | undefined): string;
|
|
13
13
|
getContextValueFromFormValue(values: (string | number | boolean)[] | undefined): (string | number | boolean)[];
|
|
14
14
|
getDisplayStringFromState(state: FormSubmissionState): string;
|
|
15
15
|
getContextValueFromState(state: FormSubmissionState): (string | number | boolean)[];
|
|
@@ -40,6 +40,9 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
40
40
|
const {
|
|
41
41
|
items
|
|
42
42
|
} = this;
|
|
43
|
+
if (!selected) {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
43
46
|
|
|
44
47
|
// Map selected values to text
|
|
45
48
|
return items.filter(item => selected.includes(item.value)).map(item => item.text).join(', ');
|
|
@@ -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","length","undefined","isValue","getDisplayStringFromFormValue","text","join","getContextValueFromFormValue","getDisplayStringFromState","getContextValueFromState","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 {\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 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\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\n if (options.required === false) {\n formSchema = formSchema.optional()\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(selected: (string | number | boolean)[]) {\n const { items } = this\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 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":"AACA,OAAOA,GAAG,MAA4B,KAAK;AAE3C,SAASC,WAAW;AACpB,SAASC,qBAAqB;AAS9B,OAAO,MAAMC,eAAe,SAASD,qBAAqB,CAAC;EAKzDE,WAAWA,CACTC,GAA6B,EAC7BC,KAA6D,EAC7D;IACA,KAAK,CAACD,GAAG,EAAEC,KAAK,CAAC;IAEjB,MAAM;MAAEC,QAAQ,EAAEC;IAAK,CAAC,GAAG,IAAI;IAC/B,MAAM;MAAEC;IAAQ,CAAC,GAAGJ,GAAG;IAEvB,IAAIK,UAAU,GACZF,IAAI,KAAK,QAAQ,GAAGR,GAAG,CAACW,KAAK,CAAS,CAAC,GAAGX,GAAG,CAACW,KAAK,CAAS,CAAC;IAE/D,MAAMC,WAAW,GAAGZ,GAAG,CAACQ,IAAI,CAAC,CAAC,CAAC,CAC5BK,KAAK,CAAC,GAAG,IAAI,CAACC,MAAM,CAAC,CACrBC,KAAK,CAAC,IAAI,CAACA,KAAK,CAAC;IAEpBL,UAAU,GAAGA,UAAU,CACpBM,KAAK,CAACJ,WAAW,CAAC,CAClBK,MAAM,CAAC,CAAC,CACRF,KAAK,CAAC,IAAI,CAACA,KAAK,CAAC,CACjBG,QAAQ,CAAC,CAAC;IAEb,IAAIT,OAAO,CAACS,QAAQ,KAAK,KAAK,EAAE;MAC9BR,UAAU,GAAGA,UAAU,CAACS,QAAQ,CAAC,CAAC;IACpC;IAEA,IAAI,CAACT,UAAU,GAAGA,UAAU,CAACU,OAAO,CAAC,EAAE,CAAC;IACxC,IAAI,CAACC,WAAW,GAAGX,UAAU,CAACU,OAAO,CAAC,IAAI,CAAC,CAACE,KAAK,CAAC,IAAI,CAAC;IACvD,IAAI,CAACb,OAAO,GAAGA,OAAO;EACxB;EAEAc,qBAAqBA,CAACC,KAA0B,EAAE;IAChD,MAAM;MAAER,KAAK;MAAES;IAAK,CAAC,GAAG,IAAI;;IAE5B;IACA,MAAMX,MAAM,GAAG,IAAI,CAACY,YAAY,CAACF,KAAK,CAACC,IAAI,CAAC,CAAC,IAAI,EAAE;;IAEnD;IACA,MAAME,QAAQ,GAAGX,KAAK,CACnBY,MAAM,CAAEC,IAAI,IAAKf,MAAM,CAACgB,QAAQ,CAACD,IAAI,CAACE,KAAK,CAAC,CAAC,CAC7CC,GAAG,CAAEH,IAAI,IAAKA,IAAI,CAACE,KAAK,CAAC;IAE5B,OAAOJ,QAAQ,CAACM,MAAM,GAAGN,QAAQ,GAAGO,SAAS;EAC/C;EAEAR,YAAYA,CAACK,KAAkC,EAAE;IAC/C,OAAO,IAAI,CAACI,OAAO,CAACJ,KAAK,CAAC,GAAGA,KAAK,GAAGG,SAAS;EAChD;EAEAE,6BAA6BA,
|
|
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","length","undefined","isValue","getDisplayStringFromFormValue","text","join","getContextValueFromFormValue","getDisplayStringFromState","getContextValueFromState","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 {\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 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\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\n if (options.required === false) {\n formSchema = formSchema.optional()\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 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
|
@@ -41,24 +41,6 @@ describe.each([
|
|
|
41
41
|
deny: ['5', '6', '7', '8']
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
|
-
{
|
|
45
|
-
component: {
|
|
46
|
-
title: 'String list title',
|
|
47
|
-
shortDescription: 'String list',
|
|
48
|
-
name: 'myComponent',
|
|
49
|
-
type: ComponentType.CheckboxesField,
|
|
50
|
-
list: 'listString',
|
|
51
|
-
options: {}
|
|
52
|
-
} satisfies CheckboxesFieldComponent,
|
|
53
|
-
|
|
54
|
-
options: {
|
|
55
|
-
label: 'string list',
|
|
56
|
-
list: listString,
|
|
57
|
-
examples: listStringExamples,
|
|
58
|
-
allow: ['1', '2', '3', '4'],
|
|
59
|
-
deny: ['5', '6', '7', '8']
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
44
|
{
|
|
63
45
|
component: {
|
|
64
46
|
title: 'Number list title',
|
|
@@ -407,5 +389,43 @@ describe.each([
|
|
|
407
389
|
expect(errors.advancedSettingsErrors).toBeEmpty()
|
|
408
390
|
})
|
|
409
391
|
})
|
|
392
|
+
|
|
393
|
+
describe('getDisplayStringFromFormValue', () => {
|
|
394
|
+
it('returns empty string when value is undefined', () => {
|
|
395
|
+
const checkboxField = field as CheckboxesField
|
|
396
|
+
const result = checkboxField.getDisplayStringFromFormValue(undefined)
|
|
397
|
+
expect(result).toBe('')
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
it('returns empty string when value is empty array', () => {
|
|
401
|
+
const checkboxField = field as CheckboxesField
|
|
402
|
+
const result = checkboxField.getDisplayStringFromFormValue([])
|
|
403
|
+
expect(result).toBe('')
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
it.each([...options.examples])(
|
|
407
|
+
'returns text for single selected value',
|
|
408
|
+
(item) => {
|
|
409
|
+
const checkboxField = field as CheckboxesField
|
|
410
|
+
const result = checkboxField.getDisplayStringFromFormValue([
|
|
411
|
+
item.value
|
|
412
|
+
])
|
|
413
|
+
expect(result).toBe(item.text)
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
it('returns comma-separated text for multiple selected values', () => {
|
|
418
|
+
const checkboxField = field as CheckboxesField
|
|
419
|
+
const item1 = options.examples[0]
|
|
420
|
+
const item2 = options.examples[2]
|
|
421
|
+
|
|
422
|
+
const result = checkboxField.getDisplayStringFromFormValue([
|
|
423
|
+
item1.value,
|
|
424
|
+
item2.value
|
|
425
|
+
])
|
|
426
|
+
|
|
427
|
+
expect(result).toBe(`${item1.text}, ${item2.text}`)
|
|
428
|
+
})
|
|
429
|
+
})
|
|
410
430
|
})
|
|
411
431
|
})
|
|
@@ -65,9 +65,15 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
65
65
|
return this.isValue(value) ? value : undefined
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
getDisplayStringFromFormValue(
|
|
68
|
+
getDisplayStringFromFormValue(
|
|
69
|
+
selected: (string | number | boolean)[] | undefined
|
|
70
|
+
) {
|
|
69
71
|
const { items } = this
|
|
70
72
|
|
|
73
|
+
if (!selected) {
|
|
74
|
+
return ''
|
|
75
|
+
}
|
|
76
|
+
|
|
71
77
|
// Map selected values to text
|
|
72
78
|
return items
|
|
73
79
|
.filter((item) => selected.includes(item.value))
|