@defra/forms-engine-plugin 4.0.7 → 4.0.8

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.
Files changed (44) hide show
  1. package/.server/server/forms/components.json +7 -0
  2. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +12 -1
  3. package/.server/server/plugins/engine/components/ComponentBase.d.ts +1 -1
  4. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  5. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  6. package/.server/server/plugins/engine/components/DeclarationField.d.ts +81 -0
  7. package/.server/server/plugins/engine/components/DeclarationField.js +123 -0
  8. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -0
  9. package/.server/server/plugins/engine/components/helpers/components.d.ts +1 -1
  10. package/.server/server/plugins/engine/components/helpers/components.js +3 -0
  11. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  12. package/.server/server/plugins/engine/components/index.d.ts +1 -0
  13. package/.server/server/plugins/engine/components/index.js +1 -0
  14. package/.server/server/plugins/engine/components/index.js.map +1 -1
  15. package/.server/server/plugins/engine/pageControllers/validationOptions.js +1 -0
  16. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  17. package/.server/server/plugins/engine/views/components/declarationfield.html +14 -0
  18. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  19. package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
  20. package/.server/server/plugins/nunjucks/filters/index.js +1 -0
  21. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
  22. package/.server/server/plugins/nunjucks/filters/merge.d.ts +7 -0
  23. package/.server/server/plugins/nunjucks/filters/merge.js +16 -0
  24. package/.server/server/plugins/nunjucks/filters/merge.js.map +1 -0
  25. package/.server/server/plugins/nunjucks/filters/merge.test.js +19 -0
  26. package/.server/server/plugins/nunjucks/filters/merge.test.js.map +1 -0
  27. package/package.json +2 -2
  28. package/src/server/forms/components.json +7 -0
  29. package/src/server/forms/page-events.yaml +1 -1
  30. package/src/server/forms/register-as-a-unicorn-breeder.yaml +12 -1
  31. package/src/server/index.test.ts +1 -0
  32. package/src/server/plugins/engine/components/ComponentBase.ts +1 -0
  33. package/src/server/plugins/engine/components/ComponentCollection.ts +1 -0
  34. package/src/server/plugins/engine/components/DeclarationField.test.ts +426 -0
  35. package/src/server/plugins/engine/components/DeclarationField.ts +167 -0
  36. package/src/server/plugins/engine/components/helpers/components.ts +5 -0
  37. package/src/server/plugins/engine/components/index.ts +1 -0
  38. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +76 -3
  39. package/src/server/plugins/engine/models/SummaryViewModel.ts +5 -1
  40. package/src/server/plugins/engine/pageControllers/validationOptions.ts +4 -0
  41. package/src/server/plugins/engine/views/components/declarationfield.html +14 -0
  42. package/src/server/plugins/nunjucks/filters/index.js +1 -0
  43. package/src/server/plugins/nunjucks/filters/merge.js +16 -0
  44. package/src/server/plugins/nunjucks/filters/merge.test.js +15 -0
@@ -3,5 +3,5 @@
3
3
  * @this {NunjucksContext}
4
4
  * @param {string} name - The name of the component
5
5
  */
