@defra/forms-engine-plugin 2.1.4 → 2.1.6

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 (135) hide show
  1. package/.server/server/plugins/engine/components/ComponentBase.d.ts +2 -2
  2. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  3. package/.server/server/plugins/engine/components/ComponentCollection.d.ts +2 -2
  4. package/.server/server/plugins/engine/components/ComponentCollection.js +1 -1
  5. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  6. package/.server/server/plugins/engine/components/TelephoneNumberField.js +1 -1
  7. package/.server/server/plugins/engine/components/TelephoneNumberField.js.map +1 -1
  8. package/.server/server/plugins/engine/components/YesNoField.js +1 -1
  9. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  10. package/.server/server/plugins/engine/components/{helpers.d.ts → helpers/components.d.ts} +3 -10
  11. package/.server/server/plugins/engine/components/{helpers.js → helpers/components.js} +16 -29
  12. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -0
  13. package/.server/server/plugins/engine/components/helpers/index.d.ts +11 -0
  14. package/.server/server/plugins/engine/components/helpers/index.js +15 -0
  15. package/.server/server/plugins/engine/components/helpers/index.js.map +1 -0
  16. package/.server/server/plugins/engine/helpers.d.ts +1 -1
  17. package/.server/server/plugins/engine/helpers.js +1 -1
  18. package/.server/server/plugins/engine/helpers.js.map +1 -1
  19. package/.server/server/plugins/engine/models/FormModel.d.ts +2 -2
  20. package/.server/server/plugins/engine/models/FormModel.js +2 -2
  21. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  22. package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +1 -1
  23. package/.server/server/plugins/engine/models/SummaryViewModel.js +1 -1
  24. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  25. package/.server/server/plugins/engine/models/types.d.ts +2 -2
  26. package/.server/server/plugins/engine/models/types.js.map +1 -1
  27. package/.server/server/plugins/engine/outputFormatters/adapter/v1.d.ts +6 -0
  28. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +23 -0
  29. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -0
  30. package/.server/server/plugins/engine/outputFormatters/human/v1.d.ts +2 -2
  31. package/.server/server/plugins/engine/outputFormatters/human/v1.js +3 -2
  32. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  33. package/.server/server/plugins/engine/outputFormatters/index.d.ts +4 -3
  34. package/.server/server/plugins/engine/outputFormatters/index.js +4 -0
  35. package/.server/server/plugins/engine/outputFormatters/index.js.map +1 -1
  36. package/.server/server/plugins/engine/outputFormatters/machine/v1.d.ts +2 -2
  37. package/.server/server/plugins/engine/outputFormatters/machine/v1.js +2 -2
  38. package/.server/server/plugins/engine/outputFormatters/machine/v1.js.map +1 -1
  39. package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +4 -1
  40. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  41. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +1 -1
  42. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  43. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +6 -5
  44. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  45. package/.server/server/plugins/engine/pageControllers/helpers/index.d.ts +9 -0
  46. package/.server/server/plugins/engine/pageControllers/helpers/index.js +15 -0
  47. package/.server/server/plugins/engine/pageControllers/helpers/index.js.map +1 -0
  48. package/.server/server/plugins/engine/pageControllers/{helpers.d.ts → helpers/pages.d.ts} +1 -10
  49. package/.server/server/plugins/engine/pageControllers/{helpers.js → helpers/pages.js} +2 -17
  50. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -0
  51. package/.server/server/plugins/engine/routes/index.d.ts +1 -1
  52. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  53. package/.server/server/plugins/engine/routes/questions.js.map +1 -1
  54. package/.server/server/plugins/engine/services/localFormsService.d.ts +1 -1
  55. package/.server/server/plugins/engine/services/notifyService.d.ts +7 -2
  56. package/.server/server/plugins/engine/services/notifyService.js +12 -3
  57. package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
  58. package/.server/server/plugins/engine/types/index.d.ts +10 -0
  59. package/.server/server/plugins/engine/types/index.js +4 -0
  60. package/.server/server/plugins/engine/types/index.js.map +1 -0
  61. package/.server/server/plugins/engine/types/schema.d.ts +5 -0
  62. package/.server/server/plugins/engine/types/schema.js +24 -0
  63. package/.server/server/plugins/engine/types/schema.js.map +1 -0
  64. package/.server/server/plugins/engine/types.d.ts +39 -3
  65. package/.server/server/plugins/engine/types.js +4 -0
  66. package/.server/server/plugins/engine/types.js.map +1 -1
  67. package/.server/server/plugins/nunjucks/filters/answer.js +2 -2
  68. package/.server/server/plugins/nunjucks/filters/answer.js.map +1 -1
  69. package/.server/server/plugins/nunjucks/filters/answer.test.js +2 -2
  70. package/.server/server/plugins/nunjucks/filters/answer.test.js.map +1 -1
  71. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  72. package/.server/server/plugins/nunjucks/filters/page.d.ts +1 -1
  73. package/.server/server/types.d.ts +1 -1
  74. package/.server/server/types.js.map +1 -1
  75. package/package.json +6 -1
  76. package/src/server/plugins/engine/components/AutocompleteField.test.ts +1 -1
  77. package/src/server/plugins/engine/components/CheckboxesField.test.ts +1 -1
  78. package/src/server/plugins/engine/components/ComponentBase.ts +2 -2
  79. package/src/server/plugins/engine/components/ComponentCollection.ts +2 -2
  80. package/src/server/plugins/engine/components/DatePartsField.test.ts +1 -1
  81. package/src/server/plugins/engine/components/Details.test.ts +1 -1
  82. package/src/server/plugins/engine/components/EmailAddressField.test.ts +1 -1
  83. package/src/server/plugins/engine/components/FileUploadField.test.ts +2 -2
  84. package/src/server/plugins/engine/components/Html.test.ts +1 -1
  85. package/src/server/plugins/engine/components/InsetText.test.ts +1 -1
  86. package/src/server/plugins/engine/components/List.test.ts +1 -1
  87. package/src/server/plugins/engine/components/Markdown.test.ts +1 -1
  88. package/src/server/plugins/engine/components/MonthYearField.test.ts +1 -1
  89. package/src/server/plugins/engine/components/MultilineTextField.test.ts +1 -1
  90. package/src/server/plugins/engine/components/NumberField.test.ts +1 -1
  91. package/src/server/plugins/engine/components/RadiosField.test.ts +1 -1
  92. package/src/server/plugins/engine/components/SelectField.test.ts +1 -1
  93. package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +1 -1
  94. package/src/server/plugins/engine/components/TelephoneNumberField.ts +1 -1
  95. package/src/server/plugins/engine/components/TextField.test.ts +1 -1
  96. package/src/server/plugins/engine/components/UkAddressField.test.ts +1 -1
  97. package/src/server/plugins/engine/components/YesNoField.test.ts +1 -1
  98. package/src/server/plugins/engine/components/YesNoField.ts +1 -1
  99. package/src/server/plugins/engine/components/{helpers.ts → helpers/components.ts} +41 -77
  100. package/src/server/plugins/engine/components/{helpers.test.ts → helpers/helpers.test.ts} +1 -1
  101. package/src/server/plugins/engine/components/helpers/index.ts +38 -0
  102. package/src/server/plugins/engine/helpers.test.ts +1 -1
  103. package/src/server/plugins/engine/helpers.ts +2 -2
  104. package/src/server/plugins/engine/models/FormModel.ts +2 -2
  105. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +1 -1
  106. package/src/server/plugins/engine/models/SummaryViewModel.ts +2 -2
  107. package/src/server/plugins/engine/models/types.ts +4 -4
  108. package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +506 -0
  109. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +53 -0
  110. package/src/server/plugins/engine/outputFormatters/human/v1.ts +8 -6
  111. package/src/server/plugins/engine/outputFormatters/index.ts +11 -3
  112. package/src/server/plugins/engine/outputFormatters/machine/v1.test.ts +1 -1
  113. package/src/server/plugins/engine/outputFormatters/machine/v1.ts +7 -3
  114. package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +1 -1
  115. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +1 -1
  116. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1 -1
  117. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +1 -1
  118. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +16 -5
  119. package/src/server/plugins/engine/pageControllers/{helpers.test.ts → helpers/helpers.test.ts} +2 -2
  120. package/src/server/plugins/engine/pageControllers/helpers/index.ts +20 -0
  121. package/src/server/plugins/engine/pageControllers/{helpers.ts → helpers/pages.ts} +1 -22
  122. package/src/server/plugins/engine/routes/index.ts +1 -1
  123. package/src/server/plugins/engine/routes/questions.test.ts +1 -1
  124. package/src/server/plugins/engine/routes/questions.ts +1 -1
  125. package/src/server/plugins/engine/services/notifyService.test.ts +156 -1
  126. package/src/server/plugins/engine/services/notifyService.ts +25 -4
  127. package/src/server/plugins/engine/types/index.ts +96 -0
  128. package/src/server/plugins/engine/types/schema.test.ts +152 -0
  129. package/src/server/plugins/engine/types/schema.ts +45 -0
  130. package/src/server/plugins/engine/types.ts +53 -3
  131. package/src/server/plugins/nunjucks/filters/answer.js +2 -2
  132. package/src/server/plugins/nunjucks/filters/answer.test.js +7 -4
  133. package/src/server/types.ts +2 -1
  134. package/.server/server/plugins/engine/components/helpers.js.map +0 -1
  135. package/.server/server/plugins/engine/pageControllers/helpers.js.map +0 -1
