@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type OnRequestCallback,\n type PluginOptions,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\nimport { type Server } from '@hapi/hapi'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type OnRequestCallback,\n type PluginOptions,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n saveAndExit?: PluginOptions['saveAndExit']\n cacheServiceCreator?: (server: Server) => CacheService\n}\n\nexport interface OutputService {\n submit: (\n context: FormContext,\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload,\n formMetadata?: FormMetadata\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {
|
|
1
|
+
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type AnyFormRequest,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.ts'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: AnyRequest) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: AnyFormRequest | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n saveAndExit?: PluginOptions['saveAndExit']\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module '@hapi/scooter' {\n declare const hapiScooter: {\n plugin: Plugin\n }\n\n export = hapiScooter\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
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": {
|
|
@@ -68,7 +70,7 @@
|
|
|
68
70
|
},
|
|
69
71
|
"license": "SEE LICENSE IN LICENSE",
|
|
70
72
|
"dependencies": {
|
|
71
|
-
"@defra/forms-model": "^3.0.
|
|
73
|
+
"@defra/forms-model": "^3.0.552",
|
|
72
74
|
"@defra/hapi-tracing": "^1.26.0",
|
|
73
75
|
"@elastic/ecs-pino-format": "^1.5.0",
|
|
74
76
|
"@hapi/boom": "^10.0.1",
|
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
|
|
|
@@ -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) {
|
|
@@ -141,6 +141,21 @@ describe('FormModel', () => {
|
|
|
141
141
|
expect(model.schemaVersion).toBe(SchemaVersion.V1)
|
|
142
142
|
})
|
|
143
143
|
|
|
144
|
+
it('sets versionNumber from options', () => {
|
|
145
|
+
const model = new FormModel(definition, {
|
|
146
|
+
basePath: 'test',
|
|
147
|
+
versionNumber: 42
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
expect(model.versionNumber).toBe(42)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('sets versionNumber to undefined when not provided', () => {
|
|
154
|
+
const model = new FormModel(definition, { basePath: 'test' })
|
|
155
|
+
|
|
156
|
+
expect(model.versionNumber).toBeUndefined()
|
|
157
|
+
})
|
|
158
|
+
|
|
144
159
|
it.each([
|
|
145
160
|
{
|
|
146
161
|
input: undefined,
|
|
@@ -185,7 +200,7 @@ describe('FormModel', () => {
|
|
|
185
200
|
})
|
|
186
201
|
|
|
187
202
|
describe('getFormContext', () => {
|
|
188
|
-
it.each([FormAction.Validate, FormAction.
|
|
203
|
+
it.each([FormAction.Validate, FormAction.SaveAndExit, undefined])(
|
|
189
204
|
'returns a form context with the correct payload and state when action is %s',
|
|
190
205
|
(action) => {
|
|
191
206
|
const formModel = new FormModel(fieldsRequiredDefinition, {
|
|
@@ -219,7 +234,7 @@ describe('FormModel', () => {
|
|
|
219
234
|
}
|
|
220
235
|
)
|
|
221
236
|
|
|
222
|
-
it('returns without updating the state when the action is not validate or
|
|
237
|
+
it('returns without updating the state when the action is not validate or saveAndExit', () => {
|
|
223
238
|
const formModel = new FormModel(fieldsRequiredDefinition, {
|
|
224
239
|
basePath: '/components'
|
|
225
240
|
})
|
|
@@ -329,6 +344,55 @@ describe('FormModel', () => {
|
|
|
329
344
|
)
|
|
330
345
|
})
|
|
331
346
|
|
|
347
|
+
it('includes submittedVersionNumber in context when versionNumber is set', () => {
|
|
348
|
+
const formModel = new FormModel(fieldsRequiredDefinition, {
|
|
349
|
+
basePath: '/components',
|
|
350
|
+
versionNumber: 123
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
const state = {
|
|
354
|
+
$$__referenceNumber: 'foobar'
|
|
355
|
+
}
|
|
356
|
+
const pageUrl = new URL('http://example.com/components/fields-required')
|
|
357
|
+
|
|
358
|
+
const request: FormContextRequest = buildFormContextRequest({
|
|
359
|
+
method: 'get',
|
|
360
|
+
query: {},
|
|
361
|
+
path: pageUrl.pathname,
|
|
362
|
+
params: { path: 'components', slug: 'fields-required' },
|
|
363
|
+
url: pageUrl,
|
|
364
|
+
app: { model: formModel }
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
const context = formModel.getFormContext(request, state)
|
|
368
|
+
|
|
369
|
+
expect(context.submittedVersionNumber).toBe(123)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('sets submittedVersionNumber to undefined when versionNumber is not set', () => {
|
|
373
|
+
const formModel = new FormModel(fieldsRequiredDefinition, {
|
|
374
|
+
basePath: '/components'
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
const state = {
|
|
378
|
+
$$__referenceNumber: 'foobar'
|
|
379
|
+
}
|
|
380
|
+
const pageUrl = new URL('http://example.com/components/fields-required')
|
|
381
|
+
|
|
382
|
+
const request: FormContextRequest = buildFormContextRequest({
|
|
383
|
+
method: 'get',
|
|
384
|
+
query: {},
|
|
385
|
+
path: pageUrl.pathname,
|
|
386
|
+
params: { path: 'components', slug: 'fields-required' },
|
|
387
|
+
url: pageUrl,
|
|
388
|
+
app: { model: formModel }
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
const context = formModel.getFormContext(request, state)
|
|
392
|
+
|
|
393
|
+
expect(context.submittedVersionNumber).toBeUndefined()
|
|
394
|
+
})
|
|
395
|
+
|
|
332
396
|
it('redirects to the page if the list field (radio) is invalidated due to list item conditions', () => {
|
|
333
397
|
const formModel = new FormModel(conditionsListDefinition, {
|
|
334
398
|
basePath: '/conditional-list-items'
|
|
@@ -76,6 +76,7 @@ export class FormModel {
|
|
|
76
76
|
name: string
|
|
77
77
|
values: FormDefinition
|
|
78
78
|
basePath: string
|
|
79
|
+
versionNumber?: number
|
|
79
80
|
conditions: Partial<Record<string, ExecutableCondition>>
|
|
80
81
|
pages: PageControllerClass[]
|
|
81
82
|
services: Services
|
|
@@ -94,7 +95,7 @@ export class FormModel {
|
|
|
94
95
|
|
|
95
96
|
constructor(
|
|
96
97
|
def: typeof this.def,
|
|
97
|
-
options: { basePath: string },
|
|
98
|
+
options: { basePath: string; versionNumber?: number },
|
|
98
99
|
services: Services = defaultServices,
|
|
99
100
|
controllers?: Record<string, typeof PageController>
|
|
100
101
|
) {
|
|
@@ -148,6 +149,7 @@ export class FormModel {
|
|
|
148
149
|
this.name = def.name ?? ''
|
|
149
150
|
this.values = result.value
|
|
150
151
|
this.basePath = options.basePath
|
|
152
|
+
this.versionNumber = options.versionNumber
|
|
151
153
|
this.conditions = {}
|
|
152
154
|
this.services = services
|
|
153
155
|
this.controllers = controllers
|
|
@@ -344,7 +346,8 @@ export class FormModel {
|
|
|
344
346
|
componentDefMap: this.componentDefMap,
|
|
345
347
|
pageMap: this.pageMap,
|
|
346
348
|
componentMap: this.componentMap,
|
|
347
|
-
referenceNumber: getReferenceNumber(state)
|
|
349
|
+
referenceNumber: getReferenceNumber(state),
|
|
350
|
+
submittedVersionNumber: this.versionNumber
|
|
348
351
|
}
|
|
349
352
|
|
|
350
353
|
// Validate current page
|
|
@@ -543,8 +546,7 @@ function validateFormPayload(
|
|
|
543
546
|
// Skip validation GET requests or other actions
|
|
544
547
|
if (
|
|
545
548
|
!request.payload ||
|
|
546
|
-
(action &&
|
|
547
|
-
![FormAction.Validate, FormAction.SaveAndReturn].includes(action))
|
|
549
|
+
(action && ![FormAction.Validate, FormAction.SaveAndExit].includes(action))
|
|
548
550
|
) {
|
|
549
551
|
return context
|
|
550
552
|
}
|
|
@@ -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(
|