@defra/forms-engine-plugin 4.0.6 → 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 (112) hide show
  1. package/.public/stylesheets/application.min.css +1 -1
  2. package/.public/stylesheets/application.min.css.map +1 -1
  3. package/.server/client/stylesheets/_location-input.scss +60 -0
  4. package/.server/client/stylesheets/application.scss +1 -0
  5. package/.server/client/stylesheets/shared.scss +1 -0
  6. package/.server/server/forms/components.json +7 -0
  7. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +40 -1
  8. package/.server/server/plugins/engine/components/ComponentBase.d.ts +2 -2
  9. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  10. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  11. package/.server/server/plugins/engine/components/DeclarationField.d.ts +81 -0
  12. package/.server/server/plugins/engine/components/DeclarationField.js +123 -0
  13. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -0
  14. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +121 -0
  15. package/.server/server/plugins/engine/components/EastingNorthingField.js +166 -0
  16. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -0
  17. package/.server/server/plugins/engine/components/LatLongField.d.ts +121 -0
  18. package/.server/server/plugins/engine/components/LatLongField.js +164 -0
  19. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -0
  20. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +134 -0
  21. package/.server/server/plugins/engine/components/LocationFieldBase.js +85 -0
  22. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -0
  23. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +108 -0
  24. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +96 -0
  25. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -0
  26. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +19 -0
  27. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +40 -0
  28. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -0
  29. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +19 -0
  30. package/.server/server/plugins/engine/components/OsGridRefField.js +56 -0
  31. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -0
  32. package/.server/server/plugins/engine/components/helpers/components.d.ts +3 -4
  33. package/.server/server/plugins/engine/components/helpers/components.js +24 -29
  34. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  35. package/.server/server/plugins/engine/components/index.d.ts +5 -0
  36. package/.server/server/plugins/engine/components/index.js +5 -0
  37. package/.server/server/plugins/engine/components/index.js.map +1 -1
  38. package/.server/server/plugins/engine/components/markdownParser.d.ts +2 -0
  39. package/.server/server/plugins/engine/components/markdownParser.js +28 -0
  40. package/.server/server/plugins/engine/components/markdownParser.js.map +1 -0
  41. package/.server/server/plugins/engine/components/types.d.ts +10 -0
  42. package/.server/server/plugins/engine/components/types.js.map +1 -1
  43. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +7 -0
  44. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  45. package/.server/server/plugins/engine/pageControllers/validationOptions.js +1 -0
  46. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  47. package/.server/server/plugins/engine/types/index.d.ts +1 -1
  48. package/.server/server/plugins/engine/types/index.js.map +1 -1
  49. package/.server/server/plugins/engine/types.d.ts +2 -2
  50. package/.server/server/plugins/engine/types.js.map +1 -1
  51. package/.server/server/plugins/engine/views/components/_location-field-base.html +53 -0
  52. package/.server/server/plugins/engine/views/components/declarationfield.html +14 -0
  53. package/.server/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
  54. package/.server/server/plugins/engine/views/components/latlongfield.html +5 -0
  55. package/.server/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
  56. package/.server/server/plugins/engine/views/components/osgridreffield.html +13 -0
  57. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  58. package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
  59. package/.server/server/plugins/nunjucks/filters/index.js +1 -0
  60. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
  61. package/.server/server/plugins/nunjucks/filters/merge.d.ts +7 -0
  62. package/.server/server/plugins/nunjucks/filters/merge.js +16 -0
  63. package/.server/server/plugins/nunjucks/filters/merge.js.map +1 -0
  64. package/.server/server/plugins/nunjucks/filters/merge.test.js +19 -0
  65. package/.server/server/plugins/nunjucks/filters/merge.test.js.map +1 -0
  66. package/package.json +3 -3
  67. package/src/client/stylesheets/_location-input.scss +60 -0
  68. package/src/client/stylesheets/application.scss +1 -0
  69. package/src/client/stylesheets/shared.scss +1 -0
  70. package/src/server/forms/components.json +7 -0
  71. package/src/server/forms/page-events.yaml +1 -1
  72. package/src/server/forms/register-as-a-unicorn-breeder.yaml +40 -1
  73. package/src/server/index.test.ts +1 -0
  74. package/src/server/plugins/engine/components/ComponentBase.ts +2 -1
  75. package/src/server/plugins/engine/components/ComponentCollection.ts +1 -0
  76. package/src/server/plugins/engine/components/DeclarationField.test.ts +426 -0
  77. package/src/server/plugins/engine/components/DeclarationField.ts +167 -0
  78. package/src/server/plugins/engine/components/EastingNorthingField.test.ts +665 -0
  79. package/src/server/plugins/engine/components/EastingNorthingField.ts +224 -0
  80. package/src/server/plugins/engine/components/LatLongField.test.ts +700 -0
  81. package/src/server/plugins/engine/components/LatLongField.ts +213 -0
  82. package/src/server/plugins/engine/components/LocationFieldBase.test.ts +253 -0
  83. package/src/server/plugins/engine/components/LocationFieldBase.ts +152 -0
  84. package/src/server/plugins/engine/components/LocationFieldHelpers.test.ts +338 -0
  85. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +123 -0
  86. package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +438 -0
  87. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +52 -0
  88. package/src/server/plugins/engine/components/OsGridRefField.test.ts +469 -0
  89. package/src/server/plugins/engine/components/OsGridRefField.ts +71 -0
  90. package/src/server/plugins/engine/components/helpers/components.test.ts +270 -0
  91. package/src/server/plugins/engine/components/helpers/components.ts +44 -47
  92. package/src/server/plugins/engine/components/helpers/helpers.test.ts +71 -1
  93. package/src/server/plugins/engine/components/index.ts +5 -0
  94. package/src/server/plugins/engine/components/markdownParser.ts +40 -0
  95. package/src/server/plugins/engine/components/types.ts +14 -0
  96. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +76 -3
  97. package/src/server/plugins/engine/models/SummaryViewModel.ts +5 -1
  98. package/src/server/plugins/engine/outputFormatters/adapter/v1.location.test.ts +356 -0
  99. package/src/server/plugins/engine/pageControllers/helpers/helpers.test.ts +4 -0
  100. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +8 -0
  101. package/src/server/plugins/engine/pageControllers/validationOptions.ts +4 -0
  102. package/src/server/plugins/engine/types/index.ts +2 -0
  103. package/src/server/plugins/engine/types.ts +4 -0
  104. package/src/server/plugins/engine/views/components/_location-field-base.html +53 -0
  105. package/src/server/plugins/engine/views/components/declarationfield.html +14 -0
  106. package/src/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
  107. package/src/server/plugins/engine/views/components/latlongfield.html +5 -0
  108. package/src/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
  109. package/src/server/plugins/engine/views/components/osgridreffield.html +13 -0
  110. package/src/server/plugins/nunjucks/filters/index.js +1 -0
  111. package/src/server/plugins/nunjucks/filters/merge.js +16 -0
  112. package/src/server/plugins/nunjucks/filters/merge.test.js +15 -0