6
- export function field(this: NunjucksContext, name: string): import("../../engine/components/TextField.js").TextField | import("../../engine/components/SelectField.js").SelectField | import("../../engine/components/RadiosField.js").RadiosField | import("../../engine/components/YesNoField.js").YesNoField | import("../../engine/components/CheckboxesField.js").CheckboxesField | import("../../engine/components/DatePartsField.js").DatePartsField | import("../../engine/components/EastingNorthingField.js").EastingNorthingField | import("../../engine/components/EmailAddressField.js").EmailAddressField | import("../../engine/components/LatLongField.js").LatLongField | import("../../engine/components/MonthYearField.js").MonthYearField | import("../../engine/components/MultilineTextField.js").MultilineTextField | import("../../engine/components/NationalGridFieldNumberField.js").NationalGridFieldNumberField | import("../../engine/components/NumberField.js").NumberField | import("../../engine/components/OsGridRefField.js").OsGridRefField | import("../../engine/components/TelephoneNumberField.js").TelephoneNumberField | import("../../engine/components/UkAddressField.js").UkAddressField | import("../../engine/components/FileUploadField.js").FileUploadField | import("../../engine/components/Details.js").Details | import("../../engine/components/Html.js").Html | import("../../engine/components/InsetText.js").InsetText | import("../../engine/components/List.js").List | import("../../engine/components/Markdown.js").Markdown | undefined;
6
+ export function field(this: NunjucksContext, name: string): import("../../engine/components/TextField.js").TextField | import("../../engine/components/SelectField.js").SelectField | import("../../engine/components/RadiosField.js").RadiosField | import("../../engine/components/YesNoField.js").YesNoField | import("../../engine/components/CheckboxesField.js").CheckboxesField | import("../../engine/components/DatePartsField.js").DatePartsField | import("../../engine/components/DeclarationField.js").DeclarationField | import("../../engine/components/EastingNorthingField.js").EastingNorthingField | import("../../engine/components/EmailAddressField.js").EmailAddressField | import("../../engine/components/LatLongField.js").LatLongField | import("../../engine/components/MonthYearField.js").MonthYearField | import("../../engine/components/MultilineTextField.js").MultilineTextField | import("../../engine/components/NationalGridFieldNumberField.js").NationalGridFieldNumberField | import("../../engine/components/NumberField.js").NumberField | import("../../engine/components/OsGridRefField.js").OsGridRefField | import("../../engine/components/TelephoneNumberField.js").TelephoneNumberField | import("../../engine/components/UkAddressField.js").UkAddressField | import("../../engine/components/FileUploadField.js").FileUploadField | import("../../engine/components/Details.js").Details | import("../../engine/components/Html.js").Html | import("../../engine/components/InsetText.js").InsetText | import("../../engine/components/List.js").List | import("../../engine/components/Markdown.js").Markdown | undefined;
7
7
  import type { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js';
@@ -5,3 +5,4 @@ export { answer } from "~/src/server/plugins/nunjucks/filters/answer.js";
5
5
  export { href } from "~/src/server/plugins/nunjucks/filters/href.js";
6
6
  export { field } from "~/src/server/plugins/nunjucks/filters/field.js";
7
7
  export { page } from "~/src/server/plugins/nunjucks/filters/page.js";
8
+ export { merge } from "~/src/server/plugins/nunjucks/filters/merge.js";
@@ -5,4 +5,5 @@ export { answer } from "./answer.js";
5
5
  export { href } from "./href.js";
6
6
  export { field } from "./field.js";
7
7
  export { page } from "./page.js";
8
+ export { merge } from "./merge.js";
8
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["highlight","inspect","evaluate","answer","href","field","page"],"sources":["../../../../../src/server/plugins/nunjucks/filters/index.js"],"sourcesContent":["export { highlight } from '~/src/server/plugins/nunjucks/filters/highlight.js'\nexport { inspect } from '~/src/server/plugins/nunjucks/filters/inspect.js'\nexport { evaluate } from '~/src/server/plugins/nunjucks/filters/evaluate.js'\nexport { answer } from '~/src/server/plugins/nunjucks/filters/answer.js'\nexport { href } from '~/src/server/plugins/nunjucks/filters/href.js'\nexport { field } from '~/src/server/plugins/nunjucks/filters/field.js'\nexport { page } from '~/src/server/plugins/nunjucks/filters/page.js'\n"],"mappings":"AAAA,SAASA,SAAS;AAClB,SAASC,OAAO;AAChB,SAASC,QAAQ;AACjB,SAASC,MAAM;AACf,SAASC,IAAI;AACb,SAASC,KAAK;AACd,SAASC,IAAI","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["highlight","inspect","evaluate","answer","href","field","page","merge"],"sources":["../../../../../src/server/plugins/nunjucks/filters/index.js"],"sourcesContent":["export { highlight } from '~/src/server/plugins/nunjucks/filters/highlight.js'\nexport { inspect } from '~/src/server/plugins/nunjucks/filters/inspect.js'\nexport { evaluate } from '~/src/server/plugins/nunjucks/filters/evaluate.js'\nexport { answer } from '~/src/server/plugins/nunjucks/filters/answer.js'\nexport { href } from '~/src/server/plugins/nunjucks/filters/href.js'\nexport { field } from '~/src/server/plugins/nunjucks/filters/field.js'\nexport { page } from '~/src/server/plugins/nunjucks/filters/page.js'\nexport { merge } from '~/src/server/plugins/nunjucks/filters/merge.js'\n"],"mappings":"AAAA,SAASA,SAAS;AAClB,SAASC,OAAO;AAChB,SAASC,QAAQ;AACjB,SAASC,MAAM;AACf,SAASC,IAAI;AACb,SAASC,KAAK;AACd,SAASC,IAAI;AACb,SAASC,KAAK","ignoreList":[]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Nunjucks filter to get the page for a given path
3
+ * @param {Record<string, any>} targetDictionary - Object to extend
4
+ * @param {Record<string, any> | string} sourceDictionary - Object to merge into target
5
+ * @returns {Record<string, any>}
6
+ */
7
+ export function merge(targetDictionary: Record<string, any>, sourceDictionary: Record<string, any> | string): Record<string, any>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Nunjucks filter to get the page for a given path
3
+ * @param {Record<string, any>} targetDictionary - Object to extend
4
+ * @param {Record<string, any> | string} sourceDictionary - Object to merge into target
5
+ * @returns {Record<string, any>}
6
+ */
7
+ export function merge(targetDictionary, sourceDictionary) {
8
+ if (typeof sourceDictionary !== 'object') {
9
+ return targetDictionary;
10
+ }
11
+ return {
12
+ ...targetDictionary,
13
+ ...sourceDictionary
14
+ };
15
+ }
16
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.js","names":["merge","targetDictionary","sourceDictionary"],"sources":["../../../../../src/server/plugins/nunjucks/filters/merge.js"],"sourcesContent":["/**\n * Nunjucks filter to get the page for a given path\n * @param {Record<string, any>} targetDictionary - Object to extend\n * @param {Record<string, any> | string} sourceDictionary - Object to merge into target\n * @returns {Record<string, any>}\n */\nexport function merge(targetDictionary, sourceDictionary) {\n if (typeof sourceDictionary !== 'object') {\n return targetDictionary\n }\n\n return {\n ...targetDictionary,\n ...sourceDictionary\n }\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASA,KAAKA,CAACC,gBAAgB,EAAEC,gBAAgB,EAAE;EACxD,IAAI,OAAOA,gBAAgB,KAAK,QAAQ,EAAE;IACxC,OAAOD,gBAAgB;EACzB;EAEA,OAAO;IACL,GAAGA,gBAAgB;IACnB,GAAGC;EACL,CAAC;AACH","ignoreList":[]}
@@ -0,0 +1,19 @@
1
+ import { merge } from "./merge.js";
2
+ describe('merge', () => {
3
+ const propertyToMerge = {
4
+ lorem: 'ipsum'
5
+ };
6
+ it('should return the target if source is not an object', () => {
7
+ expect(merge(propertyToMerge, 'dolar')).toBe(propertyToMerge);
8
+ });
9
+ it('should merge the properties if they are valid', () => {
10
+ expect(merge({
11
+ lorem: 'dolar',
12
+ dolar: 'sit'
13
+ }, propertyToMerge)).toEqual({
14
+ lorem: 'ipsum',
15
+ dolar: 'sit'
16
+ });
17
+ });
18
+ });
19
+ //# sourceMappingURL=merge.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.test.js","names":["merge","describe","propertyToMerge","lorem","it","expect","toBe","dolar","toEqual"],"sources":["../../../../../src/server/plugins/nunjucks/filters/merge.test.js"],"sourcesContent":["import { merge } from '~/src/server/plugins/nunjucks/filters/merge.js'\n\ndescribe('merge', () => {\n const propertyToMerge = { lorem: 'ipsum' }\n it('should return the target if source is not an object', () => {\n expect(merge(propertyToMerge, 'dolar')).toBe(propertyToMerge)\n })\n\n it('should merge the properties if they are valid', () => {\n expect(merge({ lorem: 'dolar', dolar: 'sit' }, propertyToMerge)).toEqual({\n lorem: 'ipsum',\n dolar: 'sit'\n })\n })\n})\n"],"mappings":"AAAA,SAASA,KAAK;AAEdC,QAAQ,CAAC,OAAO,EAAE,MAAM;EACtB,MAAMC,eAAe,GAAG;IAAEC,KAAK,EAAE;EAAQ,CAAC;EAC1CC,EAAE,CAAC,qDAAqD,EAAE,MAAM;IAC9DC,MAAM,CAACL,KAAK,CAACE,eAAe,EAAE,OAAO,CAAC,CAAC,CAACI,IAAI,CAACJ,eAAe,CAAC;EAC/D,CAAC,CAAC;EAEFE,EAAE,CAAC,+CAA+C,EAAE,MAAM;IACxDC,MAAM,CAACL,KAAK,CAAC;MAAEG,KAAK,EAAE,OAAO;MAAEI,KAAK,EAAE;IAAM,CAAC,EAAEL,eAAe,CAAC,CAAC,CAACM,OAAO,CAAC;MACvEL,KAAK,EAAE,OAAO;MACdI,KAAK,EAAE;IACT,CAAC,CAAC;EACJ,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "4.0.7",
3
+ "version": "4.0.8",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -70,7 +70,7 @@
70
70
  },
71
71
  "license": "SEE LICENSE IN LICENSE",
72
72
  "dependencies": {
73
- "@defra/forms-model": "^3.0.569",
73
+ "@defra/forms-model": "^3.0.574",
74
74
  "@defra/hapi-tracing": "^1.26.0",
75
75
  "@elastic/ecs-pino-format": "^1.5.0",
76
76
  "@hapi/boom": "^10.0.1",
@@ -120,6 +120,13 @@
120
120
  "content": "### This is a H3 in markdown\n\n[An internal link](http://localhost:3009/fictional-page)\n\n[An external link](https://defra.gov.uk/fictional-page)",
121
121
  "options": {},
122
122
  "schema": {}
123
+ },
124
+ {
125
+ "type": "DeclarationField",
126
+ "name": "declaration",
127
+ "title": "Declaration",
128
+ "content": "By submitting this form, I agree to:\n\n- Provide accurate and complete information\n- Comply with all applicable regulations\n- Accept responsibility for any false statements",
129
+ "hint": "Please read and confirm the following terms"
123
130
  }
124
131
  ]
