@defra/forms-engine-plugin 4.0.24 → 4.0.26

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 (93) hide show
  1. package/.server/server/forms/components.json +2 -2
  2. package/.server/server/plugins/engine/components/DeclarationField.d.ts +2 -0
  3. package/.server/server/plugins/engine/components/DeclarationField.js +8 -1
  4. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
  5. package/.server/server/plugins/engine/components/HiddenField.d.ts +21 -0
  6. package/.server/server/plugins/engine/components/HiddenField.js +50 -0
  7. package/.server/server/plugins/engine/components/HiddenField.js.map +1 -0
  8. package/.server/server/plugins/engine/components/Markdown.d.ts +2 -0
  9. package/.server/server/plugins/engine/components/Markdown.js +4 -1
  10. package/.server/server/plugins/engine/components/Markdown.js.map +1 -1
  11. package/.server/server/plugins/engine/components/helpers/components.d.ts +1 -1
  12. package/.server/server/plugins/engine/components/helpers/components.js +3 -0
  13. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  14. package/.server/server/plugins/engine/components/index.d.ts +1 -0
  15. package/.server/server/plugins/engine/components/index.js +1 -0
  16. package/.server/server/plugins/engine/components/index.js.map +1 -1
  17. package/.server/server/plugins/engine/helpers.js +2 -1
  18. package/.server/server/plugins/engine/helpers.js.map +1 -1
  19. package/.server/server/plugins/engine/index.js +4 -1
  20. package/.server/server/plugins/engine/index.js.map +1 -1
  21. package/.server/server/plugins/engine/models/FormModel.d.ts +2 -0
  22. package/.server/server/plugins/engine/models/FormModel.js +2 -0
  23. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  24. package/.server/server/plugins/engine/pageControllers/PageController.d.ts +1 -1
  25. package/.server/server/plugins/engine/pageControllers/PageController.js +2 -8
  26. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
  27. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +7 -0
  28. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  29. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +8 -2
  30. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
  31. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +2 -1
  32. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  33. package/.server/server/plugins/engine/pageControllers/helpers/state.d.ts +11 -0
  34. package/.server/server/plugins/engine/pageControllers/helpers/state.js +66 -0
  35. package/.server/server/plugins/engine/pageControllers/helpers/state.js.map +1 -0
  36. package/.server/server/plugins/engine/routes/index.js +2 -1
  37. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  38. package/.server/server/plugins/engine/services/formsService.d.ts +6 -0
  39. package/.server/server/plugins/engine/services/formsService.js +10 -0
  40. package/.server/server/plugins/engine/services/formsService.js.map +1 -1
  41. package/.server/server/plugins/engine/services/formsService.test.js +14 -0
  42. package/.server/server/plugins/engine/services/formsService.test.js.map +1 -0
  43. package/.server/server/plugins/engine/types.d.ts +4 -0
  44. package/.server/server/plugins/engine/types.js.map +1 -1
  45. package/.server/server/plugins/engine/views/components/declarationfield.html +1 -1
  46. package/.server/server/plugins/engine/views/components/hiddenfield.html +3 -0
  47. package/.server/server/plugins/engine/views/components/markdown.html +1 -1
  48. package/.server/server/plugins/engine/views/confirmation.html +6 -1
  49. package/.server/server/plugins/engine/views/partials/form.html +9 -1
  50. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  51. package/.server/server/services/cacheService.d.ts +3 -7
  52. package/.server/server/services/cacheService.js.map +1 -1
  53. package/.server/server/types.d.ts +1 -0
  54. package/.server/server/types.js.map +1 -1
  55. package/.server/server/utils/file-form-service.d.ts +6 -0
  56. package/.server/server/utils/file-form-service.js +22 -1
  57. package/.server/server/utils/file-form-service.js.map +1 -1
  58. package/.server/server/utils/file-form-service.test.js +86 -0
  59. package/.server/server/utils/file-form-service.test.js.map +1 -0
  60. package/package.json +3 -2
  61. package/src/server/forms/components.json +2 -2
  62. package/src/server/plugins/engine/components/DeclarationField.test.ts +24 -0
  63. package/src/server/plugins/engine/components/DeclarationField.ts +20 -2
  64. package/src/server/plugins/engine/components/HiddenField.test.ts +188 -0
  65. package/src/server/plugins/engine/components/HiddenField.ts +68 -0
  66. package/src/server/plugins/engine/components/Markdown.ts +4 -1
  67. package/src/server/plugins/engine/components/helpers/components.ts +5 -0
  68. package/src/server/plugins/engine/components/helpers/helpers.test.ts +17 -0
  69. package/src/server/plugins/engine/components/index.ts +1 -0
  70. package/src/server/plugins/engine/helpers.ts +2 -1
  71. package/src/server/plugins/engine/index.ts +5 -2
  72. package/src/server/plugins/engine/models/FormModel.ts +3 -0
  73. package/src/server/plugins/engine/pageControllers/PageController.test.ts +4 -11
  74. package/src/server/plugins/engine/pageControllers/PageController.ts +1 -9
  75. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +7 -0
  76. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +9 -2
  77. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +5 -1
  78. package/src/server/plugins/engine/pageControllers/helpers/state.test.ts +221 -0
  79. package/src/server/plugins/engine/pageControllers/helpers/state.ts +93 -0
  80. package/src/server/plugins/engine/routes/index.test.ts +24 -11
  81. package/src/server/plugins/engine/routes/index.ts +1 -1
  82. package/src/server/plugins/engine/services/formsService.js +10 -0
  83. package/src/server/plugins/engine/services/formsService.test.js +21 -0
  84. package/src/server/plugins/engine/types.ts +5 -0
  85. package/src/server/plugins/engine/views/components/declarationfield.html +1 -1
  86. package/src/server/plugins/engine/views/components/hiddenfield.html +3 -0
  87. package/src/server/plugins/engine/views/components/markdown.html +1 -1
  88. package/src/server/plugins/engine/views/confirmation.html +6 -1
  89. package/src/server/plugins/engine/views/partials/form.html +9 -1
  90. package/src/server/services/cacheService.ts +3 -2
  91. package/src/server/types.ts +1 -0
  92. package/src/server/utils/file-form-service.js +27 -1
  93. package/src/server/utils/file-form-service.test.js +114 -0