@@ -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
+ })
@@ -0,0 +1,167 @@
1
+ import { type DeclarationFieldComponent, type Item } from '@defra/forms-model'
2
+ import joi, {
3
+ type ArraySchema,
4
+ type BooleanSchema,
5
+ type StringSchema
6
+ } from 'joi'
7
+
8
+ import {
9
+ FormComponent,
10
+ isFormValue
11
+ } from '~/src/server/plugins/engine/components/FormComponent.js'
12
+ import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
13
+ import {
14
+ type ErrorMessageTemplateList,
15
+ type FormPayload,
16
+ type FormState,
17
+ type FormStateValue,
18
+ type FormSubmissionError,
19
+ type FormSubmissionState,
20
+ type FormValue
21
+ } from '~/src/server/plugins/engine/types.js'
22
+
23
+ export class DeclarationField extends FormComponent {
24
+ private readonly DEFAULT_DECLARATION_LABEL = 'I understand and agree'
25
+
26
+ declare options: DeclarationFieldComponent['options']
27
+
28
+ declare declarationConfirmationLabel: string
29
+
30
+ declare formSchema: ArraySchema<StringSchema[]>
31
+ declare stateSchema: BooleanSchema
32
+ declare content: string
33
+
34
+ constructor(
35
+ def: DeclarationFieldComponent,
36
+ props: ConstructorParameters<typeof FormComponent>[1]
37
+ ) {
38
+ super(def, props)
39
+
40
+ const { options, content } = def
41
+
42
+ let checkboxSchema = joi.string().valid('true')
43
+
44
+ if (options.required !== false) {
45
+ checkboxSchema = checkboxSchema.required()
46
+ }
47
+
48
+ const formSchema = joi
49
+ .array()
50
+ .items(checkboxSchema, joi.string().valid('unchecked').strip())
51
+ .label(this.label)
52
+ .single()
53
+ .messages({
54
+ 'any.required': messageTemplate.declarationRequired as string,
55
+ 'any.unknown': messageTemplate.declarationRequired as string,
56
+ 'array.includesRequiredUnknowns':
57
+ messageTemplate.declarationRequired as string
58
+ }) as ArraySchema<StringSchema[]>
59
+
60
+ this.formSchema = formSchema
61
+ this.stateSchema = joi.boolean().cast('string').label(this.label).required()
62
+
63
+ this.options = options
64
+ this.content = content
65
+ this.declarationConfirmationLabel =
66
+ options.declarationConfirmationLabel ?? this.DEFAULT_DECLARATION_LABEL
67
+ }
68
+
69
+ getFormValueFromState(state: FormSubmissionState) {
70
+ const { name } = this
71
+ return state[name] === true ? 'true' : undefined
72
+ }
73
+
74
+ getFormDataFromState(state: FormSubmissionState): FormPayload {
75
+ const { name } = this
76
+ return { [name]: state[name] === true ? 'true' : undefined }
77
+ }
78
+
79
+ getStateFromValidForm(payload: FormPayload): FormState {
80
+ const { name } = this
81
+ const payloadValue = payload[name]
82
+ const value =
83
+ this.isValue(payloadValue) &&
84
+ payloadValue.length > 0 &&
85
+ payloadValue.every((v) => {
86
+ return v === 'true'
87
+ })
88
+
89
+ return { [name]: value }
90
+ }
91
+
92
+ getContextValueFromFormValue(value: FormValue | FormPayload): boolean {
93
+ return value === 'true'
94
+ }
95
+
96
+ getFormValue(value?: FormStateValue | FormState) {
97
+ return this.isValue(value) ? value : undefined
98
+ }
99
+
100
+ getDisplayStringFromFormValue(value: FormValue | FormPayload): string {
101
+ return value ? this.declarationConfirmationLabel : ''
102
+ }
103
+
104
+ getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {
105
+ const defaultDeclarationConfirmationLabel =
106
+ 'I confirm that I understand and accept this declaration'
107
+ const {
108
+ title,
109
+ hint,
110
+ content,
111
+ declarationConfirmationLabel = defaultDeclarationConfirmationLabel
112
+ } = this
113
+ return {
114
+ ...super.getViewModel(payload, errors),
115
+ hint: hint ? { text: hint } : undefined,
116
+ fieldset: {
117
+ legend: {
118
+ text: title
119
+ }
120
+ },
121
+ content,
122
+ values: payload[this.name],
123
+ items: [
124
+ {
125
+ text: declarationConfirmationLabel,
126
+ value: 'true'
127
+ }
128
+ ]
129
+ }
130
+ }
131
+
132
+ isValue(value?: FormStateValue | FormState): value is Item['value'][] {
133
+ if (!Array.isArray(value)) {
134
+ return false
135
+ }
136
+
137
+ // Skip checks when empty
138
+ if (!value.length) {
139
+ return true
140
+ }
141
+
142
+ return value.every(isFormValue)
143
+ }
144
+
145
+ /**
146
+ * For error preview page that shows all possible errors on a component
147
+ */
148
+ getAllPossibleErrors(): ErrorMessageTemplateList {
149
+ return DeclarationField.getAllPossibleErrors()
150
+ }
151
+
152
+ /**
153
+ * Static version of getAllPossibleErrors that doesn't require a component instance.
154
+ */
155
+ static getAllPossibleErrors(): ErrorMessageTemplateList {
156
+ return {
157
+ baseErrors: [
158
+ { type: 'required', template: messageTemplate.declarationRequired }
159
+ ],
160
+ advancedSettingsErrors: []
161
+ }
162
+ }
163
+
164
+ static isBool(value?: FormStateValue | FormState): value is boolean {
165
+ return isFormValue(value) && typeof value === 'boolean'
166
+ }
167
+ }