125
132
  },
@@ -59,7 +59,7 @@ pages:
59
59
  next: []
60
60
  id: da0fbdb4-a2de-4650-be16-9ba552af135f
61
61
  - id: 449a45f6-4541-4a46-91bd-8b8931b07b50
62
- title: Summary
62
+ title: ''
63
63
  path: '/summary'
64
64
  controller: SummaryPageController
65
65
  events:
@@ -219,7 +219,7 @@ pages:
219
219
  controller: FileUploadPageController
220
220
  section: Regnsa
221
221
  next:
222
- - path: '/how-many-unicorns-do-you-expect-to-breed-each-year'
222
+ - path: '/declaration'
223
223
  components:
224
224
  - name: dLzALM
225
225
  title: Documents
@@ -230,6 +230,17 @@ pages:
230
230
  schema:
231
231
  min: 1
232
232
  max: 3
233
+ - title: Declaration
234
+ path: '/declaration'
235
+ section: section
236
+ components:
237
+ - name: diLmal
238
+ title: Declaration
239
+ type: DeclarationField
240
+ content: 'Fill in this field'
241
+ options: {}
242
+ next:
243
+ - path: '/summary'
233
244
  conditions:
234
245
  - displayName: Address is different
235
246
  name: IrVmYz
@@ -585,6 +585,7 @@ describe('prepareEnvironment', () => {
585
585
  'href',
586
586
  'field',
587
587
  'page',
588
+ 'merge',
588
589
  'markdown'
589
590
  ]
590
591
 
@@ -90,6 +90,7 @@ export type ComponentSchema =
90
90
  | ArraySchema<boolean>
91
91
  | ArraySchema<object>
92
92
  | BooleanSchema<string>
93
+ | BooleanSchema
93
94
  | DateSchema
94
95
  | NumberSchema<string>
95
96
  | NumberSchema
@@ -256,6 +256,7 @@ export class ComponentCollection {
256
256
  */
257
257
  validate(value: FormPayload = {}): FormValidationResult<FormPayload> {
258
258
  const result = this.formSchema.validate(value, opts)
259
+
259
260
  const details = result.error?.details
260
261
 
261
262
  return {
@@ -0,0 +1,426 @@
1
+ import {
2
+ ComponentType,
3
+ type DeclarationFieldComponent
4
+ } from '@defra/forms-model'
5
+
6
+ import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
7
+ import { DeclarationField } from '~/src/server/plugins/engine/components/DeclarationField.js'
8
+ import {
9
+ getAnswer,
10
+ type Field
11
+ } from '~/src/server/plugins/engine/components/helpers/components.js'
12
+ import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
13
+ import definition from '~/test/form/definitions/blank.js'
14
+ import { getFormData, getFormState } from '~/test/helpers/component-helpers.js'
15
+
16
+ describe('DeclarationField', () => {
17
+ let model: FormModel
18
+
19
+ beforeEach(() => {
20
+ model = new FormModel(definition, {
21
+ basePath: 'test'
22
+ })
23
+ })
24
+
25
+ describe('Defaults', () => {
26
+ let def: DeclarationFieldComponent
27
+ let collection: ComponentCollection
28
+ let field: Field
29
+
30
+ beforeEach(() => {
31
+ def = {
32
+ title: 'Example Declaration Component',
33
+ name: 'myComponent',
34
+ content: 'Lorem ipsum dolar sit amet',
35
+ shortDescription: 'Terms and conditions',
36
+ type: ComponentType.DeclarationField,
37
+ options: {}
38
+ } satisfies DeclarationFieldComponent
39
+
40
+ collection = new ComponentCollection([def], { model })
41
+ field = collection.fields[0]
42
+ })
43
+
44
+ describe('Schema', () => {
45
+ it('uses component title as label as default', () => {
46
+ const { formSchema } = collection
47
+ const { keys } = formSchema.describe()
48
+
49
+ expect(keys).toHaveProperty(
50
+ 'myComponent',
51
+ expect.objectContaining({
52
+ flags: expect.objectContaining({
53
+ label: 'Terms and conditions'
54
+ })
55
+ })
56
+ )
57
+ })
58
+
59
+ it('uses component name as keys', () => {
60
+ const { formSchema } = collection
61
+ const { keys } = formSchema.describe()
62
+
63
+ expect(field.keys).toEqual(['myComponent'])
64
+ expect(field.collection).toBeUndefined()
65
+
66
+ for (const key of field.keys) {
67
+ expect(keys).toHaveProperty(key)
68
+ }
69
+ })
70
+
71
+ it('is required by default', () => {
72
+ const { formSchema } = collection
73
+ const { keys } = formSchema.describe()
74
+
75
+ expect(keys).toHaveProperty(
76
+ 'myComponent',
77
+ expect.objectContaining({
78
+ items: expect.arrayContaining([
79
+ expect.objectContaining({
80
+ allow: ['true'],
81
+ flags: expect.objectContaining({
82
+ presence: 'required'
83
+ })
84
+ })
85
+ ])
86
+ })
87
+ )
88
+ })
89
+
90
+ it('may have unchecked value in addition to true', () => {
91
+ const { formSchema } = collection
92
+ const { keys } = formSchema.describe()
93
+
94
+ expect(keys).toHaveProperty(
95
+ 'myComponent',
96
+ expect.objectContaining({
97
+ items: expect.arrayContaining([
98
+ expect.objectContaining({
99
+ allow: ['unchecked'],
100
+ flags: expect.objectContaining({
101
+ result: 'strip'
102
+ })
103
+ })
104
+ ])
105
+ })
106
+ )
107
+
108
+ expect(keys).toHaveProperty(
109
+ 'myComponent',
110
+ expect.objectContaining({
111
+ items: expect.arrayContaining([
112
+ expect.objectContaining({
113
+ allow: ['unchecked']
114
+ })
115
+ ])
116
+ })
117
+ )
118
+ })
119
+
120
+ it('is optional when configured', () => {
121
+ const collectionOptional = new ComponentCollection(
122
+ [{ ...def, options: { required: false } }],
123
+ { model }
124
+ )
125
+
126
+ const { formSchema } = collectionOptional
127
+ const { keys } = formSchema.describe()
128
+
129
+ expect(keys).toHaveProperty(
130
+ 'myComponent',
131
+ expect.objectContaining({
132
+ items: expect.arrayContaining([
133
+ expect.objectContaining({
134
+ allow: ['true'],
135
+ flags: expect.not.objectContaining({
136
+ presence: 'required'
137
+ })
138
+ })
139
+ ])
140
+ })
141
+ )
142
+
143
+ const result = collectionOptional.validate(getFormData(['unchecked']))
144
+ expect(result.errors).toBeUndefined()
145
+ })
146
+
147
+ it('accepts valid values', () => {
148
+ const result1 = collection.validate(getFormData(['unchecked', 'true']))
149
+
150
+ expect(result1.errors).toBeUndefined()
151
+ })
152
+
153
+ it('adds errors for empty value', () => {
154
+ const result = collection.validate(getFormData(['unchecked']))
155
+
156
+ expect(result.errors).toEqual([
157
+ expect.objectContaining({
158
+ text: 'You must confirm you understand and agree with the terms and conditions to continue'
159
+ })
160
+ ])
161
+ })
162
+
163
+ it('adds errors for invalid values', () => {
164
+ const result1 = collection.validate(getFormData(['invalid']))
165
+ const result2 = collection.validate(
166
+ // @ts-expect-error - Allow invalid param for test
167
+ getFormData({ unknown: 'invalid' })
168
+ )
169
+ // @ts-expect-error - Allow invalid param for test
170
+ const result3 = collection.validate('false')
171
+
172
+ expect(result1.errors).toBeTruthy()
173
+ expect(result2.errors).toBeTruthy()
174
+ expect(result3.errors).toBeTruthy()
175
+ })
176
+ })
177
+
178
+ describe('State', () => {
179
+ it('returns text from state', () => {
180
+ const state1 = getFormState(true)
181
+ const state2 = getFormState()
182
+ // context - boolean
183
+ // state - boolean
184
+ // string - I confirm that I understand and accept this declaration
185
+ const answer1 = getAnswer(field, state1)
186
+ const answer2 = getAnswer(field, state2)
187
+
188
+ expect(answer1).toBe('I understand and agree')
189
+ expect(answer2).toBe('')
190
+ })
191
+
192
+ it('returns payload from state', () => {
193
+ const state1 = getFormState(true)
194
+ const state2 = getFormState(null)
195
+
196
+ const payload1 = field.getFormDataFromState(state1)
197
+ const payload2 = field.getFormDataFromState(state2)
198
+
199
+ expect(payload1).toEqual(getFormData('true'))
200
+ expect(payload2).toEqual(getFormData())
201
+ })
202
+
203
+ it('returns value from state', () => {
204
+ const state1 = getFormState(true)
205
+ const state2 = getFormState(null)
206
+
207
+ const value1 = field.getFormValueFromState(state1)
208
+ const value2 = field.getFormValueFromState(state2)
209
+
210
+ expect(value1).toBe('true')
211
+ expect(value2).toBeUndefined()
212
+ })
213
+
214
+ it('returns context for conditions and form submission', () => {
215
+ const state1 = getFormState(true)
216
+ const state2 = getFormState(null)
217
+
218
+ const value1 = field.getContextValueFromState(state1)
219
+ const value2 = field.getContextValueFromState(state2)
220
+
221
+ expect(value1).toBe(true)
222
+ expect(value2).toBe(false)
223
+ })
224
+
225
+ it('returns state from payload', () => {
226
+ const payload1 = getFormData(['true'])
227
+ const payload2 = getFormData([])
228
+ const payload3 = getFormData(['unchecked'])
229
+
230
+ const value1 = field.getStateFromValidForm(payload1)
231
+ const value2 = field.getStateFromValidForm(payload2)
232
+ const value3 = field.getStateFromValidForm(payload3)
233
+
234
+ expect(value1).toEqual(getFormState(true))
235
+ expect(value2).toEqual(getFormState(false))
236
+ expect(value3).toEqual(getFormState(false))
237
+ })
238
+ })
239
+
240
+ describe('View model', () => {
241
+ it('sets Nunjucks component defaults', () => {
242
+ const viewModel = field.getViewModel(getFormData([]))
243
+
244
+ expect(viewModel).toEqual(
245
+ expect.objectContaining({
246
+ label: { text: def.title },
247
+ name: 'myComponent',
248
+ attributes: {},
249
+ values: [],
250
+ content: 'Lorem ipsum dolar sit amet',
251
+ id: 'myComponent',
252
+ fieldset: {
253
+ legend: {
254
+ text: 'Example Declaration Component'
255
+ }
256
+ },
257
+ items: [
258
+ {
259
+ value: 'true',
260
+ text: 'I understand and agree'
261
+ }
262
+ ]
263
+ })
264
+ )
265
+ })
266
+
267
+ it('sets Nunjucks component value when posted', () => {
268
+ def = {
269
+ ...def,
270
+ hint: 'Please read and confirm the following'
271
+ } satisfies DeclarationFieldComponent
272
+
273
+ collection = new ComponentCollection([def], { model })
274
+ field = collection.fields[0]
275
+ const viewModel = field.getViewModel(getFormData(['true']))
276
+
277
+ expect(viewModel).toEqual(
278
+ expect.objectContaining({
279
+ values: ['true'],
280
+ hint: {
281
+ text: 'Please read and confirm the following'
282
+ }
283
+ })
284
+ )
285
+ })
286
+
287
+ it('sets custom message when in def', () => {
288
+ def = {
289
+ ...def,
290
+ title: 'Declaration',
291
+ content:
292
+ 'Declaration:\n' +
293
+ 'By submitting this form, I consent to the collection and processing of my personal data for the purposes described.\n' +
294
+ 'I understand that my data may be shared with authorised third parties where required by law',
295
+ options: {
296
+ declarationConfirmationLabel:
297
+ 'I consent to the processing of my personal data'
298
+ }
299
+ } satisfies DeclarationFieldComponent
300
+
301
+ collection = new ComponentCollection([def], { model })
302
+ field = collection.fields[0]
303
+
304
+ const viewModel = field.getViewModel(getFormData(['unchecked', 'true']))
305
+
306
+ expect(viewModel).toEqual(
307
+ expect.objectContaining({
308
+ items: [
309
+ {
310
+ value: 'true',
311
+ text: 'I consent to the processing of my personal data'
312
+ }
313
+ ]
314
+ })
315
+ )
316
+ })
317
+ })
318
+
319
+ describe('AllPossibleErrors', () => {
320
+ it('should return errors', () => {
321
+ const errors = field.getAllPossibleErrors()
322
+ expect(errors.baseErrors).not.toBeEmpty()
323
+ expect(errors.advancedSettingsErrors).toBeEmpty()
324
+ })
325
+ })
326
+
327
+ describe('getFormValue', () => {
328
+ test('should return correct value', () => {
329
+ expect(field.getFormValue(undefined)).toBeUndefined()
330
+ expect(field.getFormValue([true])).toEqual([true])
331
+ expect(field.getFormValue([])).toEqual([])
332
+ expect(field.getFormValue({})).toBeUndefined()
333
+ })
334
+ })
335
+ })
336
+
337
+ describe('Validation', () => {
338
+ describe.each([
339
+ {
340
+ description: 'Default',
341
+ component: {
342
+ title: 'Terms and conditions',
343
+ shortDescription: 'The terms and conditions',
344
+ content: 'Lorem ipsum dolar sit amet',
345
+ name: 'myComponent',
346
+ type: ComponentType.DeclarationField,
347
+ options: {}
348
+ } satisfies DeclarationFieldComponent,
349
+ assertions: [
350
+ {
351
+ input: getFormData(['unchecked', 'true']),
352
+ output: {
353
+ value: {
354
+ myComponent: ['true']
355
+ },
356
+ errors: undefined
357
+ }
358
+ }
359
+ ]
360
+ },
361
+ {
362
+ description: 'Use short description if it exists',
363
+ component: {
364
+ title: 'Terms and conditions',
365
+ shortDescription: 'Terms and conditions',
366
+ content: 'Lorem ipsum dolar sit amet',
367
+ name: 'myComponent',
368
+ type: ComponentType.DeclarationField,
369
+ options: {}
370
+ } satisfies DeclarationFieldComponent,
371
+ assertions: [
372
+ {
373
+ input: getFormData(['unchecked']),
374
+ output: {
375
+ value: { myComponent: [] },
376
+ errors: [
377
+ expect.objectContaining({
378
+ text: 'You must confirm you understand and agree with the terms and conditions to continue'
379
+ })
380
+ ]
381
+ }
382
+ }
383
+ ]
384
+ },
385
+ {
386
+ description: 'Optional field',
387
+ component: {
388
+ title: 'Example text field',
389
+ name: 'myComponent',
390
+ content: 'Lorem ipsum dolar sit amet',
391
+ type: ComponentType.DeclarationField,
392
+ options: {
393
+ required: false
394
+ }
395
+ } satisfies DeclarationFieldComponent,
396
+ assertions: [
397
+ {
398
+ input: getFormData(['unchecked']),
399
+ output: { value: { myComponent: [] } }
400
+ }
401
+ ]
402
+ }
403
+ ])('$description', ({ component: def, assertions }) => {
404
+ let collection: ComponentCollection
405
+
406
+ beforeEach(() => {
407
+ collection = new ComponentCollection([def], { model })
408
+ })
409
+
410
+ it.each([...assertions])(
411
+ 'validates custom example',
412
+ ({ input, output }) => {
413
+ const result = collection.validate(input)
414
+ expect(result).toEqual(output)
415
+ }
416
+ )
417
+ })
418
+ })
419
+
420
+ describe('isBool', () => {
421
+ test('should return correct boolean', () => {
422
+ expect(DeclarationField.isBool('string')).toBe(false)
423
+ expect(DeclarationField.isBool(true)).toBe(true)
424
+ })
425
+ })
426
+ })