@defra/forms-engine-plugin 4.0.25 → 4.0.27

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 (76) hide show
  1. package/.server/server/plugins/engine/components/HiddenField.d.ts +21 -0
  2. package/.server/server/plugins/engine/components/HiddenField.js +50 -0
  3. package/.server/server/plugins/engine/components/HiddenField.js.map +1 -0
  4. package/.server/server/plugins/engine/components/helpers/components.d.ts +1 -1
  5. package/.server/server/plugins/engine/components/helpers/components.js +3 -0
  6. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  7. package/.server/server/plugins/engine/components/index.d.ts +1 -0
  8. package/.server/server/plugins/engine/components/index.js +1 -0
  9. package/.server/server/plugins/engine/components/index.js.map +1 -1
  10. package/.server/server/plugins/engine/helpers.js +2 -5
  11. package/.server/server/plugins/engine/helpers.js.map +1 -1
  12. package/.server/server/plugins/engine/models/FormModel.d.ts +2 -0
  13. package/.server/server/plugins/engine/models/FormModel.js +2 -0
  14. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  15. package/.server/server/plugins/engine/pageControllers/PageController.d.ts +1 -1
  16. package/.server/server/plugins/engine/pageControllers/PageController.js +2 -8
  17. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
  18. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +7 -0
  19. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  20. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +8 -2
  21. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
  22. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +2 -1
  23. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  24. package/.server/server/plugins/engine/pageControllers/helpers/state.d.ts +11 -0
  25. package/.server/server/plugins/engine/pageControllers/helpers/state.js +66 -0
  26. package/.server/server/plugins/engine/pageControllers/helpers/state.js.map +1 -0
  27. package/.server/server/plugins/engine/routes/index.js +2 -1
  28. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  29. package/.server/server/plugins/engine/services/formsService.d.ts +6 -0
  30. package/.server/server/plugins/engine/services/formsService.js +10 -0
  31. package/.server/server/plugins/engine/services/formsService.js.map +1 -1
  32. package/.server/server/plugins/engine/services/formsService.test.js +14 -0
  33. package/.server/server/plugins/engine/services/formsService.test.js.map +1 -0
  34. package/.server/server/plugins/engine/types.d.ts +4 -0
  35. package/.server/server/plugins/engine/types.js.map +1 -1
  36. package/.server/server/plugins/engine/views/components/hiddenfield.html +3 -0
  37. package/.server/server/plugins/engine/views/confirmation.html +5 -0
  38. package/.server/server/plugins/engine/views/partials/form.html +9 -1
  39. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  40. package/.server/server/services/cacheService.d.ts +3 -7
  41. package/.server/server/services/cacheService.js.map +1 -1
  42. package/.server/server/types.d.ts +1 -0
  43. package/.server/server/types.js.map +1 -1
  44. package/.server/server/utils/file-form-service.d.ts +6 -0
  45. package/.server/server/utils/file-form-service.js +22 -1
  46. package/.server/server/utils/file-form-service.js.map +1 -1
  47. package/.server/server/utils/file-form-service.test.js +86 -0
  48. package/.server/server/utils/file-form-service.test.js.map +1 -0
  49. package/package.json +2 -2
  50. package/src/server/plugins/engine/components/HiddenField.test.ts +188 -0
  51. package/src/server/plugins/engine/components/HiddenField.ts +68 -0
  52. package/src/server/plugins/engine/components/helpers/components.ts +5 -0
  53. package/src/server/plugins/engine/components/helpers/helpers.test.ts +17 -0
  54. package/src/server/plugins/engine/components/index.ts +1 -0
  55. package/src/server/plugins/engine/helpers.test.ts +56 -0
  56. package/src/server/plugins/engine/helpers.ts +2 -9
  57. package/src/server/plugins/engine/models/FormModel.ts +3 -0
  58. package/src/server/plugins/engine/pageControllers/PageController.test.ts +4 -11
  59. package/src/server/plugins/engine/pageControllers/PageController.ts +1 -9
  60. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +7 -0
  61. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +9 -2
  62. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +5 -1
  63. package/src/server/plugins/engine/pageControllers/helpers/state.test.ts +221 -0
  64. package/src/server/plugins/engine/pageControllers/helpers/state.ts +93 -0
  65. package/src/server/plugins/engine/routes/index.test.ts +24 -11
  66. package/src/server/plugins/engine/routes/index.ts +1 -1
  67. package/src/server/plugins/engine/services/formsService.js +10 -0
  68. package/src/server/plugins/engine/services/formsService.test.js +21 -0
  69. package/src/server/plugins/engine/types.ts +5 -0
  70. package/src/server/plugins/engine/views/components/hiddenfield.html +3 -0
  71. package/src/server/plugins/engine/views/confirmation.html +5 -0
  72. package/src/server/plugins/engine/views/partials/form.html +9 -1
  73. package/src/server/services/cacheService.ts +3 -2
  74. package/src/server/types.ts +1 -0
  75. package/src/server/utils/file-form-service.js +27 -1
  76. package/src/server/utils/file-form-service.test.js +114 -0
