@defra/forms-engine-plugin 2.1.9 → 3.0.0
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/components/helpers/components.js +1 -1
- package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
- 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.js +1 -1
- 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/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.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.d.ts +10 -11
- 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 +3 -1
- 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/MultilineTextField.test.ts +11 -0
- package/src/server/plugins/engine/components/helpers/components.ts +1 -1
- 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 +2 -2
- package/src/server/plugins/engine/models/FormModel.ts +1 -2
- 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/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 +6 -11
- 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.ts +19 -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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"./services/*": "./.server/server/plugins/engine/services/*",
|
|
29
29
|
"./engine/*": "./.server/server/plugins/engine/*",
|
|
30
30
|
"./helpers.js": "./.server/server/plugins/engine/components/helpers.js",
|
|
31
|
+
"./schema.js": "./.server/server/schemas/index.js",
|
|
31
32
|
"./templates/*": "./.server/server/plugins/engine/views/*",
|
|
33
|
+
"./cache-service.js": "./.server/server/services/cacheService.js",
|
|
32
34
|
"./package.json": "./package.json"
|
|
33
35
|
},
|
|
34
36
|
"scripts": {
|
package/src/server/index.test.ts
CHANGED
|
@@ -639,42 +639,3 @@ describe('prepareEnvironment', () => {
|
|
|
639
639
|
)
|
|
640
640
|
})
|
|
641
641
|
})
|
|
642
|
-
|
|
643
|
-
describe('Exit route handlers', () => {
|
|
644
|
-
let server: Server
|
|
645
|
-
|
|
646
|
-
beforeAll(async () => {
|
|
647
|
-
server = await createServer({
|
|
648
|
-
services: defaultServices
|
|
649
|
-
})
|
|
650
|
-
await server.initialize()
|
|
651
|
-
})
|
|
652
|
-
|
|
653
|
-
afterAll(async () => {
|
|
654
|
-
await server.stop()
|
|
655
|
-
})
|
|
656
|
-
|
|
657
|
-
beforeEach(() => {
|
|
658
|
-
jest.mocked(getFormMetadata).mockResolvedValue(fixtures.form.metadata)
|
|
659
|
-
server.app.models.clear()
|
|
660
|
-
})
|
|
661
|
-
|
|
662
|
-
test('GET /exit returns 200 with exit page content', async () => {
|
|
663
|
-
jest.mocked(getFormMetadata).mockResolvedValueOnce({
|
|
664
|
-
...fixtures.form.metadata,
|
|
665
|
-
live: fixtures.form.state
|
|
666
|
-
})
|
|
667
|
-
|
|
668
|
-
jest.mocked(getFormDefinition).mockResolvedValue(fixtures.form.definition)
|
|
669
|
-
|
|
670
|
-
const options = {
|
|
671
|
-
method: 'GET',
|
|
672
|
-
url: `${FORM_PREFIX}/slug/exit`
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
const res = await server.inject(options)
|
|
676
|
-
|
|
677
|
-
expect(res.statusCode).toBe(StatusCodes.OK)
|
|
678
|
-
expect(res.result).toContain('Your progress has been saved')
|
|
679
|
-
})
|
|
680
|
-
})
|
package/src/server/index.ts
CHANGED
|
@@ -82,8 +82,11 @@ export async function createServer(routeConfig?: RouteConfig) {
|
|
|
82
82
|
prepareSecureContext(server)
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
const cacheService = routeConfig?.cacheServiceCreator
|
|
86
|
+
? routeConfig.cacheServiceCreator(server)
|
|
87
|
+
: undefined
|
|
85
88
|
const pluginCrumb = configureCrumbPlugin(routeConfig)
|
|
86
|
-
const pluginEngine = await configureEnginePlugin(routeConfig)
|
|
89
|
+
const pluginEngine = await configureEnginePlugin(routeConfig, cacheService)
|
|
87
90
|
|
|
88
91
|
await server.register(pluginSession)
|
|
89
92
|
await server.register(pluginPulse)
|
|
@@ -86,9 +86,9 @@ There are a number of `LiquidJS` filters available to you from within the templa
|
|
|
86
86
|
]
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
### Save and
|
|
89
|
+
### Save and exit
|
|
90
90
|
|
|
91
|
-
See [our save and
|
|
91
|
+
See [our save and exit feature page](/docs/features/code-based/SAVE_AND_EXIT.md).
|
|
92
92
|
|
|
93
93
|
### Additional notes
|
|
94
94
|
|
|
@@ -161,6 +161,17 @@ describe('MultilineTextField', () => {
|
|
|
161
161
|
expect(answer2).toBe('')
|
|
162
162
|
})
|
|
163
163
|
|
|
164
|
+
it('returns multiline text from state, collapsing multiple newlines into one', () => {
|
|
165
|
+
const state1 = getFormState('Line 1\r\nLine 2\r\nLine 3')
|
|
166
|
+
const state2 = getFormState('Line 1\r\n\r\nLine 2\r\n\r\n\r\nLine 3')
|
|
167
|
+
|
|
168
|
+
const answer1 = getAnswer(field, state1)
|
|
169
|
+
const answer2 = getAnswer(field, state2)
|
|
170
|
+
|
|
171
|
+
expect(answer1).toBe('Line 1<br>Line 2<br>Line 3<br>')
|
|
172
|
+
expect(answer2).toBe('Line 1<br>Line 2<br>Line 3<br>')
|
|
173
|
+
})
|
|
174
|
+
|
|
164
175
|
it('returns payload from state', () => {
|
|
165
176
|
const state1 = getFormState('Textarea')
|
|
166
177
|
const state2 = getFormState(null)
|
|
@@ -315,7 +315,7 @@ export function getAnswerMarkdown(
|
|
|
315
315
|
} else if (field instanceof Components.MultilineTextField) {
|
|
316
316
|
// Preserve Multiline text new lines
|
|
317
317
|
answerEscaped = answer
|
|
318
|
-
.split(
|
|
318
|
+
.split(/(?:\r?\n)+/)
|
|
319
319
|
.map(escapeMarkdown)
|
|
320
320
|
.join('\n')
|
|
321
321
|
.concat('\n')
|
|
@@ -25,7 +25,7 @@ describe('helpers tests', () => {
|
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
describe('ComponentBase tests', () => {
|
|
28
|
-
test('should handle save and
|
|
28
|
+
test('should handle save and exit functionality', () => {
|
|
29
29
|
const mockComponentDef = {
|
|
30
30
|
type: 'TextField',
|
|
31
31
|
name: 'testField',
|
|
@@ -10,17 +10,21 @@ import { formsService } from '~/src/server/plugins/engine/services/localFormsSer
|
|
|
10
10
|
import { type PluginOptions } from '~/src/server/plugins/engine/types.js'
|
|
11
11
|
import { findPackageRoot } from '~/src/server/plugins/engine/vision.js'
|
|
12
12
|
import { devtoolContext } from '~/src/server/plugins/nunjucks/context.js'
|
|
13
|
+
import { type CacheService } from '~/src/server/services/cacheService.js'
|
|
13
14
|
import { type RouteConfig } from '~/src/server/types.js'
|
|
14
15
|
|
|
15
|
-
export const configureEnginePlugin = async (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
export const configureEnginePlugin = async (
|
|
17
|
+
{
|
|
18
|
+
formFileName,
|
|
19
|
+
formFilePath,
|
|
20
|
+
services,
|
|
21
|
+
controllers,
|
|
22
|
+
preparePageEventRequestOptions,
|
|
23
|
+
onRequest,
|
|
24
|
+
saveAndExit
|
|
25
|
+
}: RouteConfig = {},
|
|
26
|
+
cache?: CacheService
|
|
27
|
+
): Promise<{
|
|
24
28
|
plugin: typeof plugin
|
|
25
29
|
options: PluginOptions
|
|
26
30
|
}> => {
|
|
@@ -50,7 +54,7 @@ export const configureEnginePlugin = async ({
|
|
|
50
54
|
formsService: await formsService()
|
|
51
55
|
},
|
|
52
56
|
controllers,
|
|
53
|
-
|
|
57
|
+
cache: cache ?? 'session',
|
|
54
58
|
nunjucks: {
|
|
55
59
|
baseLayoutPath: 'dxt-devtool-baselayout.html',
|
|
56
60
|
paths: [join(findPackageRoot(), 'src/server/devserver')] // custom layout to make it really clear this is not the same as the runner
|
|
@@ -59,7 +63,7 @@ export const configureEnginePlugin = async ({
|
|
|
59
63
|
preparePageEventRequestOptions,
|
|
60
64
|
onRequest,
|
|
61
65
|
baseUrl: 'http://localhost:3009', // always runs locally
|
|
62
|
-
|
|
66
|
+
saveAndExit
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
}
|
|
@@ -30,7 +30,8 @@ import {
|
|
|
30
30
|
import {
|
|
31
31
|
FormAction,
|
|
32
32
|
FormStatus,
|
|
33
|
-
type FormRequest
|
|
33
|
+
type FormRequest,
|
|
34
|
+
type FormResponseToolkit
|
|
34
35
|
} from '~/src/server/routes/types.js'
|
|
35
36
|
import definition from '~/test/form/definitions/basic.js'
|
|
36
37
|
import templateDefinition from '~/test/form/definitions/templates.js'
|
|
@@ -47,7 +48,7 @@ type HrefFilter = (this: NunjucksContext, path: string) => string | undefined
|
|
|
47
48
|
describe('Helpers', () => {
|
|
48
49
|
let page: PageControllerClass
|
|
49
50
|
let request: FormContextRequest
|
|
50
|
-
let h:
|
|
51
|
+
let h: FormResponseToolkit
|
|
51
52
|
|
|
52
53
|
beforeEach(() => {
|
|
53
54
|
const model = new FormModel(definition, {
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
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
25
|
import {
|
|
26
|
+
type AnyFormRequest,
|
|
26
27
|
type FormContext,
|
|
27
28
|
type FormContextRequest,
|
|
28
29
|
type FormSubmissionError
|
|
@@ -32,8 +33,7 @@ import {
|
|
|
32
33
|
FormStatus,
|
|
33
34
|
type FormParams,
|
|
34
35
|
type FormQuery,
|
|
35
|
-
type
|
|
36
|
-
type FormRequestPayload
|
|
36
|
+
type FormResponseToolkit
|
|
37
37
|
} from '~/src/server/routes/types.js'
|
|
38
38
|
|
|
39
39
|
const logger = createLogger()
|
|
@@ -117,7 +117,7 @@ engine.registerFilter('answer', function (name: string) {
|
|
|
117
117
|
|
|
118
118
|
export function proceed(
|
|
119
119
|
request: Pick<FormContextRequest, 'method' | 'payload' | 'query'>,
|
|
120
|
-
h:
|
|
120
|
+
h: FormResponseToolkit,
|
|
121
121
|
nextUrl: string
|
|
122
122
|
) {
|
|
123
123
|
const { method, payload, query } = request
|
|
@@ -327,7 +327,7 @@ export function getError(detail: ValidationErrorItem): FormSubmissionError {
|
|
|
327
327
|
* is not disabled on the current route, and that cookies/state are present.
|
|
328
328
|
*/
|
|
329
329
|
export function safeGenerateCrumb(
|
|
330
|
-
request:
|
|
330
|
+
request: AnyFormRequest | null
|
|
331
331
|
): string | undefined {
|
|
332
332
|
// no request or no .state
|
|
333
333
|
if (!request?.state) {
|
|
@@ -380,8 +380,8 @@ export function getCacheService(server: Server) {
|
|
|
380
380
|
return getPluginOptions(server).cacheService
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
-
export function
|
|
384
|
-
return getPluginOptions(server).
|
|
383
|
+
export function getSaveAndExitHelpers(server: Server) {
|
|
384
|
+
return getPluginOptions(server).saveAndExit
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
export function getPluginOptions(server: Server) {
|
|
@@ -185,7 +185,7 @@ describe('FormModel', () => {
|
|
|
185
185
|
})
|
|
186
186
|
|
|
187
187
|
describe('getFormContext', () => {
|
|
188
|
-
it.each([FormAction.Validate, FormAction.
|
|
188
|
+
it.each([FormAction.Validate, FormAction.SaveAndExit, undefined])(
|
|
189
189
|
'returns a form context with the correct payload and state when action is %s',
|
|
190
190
|
(action) => {
|
|
191
191
|
const formModel = new FormModel(fieldsRequiredDefinition, {
|
|
@@ -219,7 +219,7 @@ describe('FormModel', () => {
|
|
|
219
219
|
}
|
|
220
220
|
)
|
|
221
221
|
|
|
222
|
-
it('returns without updating the state when the action is not validate or
|
|
222
|
+
it('returns without updating the state when the action is not validate or saveAndExit', () => {
|
|
223
223
|
const formModel = new FormModel(fieldsRequiredDefinition, {
|
|
224
224
|
basePath: '/components'
|
|
225
225
|
})
|
|
@@ -543,8 +543,7 @@ function validateFormPayload(
|
|
|
543
543
|
// Skip validation GET requests or other actions
|
|
544
544
|
if (
|
|
545
545
|
!request.payload ||
|
|
546
|
-
(action &&
|
|
547
|
-
![FormAction.Validate, FormAction.SaveAndReturn].includes(action))
|
|
546
|
+
(action && ![FormAction.Validate, FormAction.SaveAndExit].includes(action))
|
|
548
547
|
) {
|
|
549
548
|
return context
|
|
550
549
|
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from '~/src/server/plugins/engine/models/index.js'
|
|
6
6
|
import { SummaryPageController } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'
|
|
7
7
|
import { buildFormContextRequest } from '~/src/server/plugins/engine/pageControllers/__stubs__/request.js'
|
|
8
|
-
import {
|
|
8
|
+
import { serverWithSaveAndExit } from '~/src/server/plugins/engine/pageControllers/__stubs__/server.js'
|
|
9
9
|
import {
|
|
10
10
|
createPage,
|
|
11
11
|
type PageControllerClass
|
|
@@ -274,16 +274,16 @@ describe('SummaryPageController', () => {
|
|
|
274
274
|
},
|
|
275
275
|
query: {},
|
|
276
276
|
app: { model },
|
|
277
|
-
server:
|
|
277
|
+
server: serverWithSaveAndExit
|
|
278
278
|
}
|
|
279
279
|
})
|
|
280
280
|
|
|
281
|
-
describe('Save and
|
|
282
|
-
it('should show save and
|
|
283
|
-
expect(controller.
|
|
281
|
+
describe('Save and Exit functionality', () => {
|
|
282
|
+
it('should show save and exit button on summary page', () => {
|
|
283
|
+
expect(controller.shouldShowSaveAndExit(request.server)).toBe(true)
|
|
284
284
|
})
|
|
285
285
|
|
|
286
|
-
it('should handle save and
|
|
286
|
+
it('should handle save and exit from summary page', () => {
|
|
287
287
|
const state: FormState = {
|
|
288
288
|
$$__referenceNumber: 'foobar',
|
|
289
289
|
orderType: 'collection',
|
|
@@ -293,7 +293,7 @@ describe('SummaryPageController', () => {
|
|
|
293
293
|
const context = model.getFormContext(request, state)
|
|
294
294
|
const viewModel = controller.getViewModel(request, context)
|
|
295
295
|
|
|
296
|
-
expect(viewModel).toHaveProperty('
|
|
296
|
+
expect(viewModel).toHaveProperty('allowSaveAndExit', true)
|
|
297
297
|
})
|
|
298
298
|
|
|
299
299
|
it('should display correct page title', () => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Joi from 'joi'
|
|
2
2
|
|
|
3
3
|
import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
|
|
4
|
+
import { CacheService } from '~/src/server/services/index.js'
|
|
4
5
|
|
|
5
6
|
const logger = createLogger()
|
|
6
7
|
|
|
@@ -8,7 +9,10 @@ const pluginRegistrationOptionsSchema = Joi.object({
|
|
|
8
9
|
model: Joi.object().optional(),
|
|
9
10
|
services: Joi.object().optional(),
|
|
10
11
|
controllers: Joi.object().pattern(Joi.string(), Joi.any()).optional(),
|
|
11
|
-
|
|
12
|
+
cache: Joi.alternatives().try(
|
|
13
|
+
Joi.object().instance(CacheService),
|
|
14
|
+
Joi.string()
|
|
15
|
+
),
|
|
12
16
|
globals: Joi.object().pattern(Joi.string(), Joi.any()).optional(),
|
|
13
17
|
filters: Joi.object().pattern(Joi.string(), Joi.any()).optional(),
|
|
14
18
|
pluginPath: Joi.string().optional(),
|
|
@@ -20,11 +24,7 @@ const pluginRegistrationOptionsSchema = Joi.object({
|
|
|
20
24
|
preparePageEventRequestOptions: Joi.function().optional(),
|
|
21
25
|
onRequest: Joi.function().optional(),
|
|
22
26
|
baseUrl: Joi.string().uri().required(),
|
|
23
|
-
|
|
24
|
-
keyGenerator: Joi.function(),
|
|
25
|
-
sessionHydrator: Joi.function(),
|
|
26
|
-
sessionPersister: Joi.function()
|
|
27
|
-
}).optional()
|
|
27
|
+
saveAndExit: Joi.function().optional()
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -19,7 +19,7 @@ describe('validatePluginOptions', () => {
|
|
|
19
19
|
expect(validatePluginOptions(validOptions)).toEqual(validOptions)
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
it('accepts optional
|
|
22
|
+
it('accepts optional property saveAndExit', () => {
|
|
23
23
|
/**
|
|
24
24
|
* @type {PluginOptions}
|
|
25
25
|
*/
|
|
@@ -32,11 +32,7 @@ describe('validatePluginOptions', () => {
|
|
|
32
32
|
return { hello: 'world' }
|
|
33
33
|
},
|
|
34
34
|
baseUrl: 'http://localhost:3009',
|
|
35
|
-
|
|
36
|
-
keyGenerator: () => 'test-key',
|
|
37
|
-
sessionHydrator: () => Promise.resolve({ someState: 'value' }),
|
|
38
|
-
sessionPersister: () => Promise.resolve(undefined)
|
|
39
|
-
}
|
|
35
|
+
saveAndExit: (request, h) => h.redirect('/save-and-exit')
|
|
40
36
|
}
|
|
41
37
|
|
|
42
38
|
expect(validatePluginOptions(validOptionsWithOptionals)).toEqual(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/dot-notation */
|
|
2
2
|
import { ComponentType, type ComponentDef } from '@defra/forms-model'
|
|
3
|
-
import { type ResponseToolkit } from '@hapi/hapi'
|
|
4
3
|
import { type ValidationErrorItem, type ValidationResult } from 'joi'
|
|
5
4
|
|
|
6
5
|
import { tempItemSchema } from '~/src/server/plugins/engine/components/FileUploadField.js'
|
|
@@ -14,7 +13,7 @@ import {
|
|
|
14
13
|
prepareStatus
|
|
15
14
|
} from '~/src/server/plugins/engine/pageControllers/FileUploadPageController.js'
|
|
16
15
|
import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
|
|
17
|
-
import {
|
|
16
|
+
import { serverWithSaveAndExit } from '~/src/server/plugins/engine/pageControllers/__stubs__/server.js'
|
|
18
17
|
import * as pageHelpers from '~/src/server/plugins/engine/pageControllers/helpers/index.js'
|
|
19
18
|
import * as uploadService from '~/src/server/plugins/engine/services/uploadService.js'
|
|
20
19
|
import {
|
|
@@ -30,7 +29,8 @@ import {
|
|
|
30
29
|
} from '~/src/server/plugins/engine/types.js'
|
|
31
30
|
import {
|
|
32
31
|
type FormRequest,
|
|
33
|
-
type FormRequestPayload
|
|
32
|
+
type FormRequestPayload,
|
|
33
|
+
type FormResponseToolkit
|
|
34
34
|
} from '~/src/server/routes/types.js'
|
|
35
35
|
import { type CacheService } from '~/src/server/services/index.js'
|
|
36
36
|
import definition from '~/test/form/definitions/file-upload-basic.js'
|
|
@@ -1040,7 +1040,7 @@ describe('FileUploadPageController', () => {
|
|
|
1040
1040
|
} as unknown as FormRequest
|
|
1041
1041
|
|
|
1042
1042
|
const context = { state } as unknown as FormContext
|
|
1043
|
-
const h = {} as unknown as
|
|
1043
|
+
const h = {} as unknown as FormResponseToolkit
|
|
1044
1044
|
|
|
1045
1045
|
const handler = controller.makeGetItemDeleteRouteHandler()
|
|
1046
1046
|
|
|
@@ -1058,7 +1058,7 @@ describe('FileUploadPageController', () => {
|
|
|
1058
1058
|
|
|
1059
1059
|
const h = {
|
|
1060
1060
|
redirect: jest.fn()
|
|
1061
|
-
} as unknown as
|
|
1061
|
+
} as unknown as FormResponseToolkit
|
|
1062
1062
|
|
|
1063
1063
|
const context = {
|
|
1064
1064
|
state: {}
|
|
@@ -1119,11 +1119,9 @@ describe('FileUploadPageController', () => {
|
|
|
1119
1119
|
})
|
|
1120
1120
|
})
|
|
1121
1121
|
|
|
1122
|
-
describe('
|
|
1123
|
-
it('should return true when save and
|
|
1124
|
-
expect(controller.
|
|
1125
|
-
true
|
|
1126
|
-
)
|
|
1122
|
+
describe('shouldShowSaveAndExit', () => {
|
|
1123
|
+
it('should return true when save and exit is enabled', () => {
|
|
1124
|
+
expect(controller.shouldShowSaveAndExit(serverWithSaveAndExit)).toBe(true)
|
|
1127
1125
|
})
|
|
1128
1126
|
})
|
|
1129
1127
|
})
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ComponentType, type PageFileUpload } from '@defra/forms-model'
|
|
2
2
|
import Boom from '@hapi/boom'
|
|
3
|
-
import { type ResponseToolkit } from '@hapi/hapi'
|
|
4
3
|
import { wait } from '@hapi/hoek'
|
|
5
4
|
import { type ValidationErrorItem } from 'joi'
|
|
6
5
|
|
|
@@ -23,6 +22,7 @@ import {
|
|
|
23
22
|
import {
|
|
24
23
|
FileStatus,
|
|
25
24
|
UploadStatus,
|
|
25
|
+
type AnyFormRequest,
|
|
26
26
|
type FeaturedFormPageViewModel,
|
|
27
27
|
type FileState,
|
|
28
28
|
type FormContext,
|
|
@@ -35,7 +35,8 @@ import {
|
|
|
35
35
|
} from '~/src/server/plugins/engine/types.js'
|
|
36
36
|
import {
|
|
37
37
|
type FormRequest,
|
|
38
|
-
type FormRequestPayload
|
|
38
|
+
type FormRequestPayload,
|
|
39
|
+
type FormResponseToolkit
|
|
39
40
|
} from '~/src/server/routes/types.js'
|
|
40
41
|
|
|
41
42
|
const MAX_UPLOADS = 25
|
|
@@ -111,7 +112,7 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
111
112
|
return payload
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
async getState(request:
|
|
115
|
+
async getState(request: AnyFormRequest) {
|
|
115
116
|
const { fileUpload } = this
|
|
116
117
|
|
|
117
118
|
// Get the actual state
|
|
@@ -148,7 +149,7 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
148
149
|
return (
|
|
149
150
|
request: FormRequest,
|
|
150
151
|
context: FormContext,
|
|
151
|
-
h:
|
|
152
|
+
h: FormResponseToolkit
|
|
152
153
|
) => {
|
|
153
154
|
const { viewModel } = this
|
|
154
155
|
const { params } = request
|
|
@@ -183,7 +184,7 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
183
184
|
return async (
|
|
184
185
|
request: FormRequestPayload,
|
|
185
186
|
context: FormContext,
|
|
186
|
-
h:
|
|
187
|
+
h: FormResponseToolkit
|
|
187
188
|
) => {
|
|
188
189
|
const { path } = this
|
|
189
190
|
const { state } = context
|
|
@@ -279,7 +280,7 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
279
280
|
* @param state - the form state
|
|
280
281
|
*/
|
|
281
282
|
private async refreshUpload(
|
|
282
|
-
request:
|
|
283
|
+
request: AnyFormRequest,
|
|
283
284
|
state: FormSubmissionState
|
|
284
285
|
) {
|
|
285
286
|
state = await this.checkUploadStatus(request, state)
|
|
@@ -295,7 +296,7 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
295
296
|
* @param depth - the number of retries so far
|
|
296
297
|
*/
|
|
297
298
|
private async checkUploadStatus(
|
|
298
|
-
request:
|
|
299
|
+
request: AnyFormRequest,
|
|
299
300
|
state: FormSubmissionState,
|
|
300
301
|
depth = 1
|
|
301
302
|
): Promise<FormSubmissionState> {
|
|
@@ -417,7 +418,7 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
417
418
|
* @param state - the form state
|
|
418
419
|
*/
|
|
419
420
|
private async initiateAndStoreNewUpload(
|
|
420
|
-
request:
|
|
421
|
+
request: AnyFormRequest,
|
|
421
422
|
state: FormSubmissionState
|
|
422
423
|
) {
|
|
423
424
|
const { fileUpload, href, path } = this
|
|
@@ -3,8 +3,11 @@ import { type ResponseToolkit } from '@hapi/hapi'
|
|
|
3
3
|
import { FORM_PREFIX } from '~/src/server/constants.js'
|
|
4
4
|
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
5
5
|
import { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { serverWithSaveAndExit } from '~/src/server/plugins/engine/pageControllers/__stubs__/server.js'
|
|
7
|
+
import {
|
|
8
|
+
type FormRequest,
|
|
9
|
+
type FormResponseToolkit
|
|
10
|
+
} from '~/src/server/routes/types.js'
|
|
8
11
|
import definition from '~/test/form/definitions/basic.js'
|
|
9
12
|
|
|
10
13
|
describe('PageController', () => {
|
|
@@ -155,7 +158,7 @@ describe('PageController', () => {
|
|
|
155
158
|
app: { model }
|
|
156
159
|
} as FormRequest
|
|
157
160
|
|
|
158
|
-
const h:
|
|
161
|
+
const h: FormResponseToolkit = {
|
|
159
162
|
redirect: jest.fn(),
|
|
160
163
|
view: jest.fn()
|
|
161
164
|
}
|
|
@@ -206,10 +209,10 @@ describe('PageController', () => {
|
|
|
206
209
|
)
|
|
207
210
|
})
|
|
208
211
|
|
|
209
|
-
it('supports save and
|
|
212
|
+
it('supports save and exit functionality', async () => {
|
|
210
213
|
const mockRequest = {
|
|
211
214
|
...request,
|
|
212
|
-
payload: {
|
|
215
|
+
payload: { saveAndExit: true }
|
|
213
216
|
} as FormRequest
|
|
214
217
|
|
|
215
218
|
const mockResponse = {
|
|
@@ -232,9 +235,9 @@ describe('PageController', () => {
|
|
|
232
235
|
})
|
|
233
236
|
})
|
|
234
237
|
|
|
235
|
-
describe('
|
|
236
|
-
it('should return false (PageController does not allow save and
|
|
237
|
-
expect(controller1.
|
|
238
|
+
describe('shouldShowSaveAndExit', () => {
|
|
239
|
+
it('should return false (PageController does not allow save and exit)', () => {
|
|
240
|
+
expect(controller1.shouldShowSaveAndExit(serverWithSaveAndExit)).toBe(
|
|
238
241
|
false
|
|
239
242
|
)
|
|
240
243
|
})
|
|
@@ -6,17 +6,12 @@ import {
|
|
|
6
6
|
type Section
|
|
7
7
|
} from '@defra/forms-model'
|
|
8
8
|
import Boom from '@hapi/boom'
|
|
9
|
-
import {
|
|
10
|
-
type Lifecycle,
|
|
11
|
-
type ResponseToolkit,
|
|
12
|
-
type RouteOptions,
|
|
13
|
-
type Server
|
|
14
|
-
} from '@hapi/hapi'
|
|
9
|
+
import { type Lifecycle, type RouteOptions, type Server } from '@hapi/hapi'
|
|
15
10
|
|
|
16
11
|
import { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
17
12
|
import {
|
|
18
13
|
encodeUrl,
|
|
19
|
-
|
|
14
|
+
getSaveAndExitHelpers,
|
|
20
15
|
getStartPath,
|
|
21
16
|
normalisePath
|
|
22
17
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
@@ -30,7 +25,8 @@ import {
|
|
|
30
25
|
type FormRequest,
|
|
31
26
|
type FormRequestPayload,
|
|
32
27
|
type FormRequestPayloadRefs,
|
|
33
|
-
type FormRequestRefs
|
|
28
|
+
type FormRequestRefs,
|
|
29
|
+
type FormResponseToolkit
|
|
34
30
|
} from '~/src/server/routes/types.js'
|
|
35
31
|
|
|
36
32
|
export class PageController {
|
|
@@ -47,7 +43,7 @@ export class PageController {
|
|
|
47
43
|
events?: Events
|
|
48
44
|
collection?: ComponentCollection
|
|
49
45
|
viewName = 'index'
|
|
50
|
-
|
|
46
|
+
allowSaveAndExit = false
|
|
51
47
|
|
|
52
48
|
constructor(model: FormModel, pageDef: Page) {
|
|
53
49
|
const { def } = model
|
|
@@ -171,7 +167,7 @@ export class PageController {
|
|
|
171
167
|
makeGetRouteHandler(): (
|
|
172
168
|
request: FormRequest,
|
|
173
169
|
context: FormContext,
|
|
174
|
-
h:
|
|
170
|
+
h: FormResponseToolkit
|
|
175
171
|
) => ReturnType<Lifecycle.Method<FormRequestRefs>> {
|
|
176
172
|
return (request, context, h) => {
|
|
177
173
|
const { viewModel, viewName } = this
|
|
@@ -182,14 +178,12 @@ export class PageController {
|
|
|
182
178
|
makePostRouteHandler(): (
|
|
183
179
|
request: FormRequestPayload,
|
|
184
180
|
context: FormContext,
|
|
185
|
-
h:
|
|
181
|
+
h: FormResponseToolkit
|
|
186
182
|
) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {
|
|
187
183
|
throw Boom.badRequest('Unsupported POST route handler for this page')
|
|
188
184
|
}
|
|
189
185
|
|
|
190
|
-
|
|
191
|
-
return (
|
|
192
|
-
getSaveAndReturnHelpers(server) !== undefined && this.allowSaveAndReturn
|
|
193
|
-
)
|
|
186
|
+
shouldShowSaveAndExit(server: Server): boolean {
|
|
187
|
+
return getSaveAndExitHelpers(server) !== undefined && this.allowSaveAndExit
|
|
194
188
|
}
|
|
195
189
|
}
|