@@ -22,6 +22,7 @@ import {
22
22
  } from '~/src/server/plugins/engine/components/helpers/components.js'
23
23
  import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
24
24
  import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
25
+ import { stripParam } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'
25
26
  import {
26
27
  type AnyFormRequest,
27
28
  type FormContext,
@@ -133,7 +134,7 @@ export function proceed(
133
134
  const response =
134
135
  isReturnAllowed && isPathRelative(returnUrl)
135
136
  ? h.redirect(returnUrl)
136
- : h.redirect(redirectPath(nextUrl))
137
+ : h.redirect(redirectPath(nextUrl, stripParam(query, 'returnUrl')))
137
138
 
138
139
  // Redirect POST to GET to avoid resubmission
139
140
  return method === 'post'
@@ -33,8 +33,11 @@ export const prepareNunjucksEnvironment = function (
33
33
  env.addFilter(name, nunjucksFilter)
34
34
  }
35
35
 
36
- env.addFilter('markdown', (text: string) =>
37
- markdownToHtml(text, pluginOptions.baseUrl)
36
+ env.addFilter('markdown', (text: string, startingHeaderLevel?: number) =>
37
+ markdownToHtml(text, {
38
+ baseUrl: pluginOptions.baseUrl,
39
+ startingHeaderLevel
40
+ })
38
41
  )
39
42
 
40
43
  for (const [name, nunjucksGlobal] of Object.entries(globals)) {
@@ -74,6 +74,7 @@ export class FormModel {
74
74
  lists: FormDefinition['lists']
75
75
  sections: FormDefinition['sections'] = []
76
76
  name: string
77
+ formId: string
77
78
  values: FormDefinition
78
79
  basePath: string
79
80
  versionNumber?: number
@@ -100,6 +101,7 @@ export class FormModel {
100
101
  basePath: string
101
102
  versionNumber?: number
102
103
  ordnanceSurveyApiKey?: string
104
+ formId?: string
103
105
  },
104
106
  services: Services = defaultServices,
105
107
  controllers?: Record<string, typeof PageController>
@@ -152,6 +154,7 @@ export class FormModel {
152
154
  this.lists = def.lists
153
155
  this.sections = def.sections
154
156
  this.name = def.name ?? ''
157
+ this.formId = options.formId ?? ''
155
158
  this.values = result.value
156
159
  this.basePath = options.basePath
157
160
  this.versionNumber = options.versionNumber
@@ -24,7 +24,8 @@ describe('PageController', () => {
24
24
  const page2 = pages[1]
25
25
 
26
26
  model = new FormModel(definition, {
27
- basePath: testBasePath
27
+ basePath: testBasePath,
28
+ formId: 'form-id'
28
29
  })
29
30
 
30
31
  controller1 = new PageController(model, page1)
@@ -61,18 +62,10 @@ describe('PageController', () => {
61
62
  })
62
63
  })
63
64
 
64
- it('returns feedback link (from form definition)', () => {
65
- expect(controller1).toHaveProperty('feedbackLink', undefined)
66
-
67
- const emailAddress = 'test@feedback.cat'
68
-
69
- model.def.feedback = {
70
- emailAddress
71
- }
72
-
65
+ it('returns feedback link default', () => {
73
66
  expect(controller1).toHaveProperty(
74
67
  'feedbackLink',
75
- `mailto:${emailAddress}`
68
+ '/form/feedback?formId=form-id'
76
69
  )
77
70
  })
78
71
 
@@ -10,7 +10,6 @@ import { type Lifecycle, type RouteOptions, type Server } from '@hapi/hapi'
10
10
 
11
11
  import { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
12
12
  import {
13
- encodeUrl,
14
13
  getSaveAndExitHelpers,
15
14
  getStartPath,
16
15
  normalisePath
@@ -119,14 +118,7 @@ export class PageController {
119
118
  }
120
119
 
121
120
  get feedbackLink() {
122
- const { def } = this
123
-
124
- // setting the feedbackLink to undefined here for feedback forms prevents the feedback link from being shown
125
- const feedbackLink = def.feedback?.emailAddress
126
- ? `mailto:${def.feedback.emailAddress}`
127
- : def.feedback?.url
128
-
129
- return encodeUrl(feedbackLink)
121
+ return `/form/feedback?formId=${this.model.formId}`
130
122
  }
131
123
 
132
124
  get phaseTag() {
@@ -28,6 +28,7 @@ import {
28
28
  } from '~/src/server/plugins/engine/helpers.js'
29
29
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
30
30
  import { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
31
+ import { prefillStateFromQueryParameters } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'
31
32
  import {
32
33
  type AnyFormRequest,
33
34
  type FormContext,
@@ -403,6 +404,12 @@ export class QuestionPageController extends PageController {
403
404
  const { collection, model, viewName } = this
404
405
  const { evaluationState } = context
405
406
 
407
+ // Copy any URL params into the form state (if not already done so)
408
+ if (await prefillStateFromQueryParameters(request, this)) {
409
+ // Forward to same page without query string
410
+ return h.redirect(`${request.url.origin}${request.url.pathname}`)
411
+ }
412
+
406
413
  const viewModel = this.getViewModel(request, context)
407
414
  viewModel.errors = collection.getViewErrors(viewModel.errors)
408
415
 
@@ -41,13 +41,20 @@ export class StatusPageController extends QuestionPageController {
41
41
 
42
42
  const slug = request.params.slug
43
43
  const { formsService } = this.model.services
44
- const { getFormMetadata } = formsService
44
+ const { getFormMetadata, getFormMetadataById } = formsService
45
45
 
46
46
  const { submissionGuidance } = await getFormMetadata(slug)
47
47
 
48
+ // Re-read form name if overriding display (for example, in a feedback form)
49
+ const storedFormId = confirmationState.formId
50
+ const formName = storedFormId
51
+ ? (await getFormMetadataById(storedFormId)).title
52
+ : undefined
53
+
48
54
  return h.view(viewName, {
49
55
  ...viewModel,
50
- submissionGuidance
56
+ submissionGuidance,
57
+ formName
51
58
  })
52
59
  }
53
60
  }
@@ -25,6 +25,7 @@ import {
25
25
  } from '~/src/server/plugins/engine/models/types.js'
26
26
  import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
27
27
  import {
28
+ type FormConfirmationState,
28
29
  type FormContext,
29
30
  type FormContextRequest,
30
31
  type FormSubmissionState
@@ -152,7 +153,10 @@ export class SummaryPageController extends QuestionPageController {
152
153
  )
153
154
  }
154
155
 
155
- await cacheService.setConfirmationState(request, { confirmed: true })
156
+ await cacheService.setConfirmationState(request, {
157
+ confirmed: true,
158
+ formId: context.state.formId
159
+ } as FormConfirmationState)
156
160
 
157
161
  // Clear all form data
158
162
  await cacheService.clearState(request)
@@ -0,0 +1,221 @@
1
+ import { ComponentType, type Page } from '@defra/forms-model'
2
+
3
+ import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
4
+ import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
5
+ import {
6
+ prefillStateFromQueryParameters,
7
+ stripParam
8
+ } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'
9
+ import { type AnyFormRequest } from '~/src/server/plugins/engine/types.js'
10
+ import { type FormsService, type Services } from '~/src/server/types.js'
11
+
12
+ function buildMockPage(
13
+ pagesOverride = {},
14
+ stateOverride = {},
15
+ servicesOverride = {} as Services
16
+ ) {
17
+ return {
18
+ model: {
19
+ def: {
20
+ metadata: {
21
+ submission: { code: 'TEST-CODE' }
22
+ } as { submission: { code: string } },
23
+ pages: pagesOverride
24
+ },
25
+ getFormContext: jest.fn().mockReturnValue({
26
+ isForceAccess: false,
27
+ data: {}
28
+ }),
29
+ services: servicesOverride
30
+ } as unknown as FormModel,
31
+ ...stateOverride
32
+ } as unknown as PageControllerClass
33
+ }
34
+
35
+ describe('State helpers', () => {
36
+ describe('prefillStateFromQueryParameters', () => {
37
+ const mockGetState = jest.fn()
38
+ const mockMergeState = jest.fn()
39
+ const mockRequestPrefill: AnyFormRequest = {
40
+ app: {},
41
+ yar: { flash: () => [] },
42
+ params: { path: 'test-path' },
43
+ query: {}
44
+ } as unknown as AnyFormRequest
45
+
46
+ it('should not add any state if no params', async () => {
47
+ const mockPagePrefill = buildMockPage([], {
48
+ getState: mockGetState,
49
+ mergeState: mockMergeState
50
+ })
51
+
52
+ expect(
53
+ await prefillStateFromQueryParameters(
54
+ mockRequestPrefill,
55
+ mockPagePrefill
56
+ )
57
+ ).toBeFalse()
58
+ expect(mockMergeState).not.toHaveBeenCalled()
59
+ })
60
+
61
+ it('should ignore if no params (but some hidden fields)', async () => {
62
+ const mockRequest2 = {
63
+ ...mockRequestPrefill,
64
+ query: {}
65
+ } as unknown as AnyFormRequest
66
+
67
+ const mockPagePrefill = buildMockPage(
68
+ [
69
+ {
70
+ components: [
71
+ {
72
+ type: ComponentType.HiddenField,
73
+ name: 'param2'
74
+ },
75
+ {
76
+ type: ComponentType.HiddenField,
77
+ name: 'param4'
78
+ }
79
+ ],
80
+ next: []
81
+ } as unknown as Page
82
+ ],
83
+ {
84
+ getState: mockGetState,
85
+ mergeState: mockMergeState
86
+ }
87
+ )
88
+
89
+ expect(
90
+ await prefillStateFromQueryParameters(mockRequest2, mockPagePrefill)
91
+ ).toBeFalse()
92
+ expect(mockMergeState).not.toHaveBeenCalled()
93
+ })
94
+
95
+ it('should only add state where param names match hidden field names', async () => {
96
+ const mockRequest2 = {
97
+ ...mockRequestPrefill,
98
+ query: {
99
+ param1: 'val1',
100
+ param2: 'val2',
101
+ param3: 'val3',
102
+ param4: 'val4'
103
+ }
104
+ } as unknown as AnyFormRequest
105
+
106
+ const mockPagePrefill = buildMockPage(
107
+ [
108
+ {
109
+ components: [
110
+ {
111
+ type: ComponentType.HiddenField,
112
+ name: 'param2'
113
+ },
114
+ {
115
+ type: ComponentType.HiddenField,
116
+ name: 'param4'
117
+ }
118
+ ],
119
+ next: []
120
+ } as unknown as Page
121
+ ],
122
+ {
123
+ getState: mockGetState.mockResolvedValue({}),
124
+ mergeState: mockMergeState
125
+ }
126
+ )
127
+
128
+ expect(
129
+ await prefillStateFromQueryParameters(mockRequest2, mockPagePrefill)
130
+ ).toBe(true)
131
+ expect(mockMergeState).toHaveBeenCalledWith(
132
+ expect.anything(),
133
+ expect.anything(),
134
+ {
135
+ param2: 'val2',
136
+ param4: 'val4'
137
+ }
138
+ )
139
+ })
140
+
141
+ it('should call lookup function for formId', async () => {
142
+ const mockRequest3 = {
143
+ ...mockRequestPrefill,
144
+ query: {
145
+ formId: 'c644804b-2f23-4c96-a2fc-ad4975974723'
146
+ }
147
+ } as unknown as AnyFormRequest
148
+
149
+ const mockPagePrefill = buildMockPage(
150
+ [
151
+ {
152
+ components: [
153
+ {
154
+ type: ComponentType.HiddenField,
155
+ name: 'formId'
156
+ }
157
+ ],
158
+ next: []
159
+ } as unknown as Page
160
+ ],
161
+ {
162
+ getState: mockGetState.mockResolvedValue({}),
163
+ mergeState: mockMergeState
164
+ },
165
+ {
166
+ formsService: {
167
+ getFormMetadata: jest.fn(),
168
+ getFormMetadataById: jest
169
+ .fn()
170
+ .mockResolvedValue({ title: 'My looked-up form name' }),
171
+ getFormDefinition: jest.fn()
172
+ } as unknown as FormsService
173
+ } as Services
174
+ )
175
+
176
+ await prefillStateFromQueryParameters(mockRequest3, mockPagePrefill)
177
+ expect(mockMergeState).toHaveBeenCalledWith(
178
+ expect.anything(),
179
+ expect.anything(),
180
+ {
181
+ formId: 'c644804b-2f23-4c96-a2fc-ad4975974723',
182
+ formName: 'My looked-up form name'
183
+ }
184
+ )
185
+ })
186
+ })
187
+
188
+ describe('stripParam', () => {
189
+ it('should remove param when exists', () => {
190
+ const params = {
191
+ paramName1: 'val1',
192
+ returnUrl: 'http://somesite.com',
193
+ paramName2: 'val2',
194
+ paramName3: undefined
195
+ }
196
+ expect(stripParam(params, 'returnUrl')).toStrictEqual({
197
+ paramName1: 'val1',
198
+ paramName2: 'val2',
199
+ paramName3: ''
200
+ })
201
+ })
202
+
203
+ it('should handle param missing', () => {
204
+ const params = {
205
+ paramName1: 'val1',
206
+ returnUrl: 'http://somesite.com',
207
+ paramName2: 'val2'
208
+ }
209
+ expect(stripParam(params, 'paramNotThere')).toStrictEqual({
210
+ paramName1: 'val1',
211
+ returnUrl: 'http://somesite.com',
212
+ paramName2: 'val2'
213
+ })
214
+ })
215
+
216
+ it('should handle no params', () => {
217
+ const params = {}
218
+ expect(stripParam(params, 'anyParam')).toBeUndefined()
219
+ })
220
+ })
221
+ })
@@ -0,0 +1,93 @@
1
+ import { getHiddenFields } from '@defra/forms-model'
2
+
3
+ import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
4
+ import {
5
+ type AnyFormRequest,
6
+ type FormStateValue
7
+ } from '~/src/server/plugins/engine/types.js'
8
+ import { type FormQuery } from '~/src/server/routes/types.js'
9
+ import { type Services } from '~/src/server/types.js'
10
+
11
+ /**
12
+ * A series of functions that can transform a pre-fill input parameter e.g lookup a form title based on form id
13
+ */
14
+ const paramLookupFunctions = {
15
+ formId: async (val: string, services: Services) => {
16
+ let formTitle
17
+ if (val) {
18
+ const meta = await services.formsService.getFormMetadataById(val)
19
+ formTitle = meta.title
20
+ }
21
+ return {
22
+ key: 'formName',
23
+ value: formTitle
24
+ }
25
+ }
26
+ } as Partial<
27
+ Record<
28
+ string,
29
+ (
30
+ val: string,
31
+ services: Services
32
+ ) => Promise<{ key: string; value: string | undefined }>
33
+ >
34
+ >
35
+
36
+ export function stripParam(query: FormQuery, paramToRemove: string) {
37
+ const params = {} as Record<string, FormStateValue | undefined>
38
+ for (const [key, value = ''] of Object.entries(query)) {
39
+ if (key !== paramToRemove) {
40
+ params[key] = value
41
+ }
42
+ }
43
+ return Object.keys(params).length ? (params as FormQuery) : undefined
44
+ }
45
+
46
+ /**
47
+ * Any hidden parameters defined in the FormDefinition may be pre-filled by URL parameter values.
48
+ * Other parameters are ignored for security reasons.
49
+ * @param request
50
+ * @param model
51
+ */
52
+ export async function prefillStateFromQueryParameters(
53
+ request: AnyFormRequest,
54
+ page: PageControllerClass
55
+ ): Promise<boolean> {
56
+ const { model } = page
57
+
58
+ const hiddenFieldNames = new Set(
59
+ getHiddenFields(model.def).map((field) => field.name)
60
+ )
61
+
62
+ if (!hiddenFieldNames.size) {
63
+ return false
64
+ }
65
+
66
+ // Remove 'returnUrl' param
67
+ const query = stripParam(request.query, 'returnUrl')
68
+
69
+ if (!query) {
70
+ return false
71
+ }
72
+
73
+ const params = {} as Record<string, FormStateValue | undefined>
74
+
75
+ for (const [key, value = ''] of Object.entries(query)) {
76
+ if (hiddenFieldNames.has(key)) {
77
+ const lookupFunc = paramLookupFunctions[key]
78
+ if (lookupFunc) {
79
+ const res = await lookupFunc(value, model.services)
80
+ // Store original value and result
81
+ params[key] = value
82
+ params[res.key] = res.value
83
+ } else {
84
+ params[key] = value
85
+ }
86
+ }
87
+ }
88
+
89
+ const formData = await page.getState(request)
90
+ await page.mergeState(request, formData, params)
91
+
92
+ return true
93
+ }
@@ -1,3 +1,4 @@
1
+ import { type Page } from '@defra/forms-model'
1
2
  import Boom from '@hapi/boom'
2
3
  import { type ResponseObject, type ResponseToolkit } from '@hapi/hapi'
3
4
 
@@ -15,9 +16,31 @@ import {
15
16
  type OnRequestCallback
16
17
  } from '~/src/server/plugins/engine/types.js'
17
18
  import { type FormResponseToolkit } from '~/src/server/routes/types.js'
19
+ import { type Services } from '~/src/server/types.js'
18
20
 
19
21
  jest.mock('~/src/server/plugins/engine/helpers')
20
22
 
23
+ function buildMockModel(
24
+ pagesOverride = [] as Page[],
25
+ pagesControllerOverride = [] as PageControllerClass[],
26
+ servicesOverride = {} as Services
27
+ ) {
28
+ return {
29
+ def: {
30
+ metadata: {
31
+ submission: { code: 'TEST-CODE' }
32
+ } as { submission: { code: string } },
33
+ pages: pagesOverride
34
+ },
35
+ getFormContext: jest.fn().mockReturnValue({
36
+ isForceAccess: false,
37
+ data: {}
38
+ }),
39
+ pages: pagesControllerOverride,
40
+ services: servicesOverride
41
+ } as unknown as FormModel
42
+ }
43
+
21
44
  describe('redirectOrMakeHandler', () => {
22
45
  const mockServer = {} as unknown as Parameters<
23
46
  typeof redirectOrMakeHandler
@@ -38,17 +61,7 @@ describe('redirectOrMakeHandler', () => {
38
61
 
39
62
  let mockPage: PageControllerClass
40
63
 
41
- const mockModel: FormModel = {
42
- def: {
43
- metadata: {
44
- submission: { code: 'TEST-CODE' }
45
- } as { submission: { code: string } }
46
- },
47
- getFormContext: jest.fn().mockReturnValue({
48
- isForceAccess: false,
49
- data: {}
50
- })
51
- } as unknown as FormModel
64
+ const mockModel = buildMockModel()
52
65
 
53
66
  const mockMakeHandler = jest
54
67
  .fn()
@@ -244,7 +244,7 @@ export function makeLoadFormPreHandler(server: Server, options: PluginOptions) {
244
244
  // Construct the form model
245
245
  const model = new FormModel(
246
246
  definition,
247
- { basePath, versionNumber, ordnanceSurveyApiKey },
247
+ { basePath, versionNumber, ordnanceSurveyApiKey, formId: id },
248
248
  services,
249
249
  controllers
250
250
  )
@@ -12,6 +12,16 @@ export function getFormMetadata(_slug) {
12
12
  throw error
13
13
  }
14
14
 
15
+ // eslint-disable-next-line jsdoc/require-returns-check
16
+ /**
17
+ * Dummy function to get form metadata.
18
+ * @param {string} _id - the id of the form
19
+ * @returns {Promise<FormMetadata>}
20
+ */
21
+ export function getFormMetadataById(_id) {
22
+ throw error
23
+ }
24
+
15
25
  // eslint-disable-next-line jsdoc/require-returns-check
16
26
  /**
17
27
  * Dummy function to get form metadata.
@@ -0,0 +1,21 @@
1
+ import { FormStatus } from '@defra/forms-model'
2
+
3
+ import {
4
+ getFormDefinition,
5
+ getFormMetadata,
6
+ getFormMetadataById
7
+ } from '~/src/server/plugins/engine/services/formsService.js'
8
+
9
+ describe('formsService', () => {
10
+ it('getFormMetadata should throw error', () => {
11
+ expect(() => getFormMetadata('slug')).toThrow()
12
+ })
13
+
14
+ it('getFormMetadataById should throw error', () => {
15
+ expect(() => getFormMetadataById('id')).toThrow()
16
+ })
17
+
18
+ it('getFormDefinition should throw error', () => {
19
+ expect(() => getFormDefinition('id', FormStatus.Draft)).toThrow()
20
+ })
21
+ })
@@ -97,6 +97,11 @@ export interface FormSubmissionError
97
97
  text: string // e.g: 'Date field must be a real date'
98
98
  }
99
99
 
100
+ export interface FormConfirmationState {
101
+ confirmed?: true
102
+ formId?: string
103
+ }
104
+
100
105
  export interface FormPayloadParams {
101
106
  action?: FormAction
102
107
  confirm?: true
@@ -5,7 +5,7 @@
5
5
  {% macro DeclarationField(component) %}
6
6
  {% set content %}
7
7
  <div class="app-prose-scope">
8
- {{ component.model.content | markdown | safe }}
8
+ {{ component.model.content | markdown(component.model.headerStartLevel) | safe }}
9
9
  </div>
10
10
  {% endset %}
11
11
  {% set checkboxes = component.model | merge({ formGroup: { beforeInputs: { html: content } } }) %}
@@ -0,0 +1,3 @@
1
+ {% macro HiddenField(component) %}
2
+ <input type="hidden" id="{{ component.model.name }}" name="{{ component.model.name }}" value="{{ component.model.value }}" />
3
+ {% endmacro %}
@@ -1,5 +1,5 @@
1
1
  {% macro Markdown(component) %}
2
2
  <div class="app-prose-scope">
3
- {{ component.model.content | markdown | safe }}
3
+ {{ component.model.content | markdown(component.model.headerStartLevel) | safe }}
4
4
  </div>
5
5
  {% endmacro %}
@@ -12,8 +12,13 @@
12
12
  }) }}
13
13
  <h2 class="govuk-heading-m">What happens next</h2>
14
14
  <div class="app-prose-scope">
15
- {{ submissionGuidance | markdown | safe }}
15
+ {{ submissionGuidance | markdown(3) | safe }}
16
16
  </div>
17
+ {% if feedbackLink %}
18
+ <p class="govuk-body">
19
+ <a href="{{ feedbackLink }}" class="govuk-link govuk-link--no-visited-state">What do you think of this service?</a> (takes 30 seconds)
20
+ </p>
21
+ {% endif %}
17
22
  </div>
18
23
  </div>
19
24
  {% endblock %}
@@ -6,9 +6,17 @@
6
6
 
7
7
  {{ componentList(components) }}
8
8
 
9
+ {% if isStartPage %}
10
+ {% set buttonText = "Start now" %}
11
+ {% elif submitButtonText %}
12
+ {% set buttonText = submitButtonText %}
13
+ {% else %}
14
+ {% set buttonText = "Continue" %}
15
+ {% endif %}
16
+
9
17
  <div class="govuk-button-group">
10
18
  {{ govukButton({
11
- text: "Start now" if isStartPage else "Continue",
19
+ text: buttonText,
12
20
  isStartButton: isStartPage,
13
21
  preventDoubleClick: true
14
22
  }) }}