@defra/forms-engine-plugin 2.1.10 → 3.0.1
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/index.js +2 -1
- package/.server/server/index.js.map +1 -1
- package/.server/server/plugins/engine/README.md +2 -2
- package/.server/server/plugins/engine/configureEnginePlugin.d.ts +2 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js +4 -4
- package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +7 -11
- package/.server/server/plugins/engine/helpers.js +2 -2
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.d.ts +2 -0
- package/.server/server/plugins/engine/models/FormModel.js +5 -2
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/options.js +3 -6
- package/.server/server/plugins/engine/options.js.map +1 -1
- package/.server/server/plugins/engine/options.test.js +2 -8
- package/.server/server/plugins/engine/options.test.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.d.ts +4 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +25 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js +7 -6
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +5 -6
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/PageController.d.ts +6 -6
- package/.server/server/plugins/engine/pageControllers/PageController.js +4 -4
- package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +13 -13
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +12 -20
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +7 -8
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +2 -2
- package/.server/server/plugins/engine/pageControllers/StartPageController.js +1 -1
- package/.server/server/plugins/engine/pageControllers/StartPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StatusPageController.d.ts +3 -4
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js +1 -1
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +5 -4
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +12 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/TerminalPageController.d.ts +4 -4
- package/.server/server/plugins/engine/pageControllers/TerminalPageController.js +1 -1
- package/.server/server/plugins/engine/pageControllers/TerminalPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/__stubs__/server.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/__stubs__/server.js +2 -6
- package/.server/server/plugins/engine/pageControllers/__stubs__/server.js.map +1 -1
- package/.server/server/plugins/engine/plugin.js +7 -12
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/routes/index.d.ts +5 -5
- package/.server/server/plugins/engine/routes/index.js +3 -1
- package/.server/server/plugins/engine/routes/index.js.map +1 -1
- package/.server/server/plugins/engine/routes/questions.d.ts +4 -4
- package/.server/server/plugins/engine/routes/questions.js.map +1 -1
- package/.server/server/plugins/engine/routes/repeaters/item-delete.js.map +1 -1
- package/.server/server/plugins/engine/routes/repeaters/summary.js.map +1 -1
- package/.server/server/plugins/engine/types/index.d.ts +2 -2
- package/.server/server/plugins/engine/types/index.js.map +1 -1
- package/.server/server/plugins/engine/types/schema.js +3 -2
- package/.server/server/plugins/engine/types/schema.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +13 -12
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/partials/form.html +3 -3
- package/.server/server/plugins/engine/views/summary.html +21 -5
- package/.server/server/plugins/nunjucks/context.d.ts +5 -6
- package/.server/server/plugins/nunjucks/context.js +3 -3
- package/.server/server/plugins/nunjucks/context.js.map +1 -1
- package/.server/server/routes/types.d.ts +3 -2
- package/.server/server/routes/types.js +1 -1
- package/.server/server/routes/types.js.map +1 -1
- package/.server/server/schemas/index.js +1 -1
- package/.server/server/schemas/index.js.map +1 -1
- package/.server/server/services/cacheService.d.ts +11 -19
- package/.server/server/services/cacheService.js +9 -30
- package/.server/server/services/cacheService.js.map +1 -1
- package/.server/server/types.d.ts +4 -1
- package/.server/server/types.js.map +1 -1
- package/.server/typings/hapi/index.d.js.map +1 -1
- package/package.json +4 -2
- package/src/server/index.test.ts +0 -39
- package/src/server/index.ts +4 -1
- package/src/server/plugins/engine/README.md +2 -2
- package/src/server/plugins/engine/components/helpers/helpers.test.ts +1 -1
- package/src/server/plugins/engine/configureEnginePlugin.ts +15 -11
- package/src/server/plugins/engine/helpers.test.ts +3 -2
- package/src/server/plugins/engine/helpers.ts +6 -6
- package/src/server/plugins/engine/models/FormModel.test.ts +66 -2
- package/src/server/plugins/engine/models/FormModel.ts +6 -4
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +7 -7
- package/src/server/plugins/engine/models/SummaryViewModel.ts +1 -1
- package/src/server/plugins/engine/options.js +6 -6
- package/src/server/plugins/engine/options.test.js +2 -6
- package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +446 -13
- package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +37 -0
- package/src/server/plugins/engine/outputFormatters/machine/v2.ts +8 -6
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +8 -10
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +9 -8
- package/src/server/plugins/engine/pageControllers/PageController.test.ts +11 -8
- package/src/server/plugins/engine/pageControllers/PageController.ts +9 -15
- package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +35 -102
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +24 -36
- package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +4 -6
- package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +8 -11
- package/src/server/plugins/engine/pageControllers/StartPageController.test.ts +4 -4
- package/src/server/plugins/engine/pageControllers/StartPageController.ts +1 -1
- package/src/server/plugins/engine/pageControllers/StatusPageController.test.ts +4 -4
- package/src/server/plugins/engine/pageControllers/StatusPageController.ts +6 -4
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -5
- package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +4 -4
- package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +7 -4
- package/src/server/plugins/engine/pageControllers/__stubs__/server.ts +5 -6
- package/src/server/plugins/engine/plugin.ts +7 -13
- package/src/server/plugins/engine/routes/index.ts +9 -12
- package/src/server/plugins/engine/routes/questions.test.ts +29 -53
- package/src/server/plugins/engine/routes/questions.ts +6 -8
- package/src/server/plugins/engine/routes/repeaters/item-delete.ts +5 -14
- package/src/server/plugins/engine/routes/repeaters/summary.ts +5 -14
- package/src/server/plugins/engine/types/index.ts +4 -1
- package/src/server/plugins/engine/types/schema.test.ts +40 -0
- package/src/server/plugins/engine/types/schema.ts +3 -1
- package/src/server/plugins/engine/types.ts +22 -13
- package/src/server/plugins/engine/views/partials/form.html +3 -3
- package/src/server/plugins/engine/views/summary.html +21 -5
- package/src/server/plugins/nunjucks/context.js +3 -3
- package/src/server/routes/types.ts +7 -2
- package/src/server/schemas/index.ts +1 -1
- package/src/server/services/cacheService.test.ts +1 -117
- package/src/server/services/cacheService.ts +22 -73
- package/src/server/types.ts +4 -1
- package/src/typings/hapi/index.d.ts +6 -7
- package/.server/server/plugins/engine/routes/exit.d.ts +0 -46
- package/.server/server/plugins/engine/routes/exit.js +0 -36
- package/.server/server/plugins/engine/routes/exit.js.map +0 -1
- package/src/server/plugins/engine/routes/exit.ts +0 -47
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { slugSchema } from '@defra/forms-model'
|
|
2
2
|
import Boom from '@hapi/boom'
|
|
3
|
-
import {
|
|
4
|
-
type ResponseToolkit,
|
|
5
|
-
type RouteOptions,
|
|
6
|
-
type ServerRoute
|
|
7
|
-
} from '@hapi/hapi'
|
|
3
|
+
import { type RouteOptions, type ServerRoute } from '@hapi/hapi'
|
|
8
4
|
import Joi from 'joi'
|
|
9
5
|
|
|
10
6
|
import { FileUploadPageController } from '~/src/server/plugins/engine/pageControllers/FileUploadPageController.js'
|
|
@@ -14,7 +10,8 @@ import {
|
|
|
14
10
|
type FormRequest,
|
|
15
11
|
type FormRequestPayload,
|
|
16
12
|
type FormRequestPayloadRefs,
|
|
17
|
-
type FormRequestRefs
|
|
13
|
+
type FormRequestRefs,
|
|
14
|
+
type FormResponseToolkit
|
|
18
15
|
} from '~/src/server/routes/types.js'
|
|
19
16
|
import {
|
|
20
17
|
actionSchema,
|
|
@@ -26,10 +23,7 @@ import {
|
|
|
26
23
|
} from '~/src/server/schemas/index.js'
|
|
27
24
|
|
|
28
25
|
// Item delete GET route
|
|
29
|
-
function getHandler(
|
|
30
|
-
request: FormRequest,
|
|
31
|
-
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
32
|
-
) {
|
|
26
|
+
function getHandler(request: FormRequest, h: FormResponseToolkit) {
|
|
33
27
|
const { params } = request
|
|
34
28
|
|
|
35
29
|
return redirectOrMakeHandler(request, h, (page, context) => {
|
|
@@ -46,10 +40,7 @@ function getHandler(
|
|
|
46
40
|
})
|
|
47
41
|
}
|
|
48
42
|
|
|
49
|
-
function postHandler(
|
|
50
|
-
request: FormRequestPayload,
|
|
51
|
-
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
52
|
-
) {
|
|
43
|
+
function postHandler(request: FormRequestPayload, h: FormResponseToolkit) {
|
|
53
44
|
const { params } = request
|
|
54
45
|
|
|
55
46
|
return redirectOrMakeHandler(request, h, (page, context) => {
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
// List summary GET route
|
|
2
2
|
import { slugSchema } from '@defra/forms-model'
|
|
3
3
|
import Boom from '@hapi/boom'
|
|
4
|
-
import {
|
|
5
|
-
type ResponseToolkit,
|
|
6
|
-
type RouteOptions,
|
|
7
|
-
type ServerRoute
|
|
8
|
-
} from '@hapi/hapi'
|
|
4
|
+
import { type RouteOptions, type ServerRoute } from '@hapi/hapi'
|
|
9
5
|
import Joi from 'joi'
|
|
10
6
|
|
|
11
7
|
import { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'
|
|
@@ -14,7 +10,8 @@ import {
|
|
|
14
10
|
type FormRequest,
|
|
15
11
|
type FormRequestPayload,
|
|
16
12
|
type FormRequestPayloadRefs,
|
|
17
|
-
type FormRequestRefs
|
|
13
|
+
type FormRequestRefs,
|
|
14
|
+
type FormResponseToolkit
|
|
18
15
|
} from '~/src/server/routes/types.js'
|
|
19
16
|
import {
|
|
20
17
|
actionSchema,
|
|
@@ -23,10 +20,7 @@ import {
|
|
|
23
20
|
stateSchema
|
|
24
21
|
} from '~/src/server/schemas/index.js'
|
|
25
22
|
|
|
26
|
-
function getHandler(
|
|
27
|
-
request: FormRequest,
|
|
28
|
-
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
29
|
-
) {
|
|
23
|
+
function getHandler(request: FormRequest, h: FormResponseToolkit) {
|
|
30
24
|
const { params } = request
|
|
31
25
|
|
|
32
26
|
return redirectOrMakeHandler(request, h, (page, context) => {
|
|
@@ -38,10 +32,7 @@ function getHandler(
|
|
|
38
32
|
})
|
|
39
33
|
}
|
|
40
34
|
|
|
41
|
-
function postHandler(
|
|
42
|
-
request: FormRequestPayload,
|
|
43
|
-
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
44
|
-
) {
|
|
35
|
+
function postHandler(request: FormRequestPayload, h: FormResponseToolkit) {
|
|
45
36
|
const { params } = request
|
|
46
37
|
|
|
47
38
|
return redirectOrMakeHandler(request, h, (page, context) => {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type {
|
|
2
|
+
AnyFormRequest,
|
|
3
|
+
AnyRequest,
|
|
2
4
|
CheckAnswers,
|
|
3
5
|
ErrorMessageTemplate,
|
|
4
6
|
ErrorMessageTemplateList,
|
|
@@ -74,7 +76,8 @@ export type {
|
|
|
74
76
|
FormRequest,
|
|
75
77
|
FormRequestPayload,
|
|
76
78
|
FormRequestPayloadRefs,
|
|
77
|
-
FormRequestRefs
|
|
79
|
+
FormRequestRefs,
|
|
80
|
+
FormResponseToolkit
|
|
78
81
|
} from '~/src/server/routes/types.js'
|
|
79
82
|
|
|
80
83
|
export { FormAction, FormStatus } from '~/src/server/routes/types.js'
|
|
@@ -156,5 +156,45 @@ describe('Schema validation', () => {
|
|
|
156
156
|
formAdapterSubmissionMessagePayloadSchema.validate(payloadWithoutData)
|
|
157
157
|
expect(error).toBeDefined()
|
|
158
158
|
})
|
|
159
|
+
|
|
160
|
+
it('should validate payload with versionMetadata', () => {
|
|
161
|
+
const payloadWithVersion = {
|
|
162
|
+
...validPayload,
|
|
163
|
+
meta: {
|
|
164
|
+
...validPayload.meta,
|
|
165
|
+
versionMetadata: {
|
|
166
|
+
versionNumber: 19,
|
|
167
|
+
createdAt: new Date('2025-09-08T09:28:15.576Z')
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const { error } =
|
|
172
|
+
formAdapterSubmissionMessagePayloadSchema.validate(payloadWithVersion)
|
|
173
|
+
expect(error).toBeUndefined()
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('should validate payload without versionMetadata', () => {
|
|
177
|
+
const { error } =
|
|
178
|
+
formAdapterSubmissionMessagePayloadSchema.validate(validPayload)
|
|
179
|
+
expect(error).toBeUndefined()
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('should reject invalid versionMetadata', () => {
|
|
183
|
+
const payloadWithInvalidVersion = {
|
|
184
|
+
...validPayload,
|
|
185
|
+
meta: {
|
|
186
|
+
...validPayload.meta,
|
|
187
|
+
versionMetadata: {
|
|
188
|
+
versionNumber: 'not-a-number', // Invalid - should be number
|
|
189
|
+
createdAt: new Date('2025-09-08T09:28:15.576Z')
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const { error } = formAdapterSubmissionMessagePayloadSchema.validate(
|
|
194
|
+
payloadWithInvalidVersion
|
|
195
|
+
)
|
|
196
|
+
expect(error).toBeDefined()
|
|
197
|
+
expect(error?.message).toContain('must be a number')
|
|
198
|
+
})
|
|
159
199
|
})
|
|
160
200
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FormStatus,
|
|
3
|
+
formVersionMetadataSchema,
|
|
3
4
|
idSchema,
|
|
4
5
|
notificationEmailAddressSchema,
|
|
5
6
|
slugSchema,
|
|
@@ -29,7 +30,8 @@ export const formAdapterSubmissionMessageMetaSchema =
|
|
|
29
30
|
.valid(...Object.values(FormStatus))
|
|
30
31
|
.required(),
|
|
31
32
|
isPreview: Joi.boolean().required(),
|
|
32
|
-
notificationEmail: notificationEmailAddressSchema.required()
|
|
33
|
+
notificationEmail: notificationEmailAddressSchema.required(),
|
|
34
|
+
versionMetadata: formVersionMetadataSchema.optional()
|
|
33
35
|
})
|
|
34
36
|
|
|
35
37
|
export const formAdapterSubmissionMessageDataSchema =
|
|
@@ -3,11 +3,16 @@ import {
|
|
|
3
3
|
type Event,
|
|
4
4
|
type FormDefinition,
|
|
5
5
|
type FormMetadata,
|
|
6
|
+
type FormVersionMetadata,
|
|
6
7
|
type Item,
|
|
7
8
|
type List,
|
|
8
9
|
type Page
|
|
9
10
|
} from '@defra/forms-model'
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type PluginProperties,
|
|
13
|
+
type Request,
|
|
14
|
+
type ResponseObject
|
|
15
|
+
} from '@hapi/hapi'
|
|
11
16
|
import { type JoiExpression, type ValidationErrorItem } from 'joi'
|
|
12
17
|
|
|
13
18
|
import { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'
|
|
@@ -36,12 +41,15 @@ import {
|
|
|
36
41
|
type FormParams,
|
|
37
42
|
type FormRequest,
|
|
38
43
|
type FormRequestPayload,
|
|
44
|
+
type FormResponseToolkit,
|
|
39
45
|
type FormStatus
|
|
40
46
|
} from '~/src/server/routes/types.js'
|
|
47
|
+
import { type CacheService } from '~/src/server/services/cacheService.js'
|
|
41
48
|
import { type RequestOptions } from '~/src/server/services/httpService.js'
|
|
42
49
|
import { type Services } from '~/src/server/types.js'
|
|
43
50
|
|
|
44
|
-
type
|
|
51
|
+
export type AnyFormRequest = FormRequest | FormRequestPayload
|
|
52
|
+
export type AnyRequest = Request | AnyFormRequest
|
|
45
53
|
|
|
46
54
|
/**
|
|
47
55
|
* Form submission state stores the following in Redis:
|
|
@@ -172,6 +180,7 @@ export interface FormContext {
|
|
|
172
180
|
pageMap: Map<string, PageControllerClass>
|
|
173
181
|
componentMap: Map<string, Component>
|
|
174
182
|
referenceNumber: string
|
|
183
|
+
submittedVersionNumber?: number
|
|
175
184
|
}
|
|
176
185
|
|
|
177
186
|
export type FormContextRequest = (
|
|
@@ -312,7 +321,7 @@ export interface FormPageViewModel extends PageViewModelBase {
|
|
|
312
321
|
context: FormContext
|
|
313
322
|
errors?: FormSubmissionError[]
|
|
314
323
|
hasMissingNotificationEmail?: boolean
|
|
315
|
-
|
|
324
|
+
allowSaveAndExit: boolean
|
|
316
325
|
}
|
|
317
326
|
|
|
318
327
|
export interface RepeaterSummaryPageViewModel extends PageViewModelBase {
|
|
@@ -357,27 +366,26 @@ export type PreparePageEventRequestOptions = (
|
|
|
357
366
|
) => void
|
|
358
367
|
|
|
359
368
|
export type OnRequestCallback = (
|
|
360
|
-
request:
|
|
369
|
+
request: AnyFormRequest,
|
|
361
370
|
params: FormParams,
|
|
362
371
|
definition: FormDefinition,
|
|
363
372
|
metadata: FormMetadata
|
|
364
373
|
) => void
|
|
365
374
|
|
|
375
|
+
export type SaveAndExitHandler = (
|
|
376
|
+
request: FormRequestPayload,
|
|
377
|
+
h: FormResponseToolkit,
|
|
378
|
+
context: FormContext
|
|
379
|
+
) => ResponseObject
|
|
380
|
+
|
|
366
381
|
export interface PluginOptions {
|
|
367
382
|
model?: FormModel
|
|
368
383
|
services?: Services
|
|
369
384
|
controllers?: Record<string, typeof PageController>
|
|
370
|
-
|
|
385
|
+
cache?: CacheService | string
|
|
371
386
|
globals?: Record<string, GlobalFunction>
|
|
372
387
|
filters?: Record<string, FilterFunction>
|
|
373
|
-
|
|
374
|
-
keyGenerator: (request: RequestType) => string
|
|
375
|
-
sessionHydrator: (request: RequestType) => Promise<FormSubmissionState>
|
|
376
|
-
sessionPersister: (
|
|
377
|
-
state: FormSubmissionState,
|
|
378
|
-
request: RequestType
|
|
379
|
-
) => Promise<void>
|
|
380
|
-
}
|
|
388
|
+
saveAndExit?: SaveAndExitHandler
|
|
381
389
|
pluginPath?: string
|
|
382
390
|
nunjucks: {
|
|
383
391
|
baseLayoutPath: string
|
|
@@ -399,6 +407,7 @@ export interface FormAdapterSubmissionMessageMeta {
|
|
|
399
407
|
status: FormStatus
|
|
400
408
|
isPreview: boolean
|
|
401
409
|
notificationEmail: string
|
|
410
|
+
versionMetadata?: FormVersionMetadata
|
|
402
411
|
}
|
|
403
412
|
|
|
404
413
|
export type FormAdapterSubmissionMessageMetaSerialised = Omit<
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
preventDoubleClick: true
|
|
14
14
|
}) }}
|
|
15
15
|
|
|
16
|
-
{% if
|
|
16
|
+
{% if allowSaveAndExit %}
|
|
17
17
|
{{ govukButton({
|
|
18
|
-
text: "Save and
|
|
18
|
+
text: "Save and exit",
|
|
19
19
|
classes: "govuk-button--secondary",
|
|
20
20
|
name: "action",
|
|
21
|
-
value: "save-and-
|
|
21
|
+
value: "save-and-exit",
|
|
22
22
|
preventDoubleClick: true
|
|
23
23
|
}) }}
|
|
24
24
|
{% endif %}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
|
|
4
4
|
{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %}
|
|
5
|
+
{% from "govuk/components/button/macro.njk" import govukButton %}
|
|
5
6
|
{% from "partials/components.html" import componentList with context %}
|
|
6
7
|
|
|
7
8
|
{% block content %}
|
|
@@ -31,7 +32,6 @@
|
|
|
31
32
|
|
|
32
33
|
<form method="post" novalidate>
|
|
33
34
|
<input type="hidden" name="crumb" value="{{ crumb }}">
|
|
34
|
-
<input type="hidden" name="action" value="send">
|
|
35
35
|
|
|
36
36
|
{% if declaration %}
|
|
37
37
|
<h2 class="govuk-heading-m" id="declaration">Declaration</h2>
|
|
@@ -42,10 +42,26 @@
|
|
|
42
42
|
|
|
43
43
|
{{ componentList(components) }}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
<div class="govuk-button-group">
|
|
46
|
+
{% set isDeclaration = declaration or components | length %}
|
|
47
|
+
|
|
48
|
+
{{ govukButton({
|
|
49
|
+
text: "Accept and send" if isDeclaration else "Send",
|
|
50
|
+
name: "action",
|
|
51
|
+
value: "send",
|
|
52
|
+
preventDoubleClick: true
|
|
53
|
+
}) }}
|
|
54
|
+
|
|
55
|
+
{% if allowSaveAndExit %}
|
|
56
|
+
{{ govukButton({
|
|
57
|
+
text: "Save and exit",
|
|
58
|
+
classes: "govuk-button--secondary",
|
|
59
|
+
name: "action",
|
|
60
|
+
value: "save-and-exit",
|
|
61
|
+
preventDoubleClick: true
|
|
62
|
+
}) }}
|
|
63
|
+
{% endif %}
|
|
64
|
+
</div>
|
|
49
65
|
</form>
|
|
50
66
|
</div>
|
|
51
67
|
</div>
|
|
@@ -18,7 +18,7 @@ const logger = createLogger()
|
|
|
18
18
|
let webpackManifest
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* @param {
|
|
21
|
+
* @param {AnyFormRequest | null} request
|
|
22
22
|
*/
|
|
23
23
|
export async function context(request) {
|
|
24
24
|
const { params, response } = request ?? {}
|
|
@@ -62,7 +62,7 @@ export async function context(request) {
|
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Returns the context for the devtool. Consumers won't have access to this.
|
|
65
|
-
* @param {
|
|
65
|
+
* @param {AnyFormRequest | null} _request
|
|
66
66
|
* @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}
|
|
67
67
|
*/
|
|
68
68
|
export function devtoolContext(_request) {
|
|
@@ -97,5 +97,5 @@ export function devtoolContext(_request) {
|
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* @import { ViewContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
100
|
-
* @import {
|
|
100
|
+
* @import { AnyFormRequest } from '~/src/server/plugins/engine/types.js'
|
|
101
101
|
*/
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ReqRefDefaults,
|
|
3
|
+
type Request,
|
|
4
|
+
type ResponseToolkit
|
|
5
|
+
} from '@hapi/hapi'
|
|
2
6
|
|
|
3
7
|
import { type FormPayload } from '~/src/server/plugins/engine/types.js'
|
|
4
8
|
|
|
@@ -33,6 +37,7 @@ export interface FormRequestPayloadRefs extends FormRequestRefs {
|
|
|
33
37
|
|
|
34
38
|
export type FormRequest = Request<FormRequestRefs>
|
|
35
39
|
export type FormRequestPayload = Request<FormRequestPayloadRefs>
|
|
40
|
+
export type FormResponseToolkit = Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
36
41
|
|
|
37
42
|
export enum FormAction {
|
|
38
43
|
Continue = 'continue',
|
|
@@ -40,7 +45,7 @@ export enum FormAction {
|
|
|
40
45
|
Delete = 'delete',
|
|
41
46
|
AddAnother = 'add-another',
|
|
42
47
|
Send = 'send',
|
|
43
|
-
|
|
48
|
+
SaveAndExit = 'save-and-exit'
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
export enum FormStatus {
|
|
@@ -2,11 +2,7 @@ import { type Request, type Server } from '@hapi/hapi'
|
|
|
2
2
|
|
|
3
3
|
import { config } from '~/src/config/index.js'
|
|
4
4
|
import { type FormRequest } from '~/src/server/routes/types.js'
|
|
5
|
-
import {
|
|
6
|
-
ADDITIONAL_IDENTIFIER,
|
|
7
|
-
CacheService,
|
|
8
|
-
merge
|
|
9
|
-
} from '~/src/server/services/cacheService.js'
|
|
5
|
+
import { CacheService, merge } from '~/src/server/services/cacheService.js'
|
|
10
6
|
|
|
11
7
|
describe('CacheService', () => {
|
|
12
8
|
let mockServer: Partial<Server>
|
|
@@ -76,63 +72,6 @@ describe('CacheService', () => {
|
|
|
76
72
|
expect(result).toEqual({})
|
|
77
73
|
})
|
|
78
74
|
})
|
|
79
|
-
|
|
80
|
-
it('should rehydrate state using custom fetcher when cache is missed', async () => {
|
|
81
|
-
const rehydratedState = { rehydrated: true }
|
|
82
|
-
|
|
83
|
-
const customFetcher = jest.fn().mockResolvedValue(rehydratedState)
|
|
84
|
-
|
|
85
|
-
cacheService = new CacheService({
|
|
86
|
-
server: mockServer as Server,
|
|
87
|
-
cacheName: 'test-cache',
|
|
88
|
-
options: { sessionHydrator: customFetcher }
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
const mockRequest = {
|
|
92
|
-
yar: { id: 'session-id' },
|
|
93
|
-
params: { state: 's', slug: 'p' }
|
|
94
|
-
} as unknown as FormRequest
|
|
95
|
-
|
|
96
|
-
mockCache.get
|
|
97
|
-
.mockResolvedValueOnce(null)
|
|
98
|
-
.mockResolvedValueOnce(rehydratedState)
|
|
99
|
-
|
|
100
|
-
const result = await cacheService.getState(mockRequest)
|
|
101
|
-
|
|
102
|
-
expect(customFetcher).toHaveBeenCalledWith(mockRequest)
|
|
103
|
-
expect(mockCache.set).toHaveBeenCalledWith(
|
|
104
|
-
expect.objectContaining({
|
|
105
|
-
segment: 'cache',
|
|
106
|
-
id: expect.stringContaining('session-id')
|
|
107
|
-
}),
|
|
108
|
-
rehydratedState,
|
|
109
|
-
config.get('sessionTimeout')
|
|
110
|
-
)
|
|
111
|
-
expect(result).toEqual(rehydratedState)
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('should return empty object when custom fetcher returns null', async () => {
|
|
115
|
-
const customFetcher = jest.fn().mockResolvedValue(null)
|
|
116
|
-
|
|
117
|
-
cacheService = new CacheService({
|
|
118
|
-
server: mockServer as Server,
|
|
119
|
-
cacheName: 'test-cache',
|
|
120
|
-
options: { sessionHydrator: customFetcher }
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
const mockRequest = {
|
|
124
|
-
yar: { id: 'session-id' },
|
|
125
|
-
params: { state: 's', slug: 'p' }
|
|
126
|
-
} as unknown as FormRequest
|
|
127
|
-
|
|
128
|
-
mockCache.get.mockResolvedValue(null)
|
|
129
|
-
|
|
130
|
-
const result = await cacheService.getState(mockRequest)
|
|
131
|
-
|
|
132
|
-
expect(customFetcher).toHaveBeenCalledWith(mockRequest)
|
|
133
|
-
expect(mockCache.set).not.toHaveBeenCalled()
|
|
134
|
-
expect(result).toEqual({})
|
|
135
|
-
})
|
|
136
75
|
})
|
|
137
76
|
|
|
138
77
|
describe('setState', () => {
|
|
@@ -178,61 +117,6 @@ describe('CacheService', () => {
|
|
|
178
117
|
)
|
|
179
118
|
})
|
|
180
119
|
})
|
|
181
|
-
|
|
182
|
-
it('should use custom key generator if provided', async () => {
|
|
183
|
-
const customKey = 'my-custom-key'
|
|
184
|
-
const customKeyGenerator = jest.fn().mockReturnValue(customKey)
|
|
185
|
-
|
|
186
|
-
cacheService = new CacheService({
|
|
187
|
-
server: mockServer as Server,
|
|
188
|
-
cacheName: 'test-cache',
|
|
189
|
-
options: { keyGenerator: customKeyGenerator }
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
const mockRequest = {
|
|
193
|
-
yar: { id: 'some-session' },
|
|
194
|
-
params: { state: 'form1', slug: 'page1' }
|
|
195
|
-
} as unknown as FormRequest
|
|
196
|
-
|
|
197
|
-
await cacheService.setState(mockRequest, { test: 'value' })
|
|
198
|
-
|
|
199
|
-
expect(mockCache.set).toHaveBeenCalledWith(
|
|
200
|
-
{
|
|
201
|
-
segment: 'cache',
|
|
202
|
-
id: 'my-custom-key'
|
|
203
|
-
},
|
|
204
|
-
{ test: 'value' },
|
|
205
|
-
expect.any(Number)
|
|
206
|
-
)
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
it('should append additionalIdentifier to custom key', () => {
|
|
210
|
-
const customKey = 'custom:key:base:'
|
|
211
|
-
const customKeyGenerator = jest.fn().mockReturnValue(customKey)
|
|
212
|
-
|
|
213
|
-
cacheService = new CacheService({
|
|
214
|
-
server: mockServer as Server,
|
|
215
|
-
cacheName: 'test-cache',
|
|
216
|
-
options: { keyGenerator: customKeyGenerator }
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
const mockRequest = {
|
|
220
|
-
yar: { id: 'session-id' },
|
|
221
|
-
params: { state: 'formA', slug: 'step1' }
|
|
222
|
-
} as unknown as FormRequest
|
|
223
|
-
|
|
224
|
-
const result = cacheService.Key(
|
|
225
|
-
mockRequest,
|
|
226
|
-
ADDITIONAL_IDENTIFIER.Confirmation
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
expect(result).toEqual({
|
|
230
|
-
segment: 'cache',
|
|
231
|
-
id: 'custom:key:base::confirmation'
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
expect(customKeyGenerator).toHaveBeenCalledWith(mockRequest)
|
|
235
|
-
})
|
|
236
120
|
})
|
|
237
121
|
|
|
238
122
|
describe('merge', () => {
|