@defra/forms-engine-plugin 0.1.11 → 0.1.12
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/.public/javascripts/file-upload.min.js +1 -1
- package/.public/javascripts/file-upload.min.js.map +1 -1
- package/.server/client/javascripts/file-upload.js +45 -4
- package/.server/client/javascripts/file-upload.js.map +1 -1
- package/.server/server/constants.js +2 -0
- package/.server/server/constants.js.map +1 -1
- package/.server/server/index.js +1 -1
- package/.server/server/index.js.map +1 -1
- package/.server/server/plugins/engine/components/AutocompleteField.js +2 -0
- package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
- package/.server/server/plugins/engine/components/CheckboxesField.js +3 -4
- package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
- package/.server/server/plugins/engine/components/ComponentCollection.js +37 -16
- package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
- package/.server/server/plugins/engine/components/DatePartsField.js +36 -2
- package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
- package/.server/server/plugins/engine/components/EmailAddressField.js +19 -3
- package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
- package/.server/server/plugins/engine/components/FileUploadField.js +44 -4
- package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
- package/.server/server/plugins/engine/components/FormComponent.js +14 -2
- package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/ListFormComponent.js +16 -3
- package/.server/server/plugins/engine/components/ListFormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/Markdown.js +24 -0
- package/.server/server/plugins/engine/components/Markdown.js.map +1 -0
- package/.server/server/plugins/engine/components/MonthYearField.js +30 -2
- package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
- package/.server/server/plugins/engine/components/MultilineTextField.js +32 -3
- package/.server/server/plugins/engine/components/MultilineTextField.js.map +1 -1
- package/.server/server/plugins/engine/components/NumberField.js +28 -3
- package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/SelectionControlField.js +14 -0
- package/.server/server/plugins/engine/components/SelectionControlField.js.map +1 -1
- package/.server/server/plugins/engine/components/TelephoneNumberField.js +19 -3
- package/.server/server/plugins/engine/components/TelephoneNumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/TextField.js +22 -3
- package/.server/server/plugins/engine/components/TextField.js.map +1 -1
- package/.server/server/plugins/engine/components/UkAddressField.js +29 -0
- package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
- package/.server/server/plugins/engine/components/YesNoField.js +18 -0
- package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers.js +16 -0
- package/.server/server/plugins/engine/components/helpers.js.map +1 -1
- package/.server/server/plugins/engine/components/index.js +1 -0
- package/.server/server/plugins/engine/components/index.js.map +1 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js +3 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
- package/.server/server/plugins/engine/helpers.js +38 -18
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.js +60 -2
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js +3 -2
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/PageController.js +13 -5
- package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +2 -2
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +19 -5
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/validationOptions.js +6 -11
- package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
- package/.server/server/plugins/engine/plugin.js +5 -4
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/services/notifyService.js +1 -4
- package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
- package/.server/server/plugins/engine/services/uploadService.js +5 -3
- package/.server/server/plugins/engine/services/uploadService.js.map +1 -1
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/components/html.html +1 -1
- package/.server/server/plugins/engine/views/components/markdown.html +5 -0
- package/.server/server/plugins/engine/views/summary.html +7 -1
- package/.server/server/plugins/nunjucks/context.js +6 -5
- package/.server/server/plugins/nunjucks/context.js.map +1 -1
- package/.server/server/plugins/nunjucks/enviroment.test.js +6 -3
- package/.server/server/plugins/nunjucks/enviroment.test.js.map +1 -1
- package/.server/server/utils/type-utils.js +8 -0
- package/.server/server/utils/type-utils.js.map +1 -0
- package/.server/typings/joi/index.d.js.map +1 -1
- package/package.json +3 -3
- package/src/client/javascripts/file-upload.js +60 -4
- package/src/server/constants.js +2 -0
- package/src/server/index.test.ts +34 -29
- package/src/server/index.ts +2 -1
- package/src/server/plugins/engine/components/AutocompleteField.test.ts +71 -3
- package/src/server/plugins/engine/components/AutocompleteField.ts +6 -2
- package/src/server/plugins/engine/components/CheckboxesField.test.ts +40 -8
- package/src/server/plugins/engine/components/CheckboxesField.ts +7 -3
- package/src/server/plugins/engine/components/ComponentCollection.ts +45 -18
- package/src/server/plugins/engine/components/DatePartsField.test.ts +13 -4
- package/src/server/plugins/engine/components/DatePartsField.ts +29 -8
- package/src/server/plugins/engine/components/EmailAddressField.test.ts +51 -1
- package/src/server/plugins/engine/components/EmailAddressField.ts +17 -2
- package/src/server/plugins/engine/components/FileUploadField.test.ts +53 -0
- package/src/server/plugins/engine/components/FileUploadField.ts +52 -3
- package/src/server/plugins/engine/components/FormComponent.ts +24 -2
- package/src/server/plugins/engine/components/ListFormComponent.ts +16 -2
- package/src/server/plugins/engine/components/Markdown.test.ts +48 -0
- package/src/server/plugins/engine/components/Markdown.ts +29 -0
- package/src/server/plugins/engine/components/MonthYearField.test.ts +35 -0
- package/src/server/plugins/engine/components/MonthYearField.ts +34 -9
- package/src/server/plugins/engine/components/MultilineTextField.test.ts +83 -5
- package/src/server/plugins/engine/components/MultilineTextField.ts +37 -2
- package/src/server/plugins/engine/components/NumberField.test.ts +24 -2
- package/src/server/plugins/engine/components/NumberField.ts +23 -3
- package/src/server/plugins/engine/components/RadiosField.test.ts +10 -1
- package/src/server/plugins/engine/components/SelectField.test.ts +2 -1
- package/src/server/plugins/engine/components/SelectionControlField.ts +14 -0
- package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +30 -2
- package/src/server/plugins/engine/components/TelephoneNumberField.ts +17 -2
- package/src/server/plugins/engine/components/TextField.test.ts +33 -1
- package/src/server/plugins/engine/components/TextField.ts +17 -2
- package/src/server/plugins/engine/components/UkAddressField.test.ts +46 -3
- package/src/server/plugins/engine/components/UkAddressField.ts +28 -0
- package/src/server/plugins/engine/components/YesNoField.test.ts +9 -1
- package/src/server/plugins/engine/components/YesNoField.ts +24 -0
- package/src/server/plugins/engine/components/helpers.test.ts +24 -0
- package/src/server/plugins/engine/components/helpers.ts +39 -0
- package/src/server/plugins/engine/components/index.ts +1 -0
- package/src/server/plugins/engine/configureEnginePlugin.ts +13 -3
- package/src/server/plugins/engine/helpers.test.ts +71 -20
- package/src/server/plugins/engine/helpers.ts +46 -19
- package/src/server/plugins/engine/models/FormModel.test.ts +91 -1
- package/src/server/plugins/engine/models/FormModel.ts +86 -3
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +46 -7
- package/src/server/plugins/engine/models/SummaryViewModel.ts +7 -3
- package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +1 -2
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +1 -1
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1 -0
- package/src/server/plugins/engine/pageControllers/PageController.test.ts +9 -6
- package/src/server/plugins/engine/pageControllers/PageController.ts +15 -5
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +2 -2
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +21 -6
- package/src/server/plugins/engine/pageControllers/validationOptions.ts +31 -17
- package/src/server/plugins/engine/plugin.ts +9 -5
- package/src/server/plugins/engine/services/notifyService.ts +1 -2
- package/src/server/plugins/engine/services/uploadService.js +10 -6
- package/src/server/plugins/engine/types.ts +10 -1
- package/src/server/plugins/engine/views/components/html.html +1 -1
- package/src/server/plugins/engine/views/components/markdown.html +5 -0
- package/src/server/plugins/engine/views/summary.html +7 -1
- package/src/server/plugins/nunjucks/context.js +4 -4
- package/src/server/plugins/nunjucks/enviroment.test.js +9 -3
- package/src/server/utils/type-utils.ts +15 -0
- package/src/typings/joi/index.d.ts +8 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ResponseToolkit } from '@hapi/hapi'
|
|
2
2
|
|
|
3
|
+
import { FORM_PREFIX } from '~/src/server/constants.js'
|
|
3
4
|
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
4
5
|
import { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
|
|
5
6
|
import { type FormRequest } from '~/src/server/routes/types.js'
|
|
@@ -10,6 +11,8 @@ describe('PageController', () => {
|
|
|
10
11
|
let controller1: PageController
|
|
11
12
|
let controller2: PageController
|
|
12
13
|
|
|
14
|
+
const testBasePath = `${FORM_PREFIX}/test`
|
|
15
|
+
|
|
13
16
|
beforeEach(() => {
|
|
14
17
|
const { pages } = definition
|
|
15
18
|
|
|
@@ -17,7 +20,7 @@ describe('PageController', () => {
|
|
|
17
20
|
const page2 = pages[1]
|
|
18
21
|
|
|
19
22
|
model = new FormModel(definition, {
|
|
20
|
-
basePath:
|
|
23
|
+
basePath: testBasePath
|
|
21
24
|
})
|
|
22
25
|
|
|
23
26
|
controller1 = new PageController(model, page1)
|
|
@@ -31,8 +34,8 @@ describe('PageController', () => {
|
|
|
31
34
|
})
|
|
32
35
|
|
|
33
36
|
it('returns href', () => {
|
|
34
|
-
expect(controller1).toHaveProperty('href',
|
|
35
|
-
expect(controller2).toHaveProperty('href',
|
|
37
|
+
expect(controller1).toHaveProperty('href', `${testBasePath}/licence`)
|
|
38
|
+
expect(controller2).toHaveProperty('href', `${testBasePath}/full-name`)
|
|
36
39
|
})
|
|
37
40
|
|
|
38
41
|
it('returns keys (empty)', () => {
|
|
@@ -99,11 +102,11 @@ describe('PageController', () => {
|
|
|
99
102
|
describe('Path methods', () => {
|
|
100
103
|
describe('Link href', () => {
|
|
101
104
|
it('prefixes paths into link hrefs', () => {
|
|
102
|
-
const href1 = controller1.getHref('
|
|
105
|
+
const href1 = controller1.getHref('')
|
|
103
106
|
const href2 = controller1.getHref('/page-one')
|
|
104
107
|
|
|
105
|
-
expect(href1).toBe(
|
|
106
|
-
expect(href2).toBe(
|
|
108
|
+
expect(href1).toBe(testBasePath)
|
|
109
|
+
expect(href2).toBe(`${testBasePath}/page-one`)
|
|
107
110
|
})
|
|
108
111
|
})
|
|
109
112
|
|
|
@@ -135,12 +135,22 @@ export class PageController {
|
|
|
135
135
|
return def.phaseBanner?.phase
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
getHref(path: string) {
|
|
139
|
-
const
|
|
138
|
+
getHref(path: string): string {
|
|
139
|
+
const basePath = this.model.basePath
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
if (path === '/') {
|
|
142
|
+
return `/${basePath}`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// if ever the path is not prefixed with a slash, add it
|
|
146
|
+
const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path
|
|
147
|
+
let finalPath = `/${basePath}`
|
|
148
|
+
if (relativeTargetPath) {
|
|
149
|
+
finalPath += `/${relativeTargetPath}`
|
|
150
|
+
}
|
|
151
|
+
finalPath = finalPath.replace(/\/{2,}/g, '/')
|
|
152
|
+
|
|
153
|
+
return finalPath
|
|
144
154
|
}
|
|
145
155
|
|
|
146
156
|
getStartPath() {
|
|
@@ -398,7 +398,7 @@ export class QuestionPageController extends PageController {
|
|
|
398
398
|
const { evaluationState } = context
|
|
399
399
|
|
|
400
400
|
const viewModel = this.getViewModel(request, context)
|
|
401
|
-
viewModel.errors = collection.
|
|
401
|
+
viewModel.errors = collection.getViewErrors(viewModel.errors)
|
|
402
402
|
|
|
403
403
|
/**
|
|
404
404
|
* Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it
|
|
@@ -498,7 +498,7 @@ export class QuestionPageController extends PageController {
|
|
|
498
498
|
*/
|
|
499
499
|
if (context.errors || isForceAccess) {
|
|
500
500
|
const viewModel = this.getViewModel(request, context)
|
|
501
|
-
viewModel.errors = collection.
|
|
501
|
+
viewModel.errors = collection.getViewErrors(viewModel.errors)
|
|
502
502
|
|
|
503
503
|
// Filter our components based on their conditions using our evaluated state
|
|
504
504
|
viewModel.components = this.filterConditionalComponents(
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
hasComponentsEvenIfNoNext,
|
|
3
|
+
type Page,
|
|
4
|
+
type SubmitPayload
|
|
5
|
+
} from '@defra/forms-model'
|
|
2
6
|
import Boom from '@hapi/boom'
|
|
3
7
|
import { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'
|
|
4
8
|
|
|
9
|
+
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
5
10
|
import { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'
|
|
6
11
|
import { getAnswer } from '~/src/server/plugins/engine/components/helpers.js'
|
|
7
12
|
import {
|
|
@@ -30,15 +35,21 @@ import {
|
|
|
30
35
|
} from '~/src/server/routes/types.js'
|
|
31
36
|
|
|
32
37
|
export class SummaryPageController extends QuestionPageController {
|
|
33
|
-
declare pageDef:
|
|
38
|
+
declare pageDef: Page
|
|
34
39
|
|
|
35
40
|
/**
|
|
36
41
|
* The controller which is used when Page["controller"] is defined as "./pages/summary.js"
|
|
37
42
|
*/
|
|
38
43
|
|
|
39
|
-
constructor(model: FormModel, pageDef:
|
|
44
|
+
constructor(model: FormModel, pageDef: Page) {
|
|
40
45
|
super(model, pageDef)
|
|
41
46
|
this.viewName = 'summary'
|
|
47
|
+
|
|
48
|
+
// Components collection
|
|
49
|
+
this.collection = new ComponentCollection(
|
|
50
|
+
hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],
|
|
51
|
+
{ model, page: this }
|
|
52
|
+
)
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
getSummaryViewModel(
|
|
@@ -47,11 +58,16 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
47
58
|
): SummaryViewModel {
|
|
48
59
|
const viewModel = new SummaryViewModel(request, this, context)
|
|
49
60
|
|
|
61
|
+
const { query } = request
|
|
62
|
+
const { payload, errors } = context
|
|
63
|
+
const components = this.collection.getViewModel(payload, errors, query)
|
|
64
|
+
|
|
50
65
|
// We already figure these out in the base page controller. Take them and apply them to our page-specific model.
|
|
51
66
|
// This is a stop-gap until we can add proper inheritance in place.
|
|
52
67
|
viewModel.backLink = this.getBackLink(request, context)
|
|
53
68
|
viewModel.feedbackLink = this.feedbackLink
|
|
54
69
|
viewModel.phaseTag = this.phaseTag
|
|
70
|
+
viewModel.components = components
|
|
55
71
|
|
|
56
72
|
return viewModel
|
|
57
73
|
}
|
|
@@ -96,7 +112,7 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
96
112
|
|
|
97
113
|
// Get the form metadata using the `slug` param
|
|
98
114
|
const { notificationEmail } = await getFormMetadata(params.slug)
|
|
99
|
-
const { isPreview } = checkFormStatus(request.
|
|
115
|
+
const { isPreview } = checkFormStatus(request.params)
|
|
100
116
|
const emailAddress = notificationEmail ?? this.model.def.outputEmail
|
|
101
117
|
|
|
102
118
|
checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)
|
|
@@ -138,8 +154,7 @@ async function submitForm(
|
|
|
138
154
|
) {
|
|
139
155
|
await extendFileRetention(model, state, emailAddress)
|
|
140
156
|
|
|
141
|
-
const
|
|
142
|
-
const formStatus = checkFormStatus(path)
|
|
157
|
+
const formStatus = checkFormStatus(request.params)
|
|
143
158
|
const logTags = ['submit', 'submissionApi']
|
|
144
159
|
|
|
145
160
|
request.logger.info(logTags, 'Preparing email', formStatus)
|
|
@@ -1,32 +1,45 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
2
|
// Declaration above is needed for: https://github.com/hapijs/joi/issues/3064
|
|
3
3
|
|
|
4
|
-
import joi, {
|
|
4
|
+
import joi, {
|
|
5
|
+
type JoiExpression,
|
|
6
|
+
type LanguageMessages,
|
|
7
|
+
type LanguageMessagesExt,
|
|
8
|
+
type ReferenceOptions,
|
|
9
|
+
type ValidationOptions
|
|
10
|
+
} from 'joi'
|
|
5
11
|
import lowerFirst from 'lodash/lowerFirst.js'
|
|
6
12
|
|
|
7
13
|
const opts = {
|
|
8
14
|
functions: {
|
|
9
15
|
lowerFirst
|
|
10
16
|
}
|
|
11
|
-
}
|
|
17
|
+
} as ReferenceOptions
|
|
12
18
|
|
|
13
19
|
/**
|
|
14
20
|
* see @link https://joi.dev/api/?v=17.4.2#template-syntax for template syntax
|
|
15
21
|
*/
|
|
16
|
-
export const messageTemplate = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
export const messageTemplate: Record<string, JoiExpression> = {
|
|
23
|
+
required: joi.expression(
|
|
24
|
+
'Enter {{lowerFirst(#label)}}',
|
|
25
|
+
opts
|
|
26
|
+
) as JoiExpression,
|
|
27
|
+
selectRequired: joi.expression(
|
|
28
|
+
'Select {{lowerFirst(#label)}}',
|
|
29
|
+
opts
|
|
30
|
+
) as JoiExpression,
|
|
31
|
+
selectYesNoRequired: '{{#label}} - select yes or no',
|
|
21
32
|
max: '{{#label}} must be {{#limit}} characters or less',
|
|
22
33
|
min: '{{#label}} must be {{#limit}} characters or more',
|
|
23
|
-
|
|
24
|
-
pattern: joi.expression(
|
|
34
|
+
minMax: '{{#label}} must be between {{#min}} and {{#max}} characters',
|
|
35
|
+
pattern: joi.expression(
|
|
36
|
+
'Enter a valid {{lowerFirst(#label)}}',
|
|
37
|
+
opts
|
|
38
|
+
) as JoiExpression,
|
|
25
39
|
format: joi.expression(
|
|
26
40
|
'Enter {{lowerFirst(#label)}} in the correct format',
|
|
27
|
-
// @ts-expect-error - joi.expression options type issue
|
|
28
41
|
opts
|
|
29
|
-
),
|
|
42
|
+
) as JoiExpression,
|
|
30
43
|
number: '{{#label}} must be a number',
|
|
31
44
|
numberPrecision: '{{#label}} must have {{#limit}} or fewer decimal places',
|
|
32
45
|
numberInteger: '{{#label}} must be a whole number',
|
|
@@ -36,19 +49,17 @@ export const messageTemplate = {
|
|
|
36
49
|
|
|
37
50
|
// Nested fields use component title
|
|
38
51
|
|
|
39
|
-
|
|
40
|
-
objectRequired: joi.expression('Enter {{#label}}', opts),
|
|
52
|
+
objectRequired: joi.expression('Enter {{#label}}', opts) as JoiExpression,
|
|
41
53
|
objectMissing: joi.expression(
|
|
42
54
|
'{{#title}} must include a {{lowerFirst(#label)}}',
|
|
43
|
-
// @ts-expect-error - joi.expression options type issue
|
|
44
55
|
opts
|
|
45
|
-
),
|
|
56
|
+
) as JoiExpression,
|
|
46
57
|
dateFormat: '{{#title}} must be a real date',
|
|
47
58
|
dateMin: '{{#title}} must be the same as or after {{#limit}}',
|
|
48
59
|
dateMax: '{{#title}} must be the same as or before {{#limit}}'
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
export const messages:
|
|
62
|
+
export const messages: LanguageMessagesExt = {
|
|
52
63
|
'string.base': messageTemplate.required,
|
|
53
64
|
'string.min': messageTemplate.min,
|
|
54
65
|
'string.empty': messageTemplate.required,
|
|
@@ -77,9 +88,12 @@ export const messages: LanguageMessages = {
|
|
|
77
88
|
'date.max': messageTemplate.dateMax
|
|
78
89
|
}
|
|
79
90
|
|
|
91
|
+
export const messagesPre: LanguageMessages =
|
|
92
|
+
messages as unknown as LanguageMessages
|
|
93
|
+
|
|
80
94
|
export const validationOptions: ValidationOptions = {
|
|
81
95
|
abortEarly: false,
|
|
82
|
-
messages,
|
|
96
|
+
messages: messagesPre,
|
|
83
97
|
errors: {
|
|
84
98
|
wrap: {
|
|
85
99
|
array: false,
|
|
@@ -105,6 +105,8 @@ export const plugin = {
|
|
|
105
105
|
dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],
|
|
106
106
|
multiple: true,
|
|
107
107
|
async register(server: Server, options: PluginOptions) {
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong
|
|
109
|
+
const prefix = server.realm.modifiers.route.prefix ?? ''
|
|
108
110
|
const {
|
|
109
111
|
model,
|
|
110
112
|
services = defaultServices,
|
|
@@ -193,9 +195,9 @@ export const plugin = {
|
|
|
193
195
|
return h.continue
|
|
194
196
|
}
|
|
195
197
|
|
|
196
|
-
const { params
|
|
198
|
+
const { params } = request
|
|
197
199
|
const { slug } = params
|
|
198
|
-
const { isPreview, state: formState } = checkFormStatus(
|
|
200
|
+
const { isPreview, state: formState } = checkFormStatus(params)
|
|
199
201
|
|
|
200
202
|
// Get the form metadata using the `slug` param
|
|
201
203
|
const metadata = await formsService.getFormMetadata(slug)
|
|
@@ -241,9 +243,11 @@ export const plugin = {
|
|
|
241
243
|
)
|
|
242
244
|
|
|
243
245
|
// Set up the basePath for the model
|
|
244
|
-
const basePath =
|
|
245
|
-
|
|
246
|
-
|
|
246
|
+
const basePath = (
|
|
247
|
+
isPreview
|
|
248
|
+
? `${prefix}${PREVIEW_PATH_PREFIX}/${formState}/${slug}`
|
|
249
|
+
: `${prefix}/${slug}`
|
|
250
|
+
).substring(1)
|
|
247
251
|
|
|
248
252
|
// Construct the form model
|
|
249
253
|
const model = new FormModel(
|
|
@@ -19,8 +19,7 @@ export async function submit(
|
|
|
19
19
|
submitResponse: SubmitResponsePayload
|
|
20
20
|
) {
|
|
21
21
|
const logTags = ['submit', 'email']
|
|
22
|
-
const
|
|
23
|
-
const formStatus = checkFormStatus(path)
|
|
22
|
+
const formStatus = checkFormStatus(request.params)
|
|
24
23
|
|
|
25
24
|
// Get submission email personalisation
|
|
26
25
|
request.logger.info(logTags, 'Getting personalisation data')
|
|
@@ -10,12 +10,19 @@ const stagingPrefix = config.get('stagingPrefix')
|
|
|
10
10
|
* Initiates a CDP file upload
|
|
11
11
|
* @param {string} path - the path of the page in the form
|
|
12
12
|
* @param {string} retrievalKey - the retrieval key for the files
|
|
13
|
-
* @param {string} [
|
|
13
|
+
* @param {string} [mimeTypesCsv] - the csv string of accepted mimeTypes
|
|
14
14
|
*/
|
|
15
|
-
export async function initiateUpload(path, retrievalKey,
|
|
15
|
+
export async function initiateUpload(path, retrievalKey, mimeTypesCsv) {
|
|
16
16
|
const postJsonByType =
|
|
17
17
|
/** @type {typeof postJson<UploadInitiateResponse>} */ (postJson)
|
|
18
18
|
|
|
19
|
+
const mimeTypesList = mimeTypesCsv
|
|
20
|
+
?.split(',')
|
|
21
|
+
.map((type) => type.trim())
|
|
22
|
+
.filter((type) => type !== '')
|
|
23
|
+
|
|
24
|
+
const mimeTypes = mimeTypesList?.length ? mimeTypesList : undefined
|
|
25
|
+
|
|
19
26
|
const payload = {
|
|
20
27
|
redirect: path,
|
|
21
28
|
callback: `${submissionUrl}/file`,
|
|
@@ -24,10 +31,7 @@ export async function initiateUpload(path, retrievalKey, mimeTypes) {
|
|
|
24
31
|
metadata: {
|
|
25
32
|
retrievalKey
|
|
26
33
|
},
|
|
27
|
-
mimeTypes
|
|
28
|
-
?.split(',')
|
|
29
|
-
.map((type) => type.trim())
|
|
30
|
-
.filter((type) => type !== '')
|
|
34
|
+
mimeTypes
|
|
31
35
|
// maxFileSize: 25 * 1000 * 1000
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
type List,
|
|
5
5
|
type Page
|
|
6
6
|
} from '@defra/forms-model'
|
|
7
|
-
import { type ValidationErrorItem } from 'joi'
|
|
7
|
+
import { type JoiExpression, type ValidationErrorItem } from 'joi'
|
|
8
8
|
|
|
9
9
|
import { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'
|
|
10
10
|
import { type Component } from '~/src/server/plugins/engine/components/helpers.js'
|
|
@@ -316,3 +316,12 @@ export type PageViewModel =
|
|
|
316
316
|
| FeaturedFormPageViewModel
|
|
317
317
|
|
|
318
318
|
export type FilterFunction = (value: unknown) => unknown
|
|
319
|
+
export interface ErrorMessageTemplate {
|
|
320
|
+
type: string
|
|
321
|
+
template: JoiExpression
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export interface ErrorMessageTemplateList {
|
|
325
|
+
baseErrors: ErrorMessageTemplate[]
|
|
326
|
+
advancedSettingsErrors: ErrorMessageTemplate[]
|
|
327
|
+
}
|
|
@@ -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 "partials/components.html" import componentList with context %}
|
|
5
6
|
|
|
6
7
|
{% block content %}
|
|
7
8
|
<div class="govuk-grid-row">
|
|
@@ -34,11 +35,16 @@
|
|
|
34
35
|
|
|
35
36
|
{% if declaration %}
|
|
36
37
|
<h2 class="govuk-heading-m" id="declaration">Declaration</h2>
|
|
38
|
+
<div class="govuk-body">
|
|
37
39
|
{{ declaration | safe }}
|
|
40
|
+
</div>
|
|
38
41
|
{% endif %}
|
|
39
42
|
|
|
43
|
+
{{ componentList(components) }}
|
|
44
|
+
|
|
45
|
+
{% set isDeclaration = declaration or components | length %}
|
|
40
46
|
<button data-prevent-double-click="true" class="govuk-button" data-module="govuk-button">
|
|
41
|
-
{{ "Accept and send" if
|
|
47
|
+
{{ "Accept and send" if isDeclaration else "Send" }}
|
|
42
48
|
</button>
|
|
43
49
|
</form>
|
|
44
50
|
</div>
|
|
@@ -7,8 +7,8 @@ import { StatusCodes } from 'http-status-codes'
|
|
|
7
7
|
import pkg from '~/package.json' with { type: 'json' }
|
|
8
8
|
import { config } from '~/src/config/index.js'
|
|
9
9
|
import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
|
|
10
|
-
import { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'
|
|
11
10
|
import {
|
|
11
|
+
checkFormStatus,
|
|
12
12
|
encodeUrl,
|
|
13
13
|
safeGenerateCrumb
|
|
14
14
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
@@ -22,9 +22,9 @@ let webpackManifest
|
|
|
22
22
|
* @param {FormRequest | FormRequestPayload | null} request
|
|
23
23
|
*/
|
|
24
24
|
export function context(request) {
|
|
25
|
-
const { params,
|
|
25
|
+
const { params, response } = request ?? {}
|
|
26
26
|
|
|
27
|
-
const isPreviewMode =
|
|
27
|
+
const { isPreview: isPreviewMode, state: formState } = checkFormStatus(params)
|
|
28
28
|
|
|
29
29
|
// Only add the slug in to the context if the response is OK.
|
|
30
30
|
// Footer meta links are not rendered when the slug is missing.
|
|
@@ -62,7 +62,7 @@ export function context(request) {
|
|
|
62
62
|
},
|
|
63
63
|
crumb: safeGenerateCrumb(request),
|
|
64
64
|
currentPath: `${request.path}${request.url.search}`,
|
|
65
|
-
previewMode: isPreviewMode ?
|
|
65
|
+
previewMode: isPreviewMode ? formState : undefined,
|
|
66
66
|
slug: isResponseOK ? params?.slug : undefined
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -88,7 +88,9 @@ describe('Nunjucks environment', () => {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
const result =
|
|
91
|
+
const result = /** @type {{ model: { content: string } }} */ (
|
|
92
|
+
checkComponentTemplates.call(nunjucksCtx, component)
|
|
93
|
+
)
|
|
92
94
|
|
|
93
95
|
expect(helpers.evaluateTemplate).toHaveBeenCalledWith(
|
|
94
96
|
'Some {{ context.someData }} content',
|
|
@@ -114,7 +116,9 @@ describe('Nunjucks environment', () => {
|
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
const result =
|
|
119
|
+
const result = /** @type {{ model: { content: string } }} */ (
|
|
120
|
+
checkComponentTemplates.call(nunjucksCtx, component)
|
|
121
|
+
)
|
|
118
122
|
|
|
119
123
|
expect(helpers.evaluateTemplate).not.toHaveBeenCalled()
|
|
120
124
|
|
|
@@ -136,7 +140,9 @@ describe('Nunjucks environment', () => {
|
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
|
|
139
|
-
const result =
|
|
143
|
+
const result = /** @type {{ model: { label?: { text: string } } }} */ (
|
|
144
|
+
checkComponentTemplates.call(nunjucksCtx, component)
|
|
145
|
+
)
|
|
140
146
|
|
|
141
147
|
expect(helpers.evaluateTemplate).toHaveBeenCalledWith(
|
|
142
148
|
'Label with {{ context.someData }}',
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Joi, {
|
|
2
|
+
type JoiExpression,
|
|
3
|
+
type LanguageMessages,
|
|
4
|
+
type LanguageMessagesExt
|
|
5
|
+
} from 'joi'
|
|
6
|
+
|
|
7
|
+
export function convertToLanguageMessages(
|
|
8
|
+
extLanguageMessages: LanguageMessagesExt
|
|
9
|
+
): LanguageMessages {
|
|
10
|
+
return extLanguageMessages as unknown as LanguageMessages
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createJoiExpression(expr: string): JoiExpression {
|
|
14
|
+
return Joi.expression(expr) as unknown as JoiExpression
|
|
15
|
+
}
|
|
@@ -19,4 +19,12 @@ declare module 'joi' {
|
|
|
19
19
|
title?: string
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
interface JoiExpressionReturn {
|
|
24
|
+
render: (p1, p2, p3, p4, p5) => string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type JoiExpression = JoiExpressionReturn | string
|
|
28
|
+
|
|
29
|
+
type LanguageMessagesExt = Record<string, JoiExpression>
|
|
22
30
|
}
|