@@ -0,0 +1,506 @@
1
+ import { type FormMetadata } from '@defra/forms-model'
2
+
3
+ import { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'
4
+ import { type Field } from '~/src/server/plugins/engine/components/helpers/components.js'
5
+ import { FormModel } from '~/src/server/plugins/engine/models/index.js'
6
+ import {
7
+ type DetailItem,
8
+ type DetailItemField,
9
+ type DetailItemRepeat
10
+ } from '~/src/server/plugins/engine/models/types.js'
11
+ import { format } from '~/src/server/plugins/engine/outputFormatters/adapter/v1.js'
12
+ import { buildFormContextRequest } from '~/src/server/plugins/engine/pageControllers/__stubs__/request.js'
13
+ import {
14
+ FileStatus,
15
+ FormAdapterSubmissionSchemaVersion,
16
+ UploadStatus,
17
+ type FileState,
18
+ type FormAdapterSubmissionMessagePayload
19
+ } from '~/src/server/plugins/engine/types.js'
20
+ import { FormStatus } from '~/src/server/routes/types.js'
21
+ import definition from '~/test/form/definitions/repeat-mixed.js'
22
+
23
+ const submitResponse = {
24
+ message: 'Submit completed',
25
+ result: {
26
+ files: {
27
+ main: '00000000-0000-0000-0000-000000000000',
28
+ repeaters: {
29
+ pizza: '11111111-1111-1111-1111-111111111111'
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ const model = new FormModel(definition, {
36
+ basePath: 'test'
37
+ })
38
+
39
+ const dummyField: Field = {
40
+ getFormValueFromState: (_) => 'hello world'
41
+ } as Field
42
+
43
+ const itemId1 = 'abc-123'
44
+ const itemId2 = 'xyz-987'
45
+
46
+ const state = {
47
+ $$__referenceNumber: 'foobar',
48
+ orderType: 'delivery',
49
+ pizza: [
50
+ {
51
+ toppings: 'Ham',
52
+ quantity: 2,
53
+ itemId: itemId1
54
+ },
55
+ {
56
+ toppings: 'Pepperoni',
57
+ quantity: 1,
58
+ itemId: itemId2
59
+ }
60
+ ]
61
+ }
62
+
63
+ const pageUrl = new URL('http://example.com/repeat/pizza-order/summary')
64
+
65
+ const request = buildFormContextRequest({
66
+ method: 'get',
67
+ url: pageUrl,
68
+ path: pageUrl.pathname,
69
+ params: {
70
+ path: 'pizza-order',
71
+ slug: 'repeat'
72
+ },
73
+ query: {},
74
+ app: { model }
75
+ })
76
+
77
+ const context = model.getFormContext(request, state)
78
+
79
+ const testDetailItemField: DetailItemField = {
80
+ name: 'exampleField',
81
+ label: 'Example Field',
82
+ href: '/example-field',
83
+ title: 'Example Field Title',
84
+ field: dummyField,
85
+ value: 'Example Value'
86
+ } as DetailItemField
87
+
88
+ const testDetailItemField2: DetailItemField = {
89
+ name: 'exampleField2',
90
+ label: 'Example Field 2',
91
+ href: '/example-field-2',
92
+ title: 'Example Field 2 Title',
93
+ field: dummyField,
94
+ value: 'Example Value 2'
95
+ } as DetailItemField
96
+
97
+ const testDetailItemRepeat: DetailItemRepeat = {
98
+ name: 'exampleRepeat',
99
+ label: 'Example Repeat',
100
+ href: '/example-repeat',
101
+ title: 'Example Repeat Title',
102
+ value: 'Example Repeat Value',
103
+ subItems: [
104
+ [
105
+ {
106
+ name: 'subItem1_1',
107
+ label: 'Sub Item 1 1',
108
+ field: dummyField,
109
+ href: '/sub-item-1-1',
110
+ title: 'Sub Item 1 1 Title',
111
+ value: 'Sub Item 1 1 Value'
112
+ } as DetailItemField,
113
+ {
114
+ name: 'subItem1_2',
115
+ label: 'Sub Item 1 2',
116
+ field: dummyField,
117
+ href: '/sub-item-1-2',
118
+ title: 'Sub Item 1 2 Title',
119
+ value: 'Sub Item 1 2 Value'
120
+ } as DetailItemField
121
+ ],
122
+ [
123
+ {
124
+ name: 'subItem2_1',
125
+ label: 'Sub Item 2 1',
126
+ field: dummyField,
127
+ href: '/sub-item-2-1',
128
+ title: 'Sub Item 2 1 Title',
129
+ value: 'Sub Item 2 1 Value'
130
+ } as DetailItemField
131
+ ]
132
+ ]
133
+ } as DetailItemRepeat
134
+
135
+ const fileState: FileState = {
136
+ uploadId: '123',
137
+ status: {
138
+ form: {
139
+ file: {
140
+ fileId: '123-456-789',
141
+ contentLength: 1,
142
+ filename: 'foobar.txt',
143
+ fileStatus: FileStatus.complete
144
+ }
145
+ },
146
+ uploadStatus: UploadStatus.ready,
147
+ numberOfRejectedFiles: 0,
148
+ metadata: {
149
+ retrievalKey: '123'
150
+ }
151
+ }
152
+ }
153
+
154
+ const fileState2: FileState = {
155
+ uploadId: '456',
156
+ status: {
157
+ form: {
158
+ file: {
159
+ fileId: '456-789-123',
160
+ contentLength: 1,
161
+ filename: 'bazbuzz.txt',
162
+ fileStatus: FileStatus.complete
163
+ }
164
+ },
165
+ uploadStatus: UploadStatus.ready,
166
+ numberOfRejectedFiles: 0,
167
+ metadata: {
168
+ retrievalKey: '456'
169
+ }
170
+ }
171
+ }
172
+
173
+ const testDetailItemFile1: DetailItemField = Object.create(
174
+ FileUploadField.prototype
175
+ )
176
+ Object.assign(testDetailItemFile1, {
177
+ name: 'exampleFile1',
178
+ label: 'Example File Field',
179
+ href: '/example-file',
180
+ title: 'Example File Field Title',
181
+ field: testDetailItemFile1,
182
+ value: 'Example File Value',
183
+ state: {
184
+ exampleFile1: [fileState, fileState2]
185
+ }
186
+ })
187
+
188
+ const items: DetailItem[] = [
189
+ testDetailItemField,
190
+ testDetailItemField2,
191
+ testDetailItemRepeat,
192
+ testDetailItemFile1
193
+ ]
194
+
195
+ describe('Adapter v1 formatter', () => {
196
+ beforeEach(() => {
197
+ jest.clearAllMocks()
198
+ jest.useFakeTimers()
199
+ jest.setSystemTime(new Date('2024-01-15T10:30:00.000Z'))
200
+ })
201
+
202
+ afterEach(() => {
203
+ jest.useRealTimers()
204
+ })
205
+
206
+ it('should return the adapter v1 output with complete formMetadata', () => {
207
+ const formMetadata: FormMetadata = {
208
+ id: 'form-123',
209
+ slug: 'test-form',
210
+ title: 'Test Form',
211
+ notificationEmail: 'test@example.com'
212
+ } as FormMetadata
213
+
214
+ const formStatus = {
215
+ isPreview: false,
216
+ state: FormStatus.Live
217
+ }
218
+
219
+ const body = format(
220
+ context,
221
+ items,
222
+ model,
223
+ submitResponse,
224
+ formStatus,
225
+ formMetadata
226
+ )
227
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
228
+
229
+ expect(parsedBody.meta).toEqual({
230
+ schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
231
+ timestamp: '2024-01-15T10:30:00.000Z',
232
+ referenceNumber: 'foobar',
233
+ formName: definition.name,
234
+ formId: 'form-123',
235
+ formSlug: 'test-form',
236
+ status: FormStatus.Live,
237
+ isPreview: false,
238
+ notificationEmail: 'test@example.com'
239
+ })
240
+
241
+ expect(parsedBody.data).toEqual({
242
+ main: {
243
+ exampleField: 'hello world',
244
+ exampleField2: 'hello world'
245
+ },
246
+ repeaters: {
247
+ exampleRepeat: [
248
+ {
249
+ subItem1_1: 'hello world',
250
+ subItem1_2: 'hello world'
251
+ },
252
+ {
253
+ subItem2_1: 'hello world'
254
+ }
255
+ ]
256
+ },
257
+ files: {
258
+ exampleFile1: [
259
+ {
260
+ fileId: '123-456-789',
261
+ userDownloadLink: 'https://forms-designer/file-download/123-456-789'
262
+ },
263
+ {
264
+ fileId: '456-789-123',
265
+ userDownloadLink: 'https://forms-designer/file-download/456-789-123'
266
+ }
267
+ ]
268
+ }
269
+ })
270
+ })
271
+
272
+ it('should handle preview form status correctly', () => {
273
+ const formMetadata: FormMetadata = {
274
+ id: 'form-123',
275
+ slug: 'test-form',
276
+ title: 'Test Form',
277
+ notificationEmail: 'test@example.com'
278
+ } as FormMetadata
279
+
280
+ const formStatus = {
281
+ isPreview: true,
282
+ state: FormStatus.Draft
283
+ }
284
+
285
+ const body = format(
286
+ context,
287
+ items,
288
+ model,
289
+ submitResponse,
290
+ formStatus,
291
+ formMetadata
292
+ )
293
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
294
+
295
+ expect(parsedBody.meta.status).toBe(FormStatus.Draft)
296
+ expect(parsedBody.meta.isPreview).toBe(true)
297
+ })
298
+
299
+ it('should handle missing formMetadata with empty strings', () => {
300
+ const formStatus = {
301
+ isPreview: false,
302
+ state: FormStatus.Live
303
+ }
304
+
305
+ const body = format(context, items, model, submitResponse, formStatus)
306
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
307
+
308
+ expect(parsedBody.meta.formId).toBe('')
309
+ expect(parsedBody.meta.formSlug).toBe('')
310
+ expect(parsedBody.meta.notificationEmail).toBe('')
311
+ expect(parsedBody.meta.formName).toBe(definition.name)
312
+ expect(parsedBody.meta.referenceNumber).toBe('foobar')
313
+ })
314
+
315
+ it('should handle partial formMetadata', () => {
316
+ const formMetadata: FormMetadata = {
317
+ id: 'form-456',
318
+ slug: 'partial-form',
319
+ title: 'Partial Form'
320
+ } as FormMetadata
321
+
322
+ const formStatus = {
323
+ isPreview: true,
324
+ state: FormStatus.Draft
325
+ }
326
+
327
+ const body = format(
328
+ context,
329
+ items,
330
+ model,
331
+ submitResponse,
332
+ formStatus,
333
+ formMetadata
334
+ )
335
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
336
+
337
+ expect(parsedBody.meta.formId).toBe('form-456')
338
+ expect(parsedBody.meta.formSlug).toBe('partial-form')
339
+ expect(parsedBody.meta.notificationEmail).toBe('')
340
+ expect(parsedBody.meta.status).toBe(FormStatus.Draft)
341
+ expect(parsedBody.meta.isPreview).toBe(true)
342
+ })
343
+
344
+ it('should use correct schema version', () => {
345
+ const formStatus = {
346
+ isPreview: false,
347
+ state: FormStatus.Live
348
+ }
349
+
350
+ const body = format(context, items, model, submitResponse, formStatus)
351
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
352
+
353
+ expect(parsedBody.meta.schemaVersion).toBe(
354
+ FormAdapterSubmissionSchemaVersion.V1
355
+ )
356
+ expect(parsedBody.meta.schemaVersion).toBe(1)
357
+ })
358
+
359
+ it('should generate valid timestamp', () => {
360
+ const formStatus = {
361
+ isPreview: false,
362
+ state: FormStatus.Live
363
+ }
364
+
365
+ const body = format(context, items, model, submitResponse, formStatus)
366
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
367
+
368
+ expect(parsedBody.meta.timestamp).toBe('2024-01-15T10:30:00.000Z')
369
+ expect(typeof parsedBody.meta.timestamp).toBe('string')
370
+
371
+ expect(new Date(parsedBody.meta.timestamp)).toEqual(
372
+ new Date('2024-01-15T10:30:00.000Z')
373
+ )
374
+ })
375
+
376
+ it('should handle empty items array', () => {
377
+ const formStatus = {
378
+ isPreview: false,
379
+ state: FormStatus.Live
380
+ }
381
+
382
+ const body = format(context, [], model, submitResponse, formStatus)
383
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
384
+
385
+ expect(parsedBody.data.main).toEqual({})
386
+ expect(parsedBody.data.repeaters).toEqual({})
387
+ expect(parsedBody.data.files).toEqual({})
388
+ })
389
+
390
+ it('should handle different form statuses', () => {
391
+ const testCases = [
392
+ {
393
+ isPreview: false,
394
+ state: FormStatus.Live,
395
+ expectedStatus: FormStatus.Live
396
+ },
397
+ {
398
+ isPreview: true,
399
+ state: FormStatus.Draft,
400
+ expectedStatus: FormStatus.Draft
401
+ },
402
+ {
403
+ isPreview: true,
404
+ state: FormStatus.Live,
405
+ expectedStatus: FormStatus.Draft
406
+ }
407
+ ]
408
+
409
+ testCases.forEach(({ isPreview, state, expectedStatus }) => {
410
+ const formStatus = { isPreview, state }
411
+ const body = format(context, items, model, submitResponse, formStatus)
412
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
413
+
414
+ expect(parsedBody.meta.status).toBe(expectedStatus)
415
+ expect(parsedBody.meta.isPreview).toBe(isPreview)
416
+ })
417
+ })
418
+
419
+ it('should return valid JSON string', () => {
420
+ const formStatus = {
421
+ isPreview: false,
422
+ state: FormStatus.Live
423
+ }
424
+
425
+ const body = format(context, items, model, submitResponse, formStatus)
426
+
427
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
428
+ expect(() => JSON.parse(body)).not.toThrow()
429
+ expect(typeof body).toBe('string')
430
+ })
431
+
432
+ it('should handle formMetadata with only id', () => {
433
+ const formMetadata: FormMetadata = {
434
+ id: 'only-id-form'
435
+ } as FormMetadata
436
+
437
+ const formStatus = {
438
+ isPreview: false,
439
+ state: FormStatus.Live
440
+ }
441
+
442
+ const body = format(
443
+ context,
444
+ items,
445
+ model,
446
+ submitResponse,
447
+ formStatus,
448
+ formMetadata
449
+ )
450
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
451
+
452
+ expect(parsedBody.meta.formId).toBe('only-id-form')
453
+ expect(parsedBody.meta.formSlug).toBe('')
454
+ expect(parsedBody.meta.notificationEmail).toBe('')
455
+ })
456
+
457
+ it('should handle formMetadata with only slug', () => {
458
+ const formMetadata: FormMetadata = {
459
+ slug: 'only-slug-form'
460
+ } as FormMetadata
461
+
462
+ const formStatus = {
463
+ isPreview: false,
464
+ state: FormStatus.Live
465
+ }
466
+
467
+ const body = format(
468
+ context,
469
+ items,
470
+ model,
471
+ submitResponse,
472
+ formStatus,
473
+ formMetadata
474
+ )
475
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
476
+
477
+ expect(parsedBody.meta.formId).toBe('')
478
+ expect(parsedBody.meta.formSlug).toBe('only-slug-form')
479
+ expect(parsedBody.meta.notificationEmail).toBe('')
480
+ })
481
+
482
+ it('should handle formMetadata with only notificationEmail', () => {
483
+ const formMetadata: FormMetadata = {
484
+ notificationEmail: 'only-email@example.com'
485
+ } as FormMetadata
486
+
487
+ const formStatus = {
488
+ isPreview: false,
489
+ state: FormStatus.Live
490
+ }
491
+
492
+ const body = format(
493
+ context,
494
+ items,
495
+ model,
496
+ submitResponse,
497
+ formStatus,
498
+ formMetadata
499
+ )
500
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
501
+
502
+ expect(parsedBody.meta.formId).toBe('')
503
+ expect(parsedBody.meta.formSlug).toBe('')
504
+ expect(parsedBody.meta.notificationEmail).toBe('only-email@example.com')
505
+ })
506
+ })
@@ -0,0 +1,53 @@
1
+ import {
2
+ type FormMetadata,
3
+ type SubmitResponsePayload
4
+ } from '@defra/forms-model'
5
+
6
+ import { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
7
+ import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
8
+ import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
9
+ import { format as machineV2 } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'
10
+ import {
11
+ FormAdapterSubmissionSchemaVersion,
12
+ type FormAdapterSubmissionMessageData,
13
+ type FormAdapterSubmissionMessagePayload,
14
+ type FormContext
15
+ } from '~/src/server/plugins/engine/types.js'
16
+ import { FormStatus } from '~/src/server/routes/types.js'
17
+
18
+ export function format(
19
+ context: FormContext,
20
+ items: DetailItem[],
21
+ model: FormModel,
22
+ submitResponse: SubmitResponsePayload,
23
+ formStatus: ReturnType<typeof checkFormStatus>,
24
+ formMetadata?: FormMetadata
25
+ ): string {
26
+ const v2DataString = machineV2(
27
+ context,
28
+ items,
29
+ model,
30
+ submitResponse,
31
+ formStatus
32
+ )
33
+ const v2DataParsed = JSON.parse(v2DataString) as {
34
+ data: FormAdapterSubmissionMessageData
35
+ }
36
+
37
+ const payload: FormAdapterSubmissionMessagePayload = {
38
+ meta: {
39
+ schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
40
+ timestamp: new Date(),
41
+ referenceNumber: context.referenceNumber,
42
+ formName: model.name,
43
+ formId: formMetadata?.id ?? '',
44
+ formSlug: formMetadata?.slug ?? '',
45
+ status: formStatus.isPreview ? FormStatus.Draft : FormStatus.Live,
46
+ isPreview: formStatus.isPreview,
47
+ notificationEmail: formMetadata?.notificationEmail ?? ''
48
+ },
49
+ data: v2DataParsed.data
50
+ }
51
+
52
+ return JSON.stringify(payload)
53
+ }
@@ -1,11 +1,12 @@
1
- import { type SubmitResponsePayload } from '@defra/forms-model'
1
+ import {
2
+ type FormMetadata,
3
+ type SubmitResponsePayload
4
+ } from '@defra/forms-model'
2
5
  import { addDays, format as dateFormat } from 'date-fns'
3
6
 
4
7
  import { config } from '~/src/config/index.js'
5
- import {
6
- escapeMarkdown,
7
- getAnswer
8
- } from '~/src/server/plugins/engine/components/helpers.js'
8
+ import { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'
9
+ import { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'
9
10
  import { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
10
11
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
11
12
  import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
@@ -18,7 +19,8 @@ export function format(
18
19
  items: DetailItem[],
19
20
  model: FormModel,
20
21
  submitResponse: SubmitResponsePayload,
21
- formStatus: ReturnType<typeof checkFormStatus>
22
+ formStatus: ReturnType<typeof checkFormStatus>,
23
+ _formMetadata?: FormMetadata
22
24
  ) {
23
25
  const { files } = submitResponse.result
24
26
 
@@ -1,8 +1,12 @@
1
- import { type SubmitResponsePayload } from '@defra/forms-model'
1
+ import {
2
+ type FormMetadata,
3
+ type SubmitResponsePayload
4
+ } from '@defra/forms-model'
2
5
 
3
6
  import { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
4
7
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
5
8
  import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
9
+ import { format as formatAdapterV1 } from '~/src/server/plugins/engine/outputFormatters/adapter/v1.js'
6
10
  import { format as formatHumanV1 } from '~/src/server/plugins/engine/outputFormatters/human/v1.js'
7
11
  import { format as formatMachineV1 } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'
8
12
  import { format as formatMachineV2 } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'
@@ -13,12 +17,13 @@ type Formatter = (
13
17
  items: DetailItem[],
14
18
  model: FormModel,
15
19
  submitResponse: SubmitResponsePayload,
16
- formStatus: ReturnType<typeof checkFormStatus>
20
+ formStatus: ReturnType<typeof checkFormStatus>,
21
+ formMetadata?: FormMetadata
17
22
  ) => string
18
23
 
19
24
  const formatters: Record<
20
25
  string,
21
- Record<string, Formatter | undefined> | undefined
26
+ Record<string, Formatter | typeof formatAdapterV1 | undefined> | undefined
22
27
  > = {
23
28
  human: {
24
29
  '1': formatHumanV1
@@ -26,6 +31,9 @@ const formatters: Record<
26
31
  machine: {
27
32
  '1': formatMachineV1,
28
33
  '2': formatMachineV2
34
+ },
35
+ adapter: {
36
+ '1': formatAdapterV1
29
37
  }
30
38
  }
31
39
 
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
2
2
 
3
3
  import { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'
4
- import { type Field } from '~/src/server/plugins/engine/components/helpers.js'
4
+ import { type Field } from '~/src/server/plugins/engine/components/helpers/components.js'
5
5
  import { FormModel } from '~/src/server/plugins/engine/models/index.js'
6
6
  import {
7
7
  type DetailItem,
@@ -1,7 +1,10 @@
1
- import { type SubmitResponsePayload } from '@defra/forms-model'
1
+ import {
2
+ type FormMetadata,
3
+ type SubmitResponsePayload
4
+ } from '@defra/forms-model'
2
5
 
3
6
  import { config } from '~/src/config/index.js'
4
- import { getAnswer } from '~/src/server/plugins/engine/components/helpers.js'
7
+ import { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'
5
8
  import { FileUploadField } from '~/src/server/plugins/engine/components/index.js'
6
9
  import { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
7
10
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
@@ -19,7 +22,8 @@ export function format(
19
22
  items: DetailItem[],
20
23
  model: FormModel,
21
24
  _submitResponse: SubmitResponsePayload,
22
- _formStatus: ReturnType<typeof checkFormStatus>
25
+ _formStatus: ReturnType<typeof checkFormStatus>,
26
+ _formMetadata?: FormMetadata
23
27
  ) {
24
28
  const now = new Date()
25
29
 
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
2
2
 
3
3
  import { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'
4
- import { type Field } from '~/src/server/plugins/engine/components/helpers.js'
4
+ import { type Field } from '~/src/server/plugins/engine/components/helpers/components.js'
5
5
  import { FormModel } from '~/src/server/plugins/engine/models/index.js'
6
6
  import {
7
7
  type DetailItem,
@@ -150,7 +150,7 @@ type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {
150
150
  field: FileUploadField
151
151
  }
152
152
 
153
- type RichFormValue =
153
+ export type RichFormValue =
154
154
  | FormValue
155
155
  | FormPayload
156
156
  | DatePartsState
@@ -15,7 +15,7 @@ import {
15
15
  } from '~/src/server/plugins/engine/pageControllers/FileUploadPageController.js'
16
16
  import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
17
17
  import { serverWithSaveAndReturn } from '~/src/server/plugins/engine/pageControllers/__stubs__/server.js'
18
- import * as pageHelpers from '~/src/server/plugins/engine/pageControllers/helpers.js'
18
+ import * as pageHelpers from '~/src/server/plugins/engine/pageControllers/helpers/index.js'
19
19
  import * as uploadService from '~/src/server/plugins/engine/services/uploadService.js'
20
20
  import {
21
21
  FileStatus,
@@ -15,7 +15,7 @@ import {
15
15
  } from '~/src/server/plugins/engine/helpers.js'
16
16
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
17
17
  import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
18
- import { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers.js'
18
+ import { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'
19
19
  import {
20
20
  getUploadStatus,
21
21
  initiateUpload