@@ -0,0 +1,188 @@
1
+ import { ComponentType, type HiddenFieldComponent } from '@defra/forms-model'
2
+
3
+ import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
4
+ import {
5
+ getAnswer,
6
+ type Field
7
+ } from '~/src/server/plugins/engine/components/helpers/components.js'
8
+ import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
9
+ import definition from '~/test/form/definitions/blank.js'
10
+ import { getFormData, getFormState } from '~/test/helpers/component-helpers.js'
11
+
12
+ describe('HiddenField', () => {
13
+ let model: FormModel
14
+
15
+ beforeEach(() => {
16
+ model = new FormModel(definition, {
17
+ basePath: 'test'
18
+ })
19
+ })
20
+
21
+ describe('Defaults', () => {
22
+ let def: HiddenFieldComponent
23
+ let collection: ComponentCollection
24
+ let field: Field
25
+
26
+ beforeEach(() => {
27
+ def = {
28
+ title: 'Hidden field',
29
+ name: 'myComponent',
30
+ type: ComponentType.HiddenField,
31
+ options: {}
32
+ } satisfies HiddenFieldComponent
33
+
34
+ collection = new ComponentCollection([def], { model })
35
+ field = collection.fields[0]
36
+ })
37
+
38
+ describe('Schema', () => {
39
+ it('uses component title as label as default', () => {
40
+ const { formSchema } = collection
41
+ const { keys } = formSchema.describe()
42
+
43
+ expect(keys).toHaveProperty(
44
+ 'myComponent',
45
+ expect.objectContaining({
46
+ flags: expect.objectContaining({
47
+ label: 'Hidden field'
48
+ })
49
+ })
50
+ )
51
+ })
52
+
53
+ it('uses component name as keys', () => {
54
+ const { formSchema } = collection
55
+ const { keys } = formSchema.describe()
56
+
57
+ expect(field.keys).toEqual(['myComponent'])
58
+ expect(field.collection).toBeUndefined()
59
+
60
+ for (const key of field.keys) {
61
+ expect(keys).toHaveProperty(key)
62
+ }
63
+ })
64
+
65
+ it('is required by default', () => {
66
+ const { formSchema } = collection
67
+ const { keys } = formSchema.describe()
68
+
69
+ expect(keys).toHaveProperty(
70
+ 'myComponent',
71
+ expect.objectContaining({
72
+ flags: expect.objectContaining({
73
+ presence: 'required'
74
+ })
75
+ })
76
+ )
77
+ })
78
+ it('accepts valid values', () => {
79
+ const result1 = collection.validate(getFormData('Hidden value'))
80
+ const result2 = collection.validate(getFormData('Hidden value 2'))
81
+
82
+ expect(result1.errors).toBeUndefined()
83
+ expect(result2.errors).toBeUndefined()
84
+ })
85
+
86
+ it('adds errors for empty value', () => {
87
+ const result = collection.validate(getFormData(''))
88
+
89
+ expect(result.errors).toEqual([
90
+ expect.objectContaining({
91
+ text: 'Enter hidden field'
92
+ })
93
+ ])
94
+ })
95
+
96
+ it('adds errors for invalid values', () => {
97
+ const result1 = collection.validate(getFormData(['invalid']))
98
+ const result2 = collection.validate(
99
+ // @ts-expect-error - Allow invalid param for test
100
+ getFormData({ unknown: 'invalid' })
101
+ )
102
+
103
+ expect(result1.errors).toBeTruthy()
104
+ expect(result2.errors).toBeTruthy()
105
+ })
106
+ })
107
+
108
+ describe('State', () => {
109
+ it('returns text from state', () => {
110
+ const state1 = getFormState('Hidden field')
111
+ const state2 = getFormState(null)
112
+
113
+ const answer1 = getAnswer(field, state1)
114
+ const answer2 = getAnswer(field, state2)
115
+
116
+ expect(answer1).toBe('Hidden field')
117
+ expect(answer2).toBe('')
118
+ })
119
+
120
+ it('returns payload from state', () => {
121
+ const state1 = getFormState('Hidden field')
122
+ const state2 = getFormState(null)
123
+
124
+ const payload1 = field.getFormDataFromState(state1)
125
+ const payload2 = field.getFormDataFromState(state2)
126
+
127
+ expect(payload1).toEqual(getFormData('Hidden field'))
128
+ expect(payload2).toEqual(getFormData())
129
+ })
130
+
131
+ it('returns value from state', () => {
132
+ const state1 = getFormState('Hidden field')
133
+ const state2 = getFormState(null)
134
+
135
+ const value1 = field.getFormValueFromState(state1)
136
+ const value2 = field.getFormValueFromState(state2)
137
+
138
+ expect(value1).toBe('Hidden field')
139
+ expect(value2).toBeUndefined()
140
+ })
141
+
142
+ it('returns context for conditions and form submission', () => {
143
+ const state1 = getFormState('Hidden field')
144
+ const state2 = getFormState(null)
145
+
146
+ const value1 = field.getContextValueFromState(state1)
147
+ const value2 = field.getContextValueFromState(state2)
148
+
149
+ expect(value1).toBe('Hidden field')
150
+ expect(value2).toBeNull()
151
+ })
152
+
153
+ it('returns state from payload', () => {
154
+ const payload1 = getFormData('Hidden field')
155
+ const payload2 = getFormData()
156
+
157
+ const value1 = field.getStateFromValidForm(payload1)
158
+ const value2 = field.getStateFromValidForm(payload2)
159
+
160
+ expect(value1).toEqual(getFormState('Hidden field'))
161
+ expect(value2).toEqual(getFormState(null))
162
+ })
163
+ })
164
+
165
+ describe('View model', () => {
166
+ it('sets Nunjucks component defaults', () => {
167
+ const viewModel = field.getViewModel(getFormData('Hidden field'))
168
+
169
+ expect(viewModel).toEqual(
170
+ expect.objectContaining({
171
+ label: { text: def.title },
172
+ name: 'myComponent',
173
+ id: 'myComponent',
174
+ value: 'Hidden field'
175
+ })
176
+ )
177
+ })
178
+ })
179
+
180
+ describe('AllPossibleErrors', () => {
181
+ it('should return errors', () => {
182
+ const errors = field.getAllPossibleErrors()
183
+ expect(errors.baseErrors).not.toBeEmpty()
184
+ expect(errors.advancedSettingsErrors).toBeEmpty()
185
+ })
186
+ })
187
+ })
188
+ })
@@ -0,0 +1,68 @@
1
+ import {
2
+ type HiddenFieldComponent,
3
+ type TextFieldComponent
4
+ } from '@defra/forms-model'
5
+ import joi, { type StringSchema } from 'joi'
6
+
7
+ import { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'
8
+ import { TextField } from '~/src/server/plugins/engine/components/TextField.js'
9
+ import { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
10
+ import {
11
+ type ErrorMessageTemplateList,
12
+ type FormState,
13
+ type FormStateValue,
14
+ type FormSubmissionState
15
+ } from '~/src/server/plugins/engine/types.js'
16
+
17
+ export class HiddenField extends FormComponent {
18
+ declare formSchema: StringSchema
19
+ declare stateSchema: StringSchema
20
+ declare schema: TextFieldComponent['schema']
21
+ declare options: TextFieldComponent['options']
22
+
23
+ constructor(
24
+ def: HiddenFieldComponent,
25
+ props: ConstructorParameters<typeof FormComponent>[1]
26
+ ) {
27
+ super(def, props)
28
+
29
+ const { options } = def
30
+
31
+ let formSchema = joi.string().trim().label(this.label).required()
32
+
33
+ if (options.required === false) {
34
+ formSchema = formSchema.allow('')
35
+ }
36
+
37
+ this.formSchema = formSchema.default('')
38
+ this.stateSchema = formSchema.default(null).allow(null)
39
+ this.schema = {}
40
+ this.options = {}
41
+ }
42
+
43
+ getFormValueFromState(state: FormSubmissionState) {
44
+ const { name } = this
45
+ return this.getFormValue(state[name])
46
+ }
47
+
48
+ isValue(value?: FormStateValue | FormState): value is string {
49
+ return TextField.isText(value)
50
+ }
51
+
52
+ /**
53
+ * For error preview page that shows all possible errors on a component
54
+ */
55
+ getAllPossibleErrors(): ErrorMessageTemplateList {
56
+ return HiddenField.getAllPossibleErrors()
57
+ }
58
+
59
+ /**
60
+ * Static version of getAllPossibleErrors that doesn't require a component instance.
61
+ */
62
+ static getAllPossibleErrors(): ErrorMessageTemplateList {
63
+ return {
64
+ baseErrors: [{ type: 'required', template: messageTemplate.required }],
65
+ advancedSettingsErrors: []
66
+ }
67
+ }
68
+ }
@@ -34,6 +34,7 @@ export type Field = InstanceType<
34
34
  | typeof Components.TextField
35
35
  | typeof Components.UkAddressField
36
36
  | typeof Components.FileUploadField
37
+ | typeof Components.HiddenField
37
38
  >
38
39
 
39
40
  // Guidance component instances only
@@ -186,6 +187,10 @@ export function createComponent(
186
187
  case ComponentType.LatLongField:
187
188
  component = new Components.LatLongField(def, options)
188
189
  break
190
+
191
+ case ComponentType.HiddenField:
192
+ component = new Components.HiddenField(def, options)
193
+ break
189
194
  }
190
195
 
191
196
  if (typeof component === 'undefined') {
@@ -2,6 +2,7 @@ import { ComponentType, type ComponentDef } from '@defra/forms-model'
2
2
 
3
3
  import { ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'
4
4
  import { EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'
5
+ import { HiddenField } from '~/src/server/plugins/engine/components/HiddenField.js'
5
6
  import { LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js'
6
7
  import { NationalGridFieldNumberField } from '~/src/server/plugins/engine/components/NationalGridFieldNumberField.js'
7
8
  import { OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'
@@ -96,6 +97,22 @@ describe('helpers tests', () => {
96
97
  expect(component.name).toBe('testField')
97
98
  expect(component.title).toBe('Test National Grid')
98
99
  })
100
+
101
+ test('should create HiddenField component', () => {
102
+ const component = createComponent(
103
+ {
104
+ type: ComponentType.HiddenField,
105
+ name: 'hiddenField',
106
+ title: 'Hidden field',
107
+ options: {}
108
+ },
109
+ { model: formModel }
110
+ )
111
+
112
+ expect(component).toBeInstanceOf(HiddenField)
113
+ expect(component.name).toBe('hiddenField')
114
+ expect(component.title).toBe('Hidden field')
115
+ })
99
116
  })
100
117
 
101
118
  describe('ComponentBase tests', () => {
@@ -28,3 +28,4 @@ export { EastingNorthingField } from '~/src/server/plugins/engine/components/Eas
28
28
  export { OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'
29
29
  export { NationalGridFieldNumberField } from '~/src/server/plugins/engine/components/NationalGridFieldNumberField.js'
30
30
  export { LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js'
31
+ export { HiddenField } from '~/src/server/plugins/engine/components/HiddenField.js'
@@ -1,3 +1,8 @@
1
+ import {
2
+ ComponentType,
3
+ type FormDefinition,
4
+ type PageQuestion
5
+ } from '@defra/forms-model'
1
6
  import Boom from '@hapi/boom'
2
7
  import { type ResponseObject, type ResponseToolkit } from '@hapi/hapi'
3
8
  import { StatusCodes } from 'http-status-codes'
@@ -14,6 +19,7 @@ import {
14
19
  getPageHref,
15
20
  proceed,
16
21
  safeGenerateCrumb,
22
+ setPageTitles,
17
23
  type GlobalScope
18
24
  } from '~/src/server/plugins/engine/helpers.js'
19
25
  import { handleLegacyRedirect } from '~/src/server/plugins/engine/helpers.js'
@@ -51,6 +57,7 @@ describe('Helpers', () => {
51
57
  let h: FormResponseToolkit
52
58
 
53
59
  beforeEach(() => {
60
+ jest.clearAllMocks()
54
61
  const model = new FormModel(definition, {
55
62
  basePath: 'test'
56
63
  })
@@ -843,4 +850,53 @@ describe('Helpers', () => {
843
850
  expect(response).toBe(mockRedirectResponse)
844
851
  })
845
852
  })
853
+
854
+ describe('setPageTitles', () => {
855
+ const definition: FormDefinition = {
856
+ name: 'Test Form',
857
+ startPage: '/page1',
858
+ pages: [
859
+ {
860
+ path: '/page1',
861
+ title: '',
862
+ next: [],
863
+ components: [
864
+ {
865
+ type: ComponentType.TextField,
866
+ name: 'textfield1',
867
+ title: 'What is your name?',
868
+ options: {},
869
+ schema: {}
870
+ },
871
+ {
872
+ type: ComponentType.TextField,
873
+ name: 'textfield2',
874
+ title: 'What is your favourite food?',
875
+ options: {},
876
+ schema: {}
877
+ }
878
+ ]
879
+ } satisfies PageQuestion
880
+ ],
881
+ lists: [],
882
+ sections: [],
883
+ conditions: []
884
+ }
885
+
886
+ beforeEach(() => {
887
+ jest.clearAllMocks()
888
+ })
889
+ it('should set title if missing', () => {
890
+ const def = structuredClone(definition)
891
+ setPageTitles(def)
892
+ expect(def.pages[0].title).toBe('What is your name?')
893
+ })
894
+
895
+ it('should keep title if supplied', () => {
896
+ const def = structuredClone(definition)
897
+ def.pages[0].title = 'Page 1 title'
898
+ setPageTitles(def)
899
+ expect(def.pages[0].title).toBe('Page 1 title')
900
+ })
901
+ })
846
902
  })
@@ -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'
@@ -412,14 +413,6 @@ export function setPageTitles(def: FormDefinition) {
412
413
 
413
414
  page.title = firstFormComponent?.title ?? ''
414
415
  }
415
-
416
- if (!page.title) {
417
- const formNameMsg = def.name ? ` in form '${def.name}'` : ''
418
-
419
- logger.info(
420
- `[pageTitleMissing] Page '${page.path}' has no title${formNameMsg}`
421
- )
422
- }
423
416
  }
424
417
  })
425
418
  }
@@ -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)