@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.
- package/.server/server/forms/components.json +2 -2
- package/.server/server/plugins/engine/components/DeclarationField.d.ts +2 -0
- package/.server/server/plugins/engine/components/DeclarationField.js +8 -1
- package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
- package/.server/server/plugins/engine/components/HiddenField.d.ts +21 -0
- package/.server/server/plugins/engine/components/HiddenField.js +50 -0
- package/.server/server/plugins/engine/components/HiddenField.js.map +1 -0
- package/.server/server/plugins/engine/components/Markdown.d.ts +2 -0
- package/.server/server/plugins/engine/components/Markdown.js +4 -1
- package/.server/server/plugins/engine/components/Markdown.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/components.d.ts +1 -1
- package/.server/server/plugins/engine/components/helpers/components.js +3 -0
- package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
- package/.server/server/plugins/engine/components/index.d.ts +1 -0
- package/.server/server/plugins/engine/components/index.js +1 -0
- package/.server/server/plugins/engine/components/index.js.map +1 -1
- package/.server/server/plugins/engine/helpers.js +2 -1
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/index.js +4 -1
- package/.server/server/plugins/engine/index.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.d.ts +2 -0
- package/.server/server/plugins/engine/models/FormModel.js +2 -0
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/PageController.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/PageController.js +2 -8
- package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +7 -0
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js +8 -2
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +2 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/helpers/state.d.ts +11 -0
- package/.server/server/plugins/engine/pageControllers/helpers/state.js +66 -0
- package/.server/server/plugins/engine/pageControllers/helpers/state.js.map +1 -0
- package/.server/server/plugins/engine/routes/index.js +2 -1
- package/.server/server/plugins/engine/routes/index.js.map +1 -1
- package/.server/server/plugins/engine/services/formsService.d.ts +6 -0
- package/.server/server/plugins/engine/services/formsService.js +10 -0
- package/.server/server/plugins/engine/services/formsService.js.map +1 -1
- package/.server/server/plugins/engine/services/formsService.test.js +14 -0
- package/.server/server/plugins/engine/services/formsService.test.js.map +1 -0
- package/.server/server/plugins/engine/types.d.ts +4 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/components/declarationfield.html +1 -1
- package/.server/server/plugins/engine/views/components/hiddenfield.html +3 -0
- package/.server/server/plugins/engine/views/components/markdown.html +1 -1
- package/.server/server/plugins/engine/views/confirmation.html +6 -1
- package/.server/server/plugins/engine/views/partials/form.html +9 -1
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/.server/server/services/cacheService.d.ts +3 -7
- package/.server/server/services/cacheService.js.map +1 -1
- package/.server/server/types.d.ts +1 -0
- package/.server/server/types.js.map +1 -1
- package/.server/server/utils/file-form-service.d.ts +6 -0
- package/.server/server/utils/file-form-service.js +22 -1
- package/.server/server/utils/file-form-service.js.map +1 -1
- package/.server/server/utils/file-form-service.test.js +86 -0
- package/.server/server/utils/file-form-service.test.js.map +1 -0
- package/package.json +3 -2
- package/src/server/forms/components.json +2 -2
- package/src/server/plugins/engine/components/DeclarationField.test.ts +24 -0
- package/src/server/plugins/engine/components/DeclarationField.ts +20 -2
- package/src/server/plugins/engine/components/HiddenField.test.ts +188 -0
- package/src/server/plugins/engine/components/HiddenField.ts +68 -0
- package/src/server/plugins/engine/components/Markdown.ts +4 -1
- package/src/server/plugins/engine/components/helpers/components.ts +5 -0
- package/src/server/plugins/engine/components/helpers/helpers.test.ts +17 -0
- package/src/server/plugins/engine/components/index.ts +1 -0
- package/src/server/plugins/engine/helpers.ts +2 -1
- package/src/server/plugins/engine/index.ts +5 -2
- package/src/server/plugins/engine/models/FormModel.ts +3 -0
- package/src/server/plugins/engine/pageControllers/PageController.test.ts +4 -11
- package/src/server/plugins/engine/pageControllers/PageController.ts +1 -9
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +7 -0
- package/src/server/plugins/engine/pageControllers/StatusPageController.ts +9 -2
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +5 -1
- package/src/server/plugins/engine/pageControllers/helpers/state.test.ts +221 -0
- package/src/server/plugins/engine/pageControllers/helpers/state.ts +93 -0
- package/src/server/plugins/engine/routes/index.test.ts +24 -11
- package/src/server/plugins/engine/routes/index.ts +1 -1
- package/src/server/plugins/engine/services/formsService.js +10 -0
- package/src/server/plugins/engine/services/formsService.test.js +21 -0
- package/src/server/plugins/engine/types.ts +5 -0
- package/src/server/plugins/engine/views/components/declarationfield.html +1 -1
- package/src/server/plugins/engine/views/components/hiddenfield.html +3 -0
- package/src/server/plugins/engine/views/components/markdown.html +1 -1
- package/src/server/plugins/engine/views/confirmation.html +6 -1
- package/src/server/plugins/engine/views/partials/form.html +9 -1
- package/src/server/services/cacheService.ts +3 -2
- package/src/server/types.ts +1 -0
- package/src/server/utils/file-form-service.js +27 -1
- 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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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
|
|
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
|
|
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 } } }) %}
|
|
@@ -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:
|
|
19
|
+
text: buttonText,
|
|
12
20
|
isStartButton: isStartPage,
|
|
13
21
|
preventDoubleClick: true
|
|
14
22
|
}) }}
|