@defra/forms-engine-plugin 0.0.4 → 0.0.5
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/package.json +3 -2
- package/src/client/javascripts/application.js +87 -0
- package/src/client/javascripts/file-upload.js +386 -0
- package/src/client/stylesheets/_code.scss +33 -0
- package/src/client/stylesheets/_govuk-frontend.scss +4 -0
- package/src/client/stylesheets/_prose.scss +56 -0
- package/src/client/stylesheets/_service-banner.scss +24 -0
- package/src/client/stylesheets/_summary-list.scss +28 -0
- package/src/client/stylesheets/_tag-env.scss +24 -0
- package/src/client/stylesheets/application.scss +14 -0
- package/src/common/cookies.js +58 -0
- package/src/common/cookies.test.js +23 -0
- package/src/common/types.js +5 -0
- package/src/config/index.ts +271 -0
- package/src/index.ts +31 -0
- package/src/server/common/helpers/logging/logger-options.test.ts +50 -0
- package/src/server/common/helpers/logging/logger-options.ts +46 -0
- package/src/server/common/helpers/logging/logger.ts +7 -0
- package/src/server/common/helpers/logging/request-logger.ts +9 -0
- package/src/server/common/helpers/logging/request-tracing.js +10 -0
- package/src/server/common/helpers/redis-client.js +70 -0
- package/src/server/constants.js +1 -0
- package/src/server/forms/README.md +10 -0
- package/src/server/forms/components.json +1015 -0
- package/src/server/forms/report-a-terrorist.json +270 -0
- package/src/server/forms/runner-components-test.json +365 -0
- package/src/server/forms/test.json +581 -0
- package/src/server/index.test.ts +582 -0
- package/src/server/index.ts +140 -0
- package/src/server/plugins/blankie.test.ts +73 -0
- package/src/server/plugins/blankie.ts +48 -0
- package/src/server/plugins/crumb.ts +20 -0
- package/src/server/plugins/engine/README.md +87 -0
- package/src/server/plugins/engine/components/AutocompleteField.test.ts +294 -0
- package/src/server/plugins/engine/components/AutocompleteField.ts +49 -0
- package/src/server/plugins/engine/components/CheckboxesField.test.ts +379 -0
- package/src/server/plugins/engine/components/CheckboxesField.ts +106 -0
- package/src/server/plugins/engine/components/ComponentBase.ts +97 -0
- package/src/server/plugins/engine/components/ComponentCollection.ts +278 -0
- package/src/server/plugins/engine/components/DatePartsField.test.ts +822 -0
- package/src/server/plugins/engine/components/DatePartsField.ts +264 -0
- package/src/server/plugins/engine/components/Details.test.ts +49 -0
- package/src/server/plugins/engine/components/Details.ts +30 -0
- package/src/server/plugins/engine/components/EmailAddressField.test.ts +395 -0
- package/src/server/plugins/engine/components/EmailAddressField.ts +55 -0
- package/src/server/plugins/engine/components/FileUploadField.test.ts +778 -0
- package/src/server/plugins/engine/components/FileUploadField.ts +262 -0
- package/src/server/plugins/engine/components/FormComponent.ts +249 -0
- package/src/server/plugins/engine/components/Html.test.ts +48 -0
- package/src/server/plugins/engine/components/Html.ts +29 -0
- package/src/server/plugins/engine/components/InsetText.test.ts +48 -0
- package/src/server/plugins/engine/components/InsetText.ts +27 -0
- package/src/server/plugins/engine/components/List.test.ts +76 -0
- package/src/server/plugins/engine/components/List.ts +72 -0
- package/src/server/plugins/engine/components/ListFormComponent.ts +140 -0
- package/src/server/plugins/engine/components/MonthYearField.test.ts +567 -0
- package/src/server/plugins/engine/components/MonthYearField.ts +222 -0
- package/src/server/plugins/engine/components/MultilineTextField.test.ts +558 -0
- package/src/server/plugins/engine/components/MultilineTextField.ts +138 -0
- package/src/server/plugins/engine/components/NumberField.test.ts +701 -0
- package/src/server/plugins/engine/components/NumberField.ts +163 -0
- package/src/server/plugins/engine/components/RadiosField.test.ts +288 -0
- package/src/server/plugins/engine/components/RadiosField.ts +24 -0
- package/src/server/plugins/engine/components/SelectField.test.ts +288 -0
- package/src/server/plugins/engine/components/SelectField.ts +47 -0
- package/src/server/plugins/engine/components/SelectionControlField.ts +43 -0
- package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +356 -0
- package/src/server/plugins/engine/components/TelephoneNumberField.ts +67 -0
- package/src/server/plugins/engine/components/TextField.test.ts +489 -0
- package/src/server/plugins/engine/components/TextField.ts +96 -0
- package/src/server/plugins/engine/components/UkAddressField.test.ts +623 -0
- package/src/server/plugins/engine/components/UkAddressField.ts +172 -0
- package/src/server/plugins/engine/components/YesNoField.test.ts +248 -0
- package/src/server/plugins/engine/components/YesNoField.ts +31 -0
- package/src/server/plugins/engine/components/constants.ts +1 -0
- package/src/server/plugins/engine/components/helpers.ts +330 -0
- package/src/server/plugins/engine/components/index.ts +24 -0
- package/src/server/plugins/engine/components/types.ts +117 -0
- package/src/server/plugins/engine/configureEnginePlugin.ts +47 -0
- package/src/server/plugins/engine/helpers.test.ts +791 -0
- package/src/server/plugins/engine/helpers.ts +379 -0
- package/src/server/plugins/engine/index.ts +7 -0
- package/src/server/plugins/engine/models/FormModel.test.ts +42 -0
- package/src/server/plugins/engine/models/FormModel.ts +443 -0
- package/src/server/plugins/engine/models/RepeatingSummaryViewModel.ts +0 -0
- package/src/server/plugins/engine/models/Section.ts +0 -0
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +209 -0
- package/src/server/plugins/engine/models/SummaryViewModel.ts +220 -0
- package/src/server/plugins/engine/models/index.ts +2 -0
- package/src/server/plugins/engine/models/types.ts +114 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +143 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +73 -0
- package/src/server/plugins/engine/outputFormatters/index.test.ts +17 -0
- package/src/server/plugins/engine/outputFormatters/index.ts +44 -0
- package/src/server/plugins/engine/outputFormatters/machine/v1.test.ts +229 -0
- package/src/server/plugins/engine/outputFormatters/machine/v1.ts +140 -0
- package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +229 -0
- package/src/server/plugins/engine/outputFormatters/machine/v2.ts +153 -0
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1108 -0
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +446 -0
- package/src/server/plugins/engine/pageControllers/PageController.test.ts +205 -0
- package/src/server/plugins/engine/pageControllers/PageController.ts +176 -0
- package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +1264 -0
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +561 -0
- package/src/server/plugins/engine/pageControllers/README.md +28 -0
- package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +264 -0
- package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +458 -0
- package/src/server/plugins/engine/pageControllers/StartPageController.ts +18 -0
- package/src/server/plugins/engine/pageControllers/StatusPageController.ts +50 -0
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +261 -0
- package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +28 -0
- package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +19 -0
- package/src/server/plugins/engine/pageControllers/helpers.test.ts +198 -0
- package/src/server/plugins/engine/pageControllers/helpers.ts +101 -0
- package/src/server/plugins/engine/pageControllers/index.ts +10 -0
- package/src/server/plugins/engine/pageControllers/validationOptions.ts +89 -0
- package/src/server/plugins/engine/plugin.ts +673 -0
- package/src/server/plugins/engine/services/formSubmissionService.js +46 -0
- package/src/server/plugins/engine/services/formsService.js +46 -0
- package/src/server/plugins/engine/services/formsService.test.js +90 -0
- package/src/server/plugins/engine/services/index.js +3 -0
- package/src/server/plugins/engine/services/notifyService.test.ts +132 -0
- package/src/server/plugins/engine/services/notifyService.ts +64 -0
- package/src/server/plugins/engine/services/uploadService.js +60 -0
- package/src/server/plugins/engine/types.ts +315 -0
- package/src/server/plugins/engine/views/components/autocompletefield.html +5 -0
- package/src/server/plugins/engine/views/components/checkboxesfield.html +5 -0
- package/src/server/plugins/engine/views/components/datepartsfield.html +5 -0
- package/src/server/plugins/engine/views/components/details.html +6 -0
- package/src/server/plugins/engine/views/components/emailaddressfield.html +5 -0
- package/src/server/plugins/engine/views/components/fileuploadfield-key.html +8 -0
- package/src/server/plugins/engine/views/components/fileuploadfield-value.html +3 -0
- package/src/server/plugins/engine/views/components/fileuploadfield.html +24 -0
- package/src/server/plugins/engine/views/components/html.html +3 -0
- package/src/server/plugins/engine/views/components/insettext.html +7 -0
- package/src/server/plugins/engine/views/components/list.html +36 -0
- package/src/server/plugins/engine/views/components/monthyearfield.html +5 -0
- package/src/server/plugins/engine/views/components/multilinetextfield.html +10 -0
- package/src/server/plugins/engine/views/components/numberfield.html +5 -0
- package/src/server/plugins/engine/views/components/radiosfield.html +5 -0
- package/src/server/plugins/engine/views/components/selectfield.html +5 -0
- package/src/server/plugins/engine/views/components/telephonenumberfield.html +5 -0
- package/src/server/plugins/engine/views/components/textfield.html +5 -0
- package/src/server/plugins/engine/views/components/ukaddressfield.html +25 -0
- package/src/server/plugins/engine/views/components/yesnofield.html +5 -0
- package/src/server/plugins/engine/views/file-upload.html +45 -0
- package/src/server/plugins/engine/views/index.html +39 -0
- package/src/server/plugins/engine/views/item-delete.html +56 -0
- package/src/server/plugins/engine/views/partials/components.html +6 -0
- package/src/server/plugins/engine/views/partials/conditional-components.html +3 -0
- package/src/server/plugins/engine/views/partials/debug.html +44 -0
- package/src/server/plugins/engine/views/partials/form.html +15 -0
- package/src/server/plugins/engine/views/partials/heading.html +16 -0
- package/src/server/plugins/engine/views/partials/preview-banner.html +32 -0
- package/src/server/plugins/engine/views/partials/preview-banner.test.js +122 -0
- package/src/server/plugins/engine/views/partials/warn-missing-notification-email.html +10 -0
- package/src/server/plugins/engine/views/repeat-list-summary.html +53 -0
- package/src/server/plugins/errorPages.ts +58 -0
- package/src/server/plugins/nunjucks/context.js +88 -0
- package/src/server/plugins/nunjucks/context.test.js +142 -0
- package/src/server/plugins/nunjucks/enviroment.test.js +201 -0
- package/src/server/plugins/nunjucks/environment.js +116 -0
- package/src/server/plugins/nunjucks/filters/answer.js +27 -0
- package/src/server/plugins/nunjucks/filters/answer.test.js +89 -0
- package/src/server/plugins/nunjucks/filters/evaluate.js +21 -0
- package/src/server/plugins/nunjucks/filters/field.js +28 -0
- package/src/server/plugins/nunjucks/filters/field.test.js +75 -0
- package/src/server/plugins/nunjucks/filters/highlight.js +11 -0
- package/src/server/plugins/nunjucks/filters/href.js +30 -0
- package/src/server/plugins/nunjucks/filters/href.test.js +80 -0
- package/src/server/plugins/nunjucks/filters/index.js +8 -0
- package/src/server/plugins/nunjucks/filters/inspect.js +15 -0
- package/src/server/plugins/nunjucks/filters/page.js +24 -0
- package/src/server/plugins/nunjucks/filters/page.test.js +65 -0
- package/src/server/plugins/nunjucks/index.js +3 -0
- package/src/server/plugins/nunjucks/plugin.js +40 -0
- package/src/server/plugins/nunjucks/render.js +42 -0
- package/src/server/plugins/nunjucks/types.js +40 -0
- package/src/server/plugins/pulse.ts +11 -0
- package/src/server/plugins/router.ts +201 -0
- package/src/server/plugins/session.ts +28 -0
- package/src/server/routes/health.js +13 -0
- package/src/server/routes/health.test.js +35 -0
- package/src/server/routes/index.test.ts +125 -0
- package/src/server/routes/index.ts +2 -0
- package/src/server/routes/public.ts +47 -0
- package/src/server/routes/types.ts +48 -0
- package/src/server/schemas/index.ts +34 -0
- package/src/server/secure-context.js +43 -0
- package/src/server/services/cacheService.test.ts +276 -0
- package/src/server/services/cacheService.ts +131 -0
- package/src/server/services/httpService.test.js +491 -0
- package/src/server/services/httpService.ts +50 -0
- package/src/server/services/index.ts +1 -0
- package/src/server/types.ts +54 -0
- package/src/server/utils/notify.test.ts +37 -0
- package/src/server/utils/notify.ts +50 -0
- package/src/server/utils/secure-context/get-trust-store-certs.js +11 -0
- package/src/server/utils/secure-context/get-trust-store-certs.test.js +19 -0
- package/src/server/utils/utils.js +24 -0
- package/src/server/utils/utils.test.js +54 -0
- package/src/server/views/404.html +16 -0
- package/src/server/views/500.html +19 -0
- package/src/server/views/components/debug/macro.njk +3 -0
- package/src/server/views/components/debug/template.njk +13 -0
- package/src/server/views/components/service-banner/macro.njk +3 -0
- package/src/server/views/components/service-banner/template.njk +20 -0
- package/src/server/views/components/service-banner/template.test.js +43 -0
- package/src/server/views/components/tag-env/macro.njk +3 -0
- package/src/server/views/components/tag-env/template.njk +30 -0
- package/src/server/views/components/tag-env/template.test.js +66 -0
- package/src/server/views/confirmation.html +19 -0
- package/src/server/views/help/accessibility-statement.html +58 -0
- package/src/server/views/help/cookie-preferences.html +57 -0
- package/src/server/views/help/cookies.html +71 -0
- package/src/server/views/help/get-support.html +37 -0
- package/src/server/views/help/privacy-notice.html +68 -0
- package/src/server/views/help/terms-and-conditions.html +83 -0
- package/src/server/views/layout.html +199 -0
- package/src/server/views/summary.html +50 -0
- package/src/typings/hapi/index.d.ts +95 -0
- package/src/typings/hapi-tracing/index.d.ts +6 -0
- package/src/typings/index.d.ts +3 -0
- package/src/typings/joi/index.d.ts +22 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { getAnswer } from '~/src/server/plugins/engine/components/helpers.js'
|
|
2
|
+
import { answer } from '~/src/server/plugins/nunjucks/filters/answer.js'
|
|
3
|
+
|
|
4
|
+
jest.mock('~/src/server/plugins/engine/components/helpers.ts', () => ({
|
|
5
|
+
getAnswer: jest.fn()
|
|
6
|
+
}))
|
|
7
|
+
|
|
8
|
+
describe('answer Nunjucks filter', () => {
|
|
9
|
+
/** @type { NunjucksContext } */
|
|
10
|
+
let mockThis
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks()
|
|
14
|
+
|
|
15
|
+
mockThis = /** @type {NunjucksContext} */ (
|
|
16
|
+
/** @type {unknown} */ ({
|
|
17
|
+
ctx: {
|
|
18
|
+
context: {
|
|
19
|
+
componentMap: new Map(),
|
|
20
|
+
relevantState: { someState: 'value' }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
jest.mocked(getAnswer).mockReturnValue('test answer')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('missing context', () => {
|
|
30
|
+
it('returns undefined', () => {
|
|
31
|
+
mockThis.ctx.context = undefined
|
|
32
|
+
|
|
33
|
+
const result = answer.call(mockThis, 'componentName')
|
|
34
|
+
|
|
35
|
+
expect(result).toBeUndefined()
|
|
36
|
+
expect(getAnswer).not.toHaveBeenCalled()
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('component lookup', () => {
|
|
41
|
+
it('returns undefined for non-existent component', () => {
|
|
42
|
+
const result = answer.call(mockThis, 'nonExistentComponent')
|
|
43
|
+
|
|
44
|
+
expect(result).toBeUndefined()
|
|
45
|
+
expect(getAnswer).not.toHaveBeenCalled()
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('non-form component', () => {
|
|
50
|
+
it('returns undefined', () => {
|
|
51
|
+
mockThis.ctx.context?.componentMap.set(
|
|
52
|
+
'nonFormComponent',
|
|
53
|
+
// @ts-expect-error - simplified mock component for testing
|
|
54
|
+
{ isFormComponent: false }
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const result = answer.call(mockThis, 'nonFormComponent')
|
|
58
|
+
|
|
59
|
+
expect(result).toBeUndefined()
|
|
60
|
+
expect(getAnswer).not.toHaveBeenCalled()
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('valid form component', () => {
|
|
65
|
+
it('returns the answer', () => {
|
|
66
|
+
const mockFormComponent = {
|
|
67
|
+
isFormComponent: true,
|
|
68
|
+
someProperty: 'value'
|
|
69
|
+
}
|
|
70
|
+
mockThis.ctx.context?.componentMap.set(
|
|
71
|
+
'validFormComponent',
|
|
72
|
+
// @ts-expect-error - simplified mock component for testing
|
|
73
|
+
mockFormComponent
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const result = answer.call(mockThis, 'validFormComponent')
|
|
77
|
+
|
|
78
|
+
expect(getAnswer).toHaveBeenCalledWith(
|
|
79
|
+
mockFormComponent,
|
|
80
|
+
mockThis.ctx.context?.relevantState
|
|
81
|
+
)
|
|
82
|
+
expect(result).toBe('test answer')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
89
|
+
*/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { evaluateTemplate } from '~/src/server/plugins/engine/helpers.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Nunjucks filter to evaluate a liquid template.
|
|
5
|
+
* Currently just used in `src/server/views/layout.html#LN37` for the pageTitle
|
|
6
|
+
* @this {NunjucksContext}
|
|
7
|
+
* @param {string} template
|
|
8
|
+
*/
|
|
9
|
+
export function evaluate(template) {
|
|
10
|
+
const { context } = this.ctx
|
|
11
|
+
|
|
12
|
+
if (!context || typeof template !== 'string') {
|
|
13
|
+
return template
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return evaluateTemplate(template, context)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
21
|
+
*/
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nunjucks filter to get the answer for a component
|
|
3
|
+
* @this {NunjucksContext}
|
|
4
|
+
* @param {string} name - The name of the component
|
|
5
|
+
*/
|
|
6
|
+
export function field(name) {
|
|
7
|
+
if (typeof name !== 'string') {
|
|
8
|
+
return undefined
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { context } = this.ctx
|
|
12
|
+
|
|
13
|
+
if (!context) {
|
|
14
|
+
return undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const component = context.componentMap.get(name)
|
|
18
|
+
|
|
19
|
+
if (!component?.isFormComponent) {
|
|
20
|
+
return undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return component
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
28
|
+
*/
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { field } from '~/src/server/plugins/nunjucks/filters/field.js'
|
|
2
|
+
|
|
3
|
+
describe('field Nunjucks filter', () => {
|
|
4
|
+
/** @type {NunjucksContext} */
|
|
5
|
+
let mockThis
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
mockThis = /** @type {NunjucksContext} */ (
|
|
9
|
+
/** @type {unknown} */ ({
|
|
10
|
+
ctx: {
|
|
11
|
+
context: {
|
|
12
|
+
componentMap: new Map()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('invalid inputs', () => {
|
|
20
|
+
it('returns undefined for non-string name parameter', () => {
|
|
21
|
+
// @ts-expect-error - testing invalid input
|
|
22
|
+
const result = field.call(mockThis, 123)
|
|
23
|
+
expect(result).toBeUndefined()
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('missing context', () => {
|
|
28
|
+
it('returns undefined', () => {
|
|
29
|
+
mockThis.ctx.context = undefined
|
|
30
|
+
const result = field.call(mockThis, 'componentName')
|
|
31
|
+
expect(result).toBeUndefined()
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('component lookup', () => {
|
|
36
|
+
it('returns undefined for non-existent component', () => {
|
|
37
|
+
const result = field.call(mockThis, 'nonExistentComponent')
|
|
38
|
+
expect(result).toBeUndefined()
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('non-form component', () => {
|
|
43
|
+
it('returns undefined', () => {
|
|
44
|
+
mockThis.ctx.context?.componentMap.set(
|
|
45
|
+
'nonFormComponent',
|
|
46
|
+
// @ts-expect-error - simplified mock component for testing
|
|
47
|
+
{ isFormComponent: false }
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const result = field.call(mockThis, 'nonFormComponent')
|
|
51
|
+
expect(result).toBeUndefined()
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('valid form component', () => {
|
|
56
|
+
it('returns the component', () => {
|
|
57
|
+
const mockFormComponent = {
|
|
58
|
+
isFormComponent: true,
|
|
59
|
+
someProperty: 'value'
|
|
60
|
+
}
|
|
61
|
+
mockThis.ctx.context?.componentMap.set(
|
|
62
|
+
'validFormComponent',
|
|
63
|
+
// @ts-expect-error - simplified mock component for testing
|
|
64
|
+
mockFormComponent
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const result = field.call(mockThis, 'validFormComponent')
|
|
68
|
+
expect(result).toBe(mockFormComponent)
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
75
|
+
*/
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import hljs from 'highlight.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format code with syntax highlighting
|
|
5
|
+
* @param {string} code - Code in plain text
|
|
6
|
+
* @param {string} [language] - Code programming language
|
|
7
|
+
* @returns {string} Code with syntax highlighting
|
|
8
|
+
*/
|
|
9
|
+
export function highlight(code, language) {
|
|
10
|
+
return hljs.highlight(code, { language: language ?? 'plaintext' }).value
|
|
11
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getPageHref } from '~/src/server/plugins/engine/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Nunjucks filter to get the answer for a component
|
|
5
|
+
* @this {NunjucksContext}
|
|
6
|
+
* @param {string} path - The name of the component
|
|
7
|
+
*/
|
|
8
|
+
export function href(path) {
|
|
9
|
+
if (typeof path !== 'string') {
|
|
10
|
+
return undefined
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { context } = this.ctx
|
|
14
|
+
|
|
15
|
+
if (!context) {
|
|
16
|
+
return undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const page = context.pageMap.get(path)
|
|
20
|
+
|
|
21
|
+
if (page === undefined) {
|
|
22
|
+
return undefined
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return getPageHref(page)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
30
|
+
*/
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getPageHref } from '~/src/server/plugins/engine/index.js'
|
|
2
|
+
import { href } from '~/src/server/plugins/nunjucks/filters/href.js'
|
|
3
|
+
|
|
4
|
+
jest.mock('~/src/server/plugins/engine/index.ts', () => ({
|
|
5
|
+
getPageHref: jest.fn()
|
|
6
|
+
}))
|
|
7
|
+
|
|
8
|
+
describe('href Nunjucks filter', () => {
|
|
9
|
+
/** @type { NunjucksContext } */
|
|
10
|
+
let mockThis
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks()
|
|
14
|
+
|
|
15
|
+
mockThis = /** @type {NunjucksContext} */ (
|
|
16
|
+
/** @type {unknown} */ ({
|
|
17
|
+
ctx: {
|
|
18
|
+
context: {
|
|
19
|
+
pageMap: new Map()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
jest.mocked(getPageHref).mockReturnValue('/some-page')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('invalid inputs', () => {
|
|
29
|
+
it('returns undefined for non-string path parameter', () => {
|
|
30
|
+
// @ts-expect-error - testing invalid input
|
|
31
|
+
const result = href.call(mockThis, 123)
|
|
32
|
+
|
|
33
|
+
expect(result).toBeUndefined()
|
|
34
|
+
expect(getPageHref).not.toHaveBeenCalled()
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('missing context', () => {
|
|
39
|
+
it('returns undefined', () => {
|
|
40
|
+
mockThis.ctx.context = undefined
|
|
41
|
+
|
|
42
|
+
const result = href.call(mockThis, 'pagePath')
|
|
43
|
+
|
|
44
|
+
expect(result).toBeUndefined()
|
|
45
|
+
expect(getPageHref).not.toHaveBeenCalled()
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('page lookup', () => {
|
|
50
|
+
it('returns undefined for non-existent page', () => {
|
|
51
|
+
const result = href.call(mockThis, 'nonExistentPage')
|
|
52
|
+
|
|
53
|
+
expect(result).toBeUndefined()
|
|
54
|
+
expect(getPageHref).not.toHaveBeenCalled()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('valid page', () => {
|
|
59
|
+
it('returns the href for the page', () => {
|
|
60
|
+
const mockPage = {
|
|
61
|
+
path: '/some-page',
|
|
62
|
+
someProperty: 'value'
|
|
63
|
+
}
|
|
64
|
+
mockThis.ctx.context?.pageMap.set(
|
|
65
|
+
'validPage',
|
|
66
|
+
// @ts-expect-error - simplified mock page for testing
|
|
67
|
+
mockPage
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const result = href.call(mockThis, 'validPage')
|
|
71
|
+
|
|
72
|
+
expect(getPageHref).toHaveBeenCalledWith(mockPage)
|
|
73
|
+
expect(result).toBe('/some-page')
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
80
|
+
*/
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { markdownToHtml as markdown } from '@defra/forms-model'
|
|
2
|
+
export { highlight } from '~/src/server/plugins/nunjucks/filters/highlight.js'
|
|
3
|
+
export { inspect } from '~/src/server/plugins/nunjucks/filters/inspect.js'
|
|
4
|
+
export { evaluate } from '~/src/server/plugins/nunjucks/filters/evaluate.js'
|
|
5
|
+
export { answer } from '~/src/server/plugins/nunjucks/filters/answer.js'
|
|
6
|
+
export { href } from '~/src/server/plugins/nunjucks/filters/href.js'
|
|
7
|
+
export { field } from '~/src/server/plugins/nunjucks/filters/field.js'
|
|
8
|
+
export { page } from '~/src/server/plugins/nunjucks/filters/page.js'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import util from 'util'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format JavaScript objects as strings
|
|
5
|
+
* @param {unknown} object - JavaScript object to format
|
|
6
|
+
* @returns {string} Formatted string
|
|
7
|
+
*/
|
|
8
|
+
export function inspect(object) {
|
|
9
|
+
return util.inspect(object, {
|
|
10
|
+
compact: false,
|
|
11
|
+
depth: Infinity,
|
|
12
|
+
maxArrayLength: Infinity,
|
|
13
|
+
maxStringLength: Infinity
|
|
14
|
+
})
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nunjucks filter to get the page for a given path
|
|
3
|
+
* @this {NunjucksContext}
|
|
4
|
+
* @param {string} path - The path of the page
|
|
5
|
+
*/
|
|
6
|
+
export function page(path) {
|
|
7
|
+
if (typeof path !== 'string') {
|
|
8
|
+
return undefined
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { context } = this.ctx
|
|
12
|
+
|
|
13
|
+
if (!context) {
|
|
14
|
+
return undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const foundPage = context.pageMap.get(path)
|
|
18
|
+
|
|
19
|
+
return foundPage
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
24
|
+
*/
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { page } from '~/src/server/plugins/nunjucks/filters/page.js'
|
|
2
|
+
|
|
3
|
+
describe('page Nunjucks filter', () => {
|
|
4
|
+
/** @type { NunjucksContext } */
|
|
5
|
+
let mockThis
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
mockThis = /** @type {NunjucksContext} */ (
|
|
9
|
+
/** @type {unknown} */ ({
|
|
10
|
+
ctx: {
|
|
11
|
+
context: {
|
|
12
|
+
pageMap: new Map()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('invalid inputs', () => {
|
|
20
|
+
it('returns undefined for non-string path parameter', () => {
|
|
21
|
+
// @ts-expect-error - testing invalid input
|
|
22
|
+
const result = page.call(mockThis, 123)
|
|
23
|
+
|
|
24
|
+
expect(result).toBeUndefined()
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('missing context', () => {
|
|
29
|
+
it('returns undefined', () => {
|
|
30
|
+
mockThis.ctx.context = undefined
|
|
31
|
+
|
|
32
|
+
const result = page.call(mockThis, 'pagePath')
|
|
33
|
+
|
|
34
|
+
expect(result).toBeUndefined()
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('page lookup', () => {
|
|
39
|
+
it('returns undefined for non-existent page', () => {
|
|
40
|
+
const result = page.call(mockThis, 'nonExistentPage')
|
|
41
|
+
|
|
42
|
+
expect(result).toBeUndefined()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('returns the page when found', () => {
|
|
46
|
+
const mockPage = {
|
|
47
|
+
path: '/some-page',
|
|
48
|
+
someProperty: 'value'
|
|
49
|
+
}
|
|
50
|
+
mockThis.ctx.context?.pageMap.set(
|
|
51
|
+
'validPage',
|
|
52
|
+
// @ts-expect-error - simplified mock page for testing
|
|
53
|
+
mockPage
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const result = page.call(mockThis, 'validPage')
|
|
57
|
+
|
|
58
|
+
expect(result).toBe(mockPage)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @import { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js'
|
|
65
|
+
*/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import vision from '@hapi/vision'
|
|
2
|
+
import nunjucks from 'nunjucks'
|
|
3
|
+
|
|
4
|
+
import { config } from '~/src/config/index.js'
|
|
5
|
+
import { context } from '~/src/server/plugins/nunjucks/context.js'
|
|
6
|
+
import {
|
|
7
|
+
environment,
|
|
8
|
+
paths
|
|
9
|
+
} from '~/src/server/plugins/nunjucks/environment.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @type {ServerRegisterPluginObject<ServerViewsConfiguration>}
|
|
13
|
+
*/
|
|
14
|
+
export const plugin = {
|
|
15
|
+
plugin: vision,
|
|
16
|
+
options: {
|
|
17
|
+
engines: {
|
|
18
|
+
html: {
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} path
|
|
21
|
+
* @param {{ environment: typeof environment }} compileOptions
|
|
22
|
+
* @returns {(options: ReturnType<Awaited<typeof context>>) => string}
|
|
23
|
+
*/
|
|
24
|
+
compile(path, compileOptions) {
|
|
25
|
+
return (options) =>
|
|
26
|
+
nunjucks.compile(path, compileOptions.environment).render(options)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
path: paths,
|
|
31
|
+
compileOptions: { environment },
|
|
32
|
+
isCached: config.get('isProduction'),
|
|
33
|
+
context
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @import { ServerRegisterPluginObject } from '@hapi/hapi'
|
|
39
|
+
* @import { ServerViewsConfiguration } from '@hapi/vision'
|
|
40
|
+
*/
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { environment } from '~/src/server/plugins/nunjucks/environment.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Render Nunjucks macro
|
|
5
|
+
* @param {string} macroName
|
|
6
|
+
* @param {string} macroPath
|
|
7
|
+
* @param {RenderOptions & MacroOptions} [options]
|
|
8
|
+
*/
|
|
9
|
+
export function macro(macroName, macroPath, options) {
|
|
10
|
+
const macroParams = JSON.stringify(options?.params, undefined, 2)
|
|
11
|
+
let macroString = `{%- from "${macroPath}" import ${macroName} -%}`
|
|
12
|
+
|
|
13
|
+
if (options?.callBlock) {
|
|
14
|
+
macroString += `{%- call ${macroName}(${macroParams}) -%}${options.callBlock}{%- endcall -%}`
|
|
15
|
+
} else {
|
|
16
|
+
macroString += `{{- ${macroName}(${macroParams}) -}}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return string(macroString, options)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Render Nunjucks code
|
|
24
|
+
* @param {string} viewString - Nunjucks string to render
|
|
25
|
+
* @param {RenderOptions} [options]
|
|
26
|
+
*/
|
|
27
|
+
export function string(viewString, options) {
|
|
28
|
+
return environment.renderString(viewString, options?.context ?? {})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Render Nunjucks view
|
|
33
|
+
* @param {string} viewPath
|
|
34
|
+
* @param {RenderOptions} [options]
|
|
35
|
+
*/
|
|
36
|
+
export function view(viewPath, options) {
|
|
37
|
+
return environment.render(viewPath, options?.context)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @import { MacroOptions, RenderOptions } from '~/src/server/plugins/nunjucks/types.js'
|
|
42
|
+
*/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} MacroOptions
|
|
3
|
+
* @property {string} [callBlock] - Nunjucks call block content
|
|
4
|
+
* @property {object} [params] - Nunjucks macro params
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} RenderOptions
|
|
9
|
+
* @property {object} [context] - Nunjucks render context
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} ViewContext - Nunjucks view context
|
|
14
|
+
* @property {string} appVersion - Application version
|
|
15
|
+
* @property {string} assetPath - Asset path
|
|
16
|
+
* @property {Partial<Config>} config - Application config properties
|
|
17
|
+
* @property {CookieConsent} [cookieConsent] - Cookie consent preferences
|
|
18
|
+
* @property {string} [crumb] - Cross-Site Request Forgery (CSRF) token
|
|
19
|
+
* @property {string} [cspNonce] - Content Security Policy (CSP) nonce
|
|
20
|
+
* @property {string} [currentPath] - Current path
|
|
21
|
+
* @property {string} [previewMode] - Preview mode
|
|
22
|
+
* @property {string} [slug] - Form slug
|
|
23
|
+
* @property {(asset?: string) => string} getAssetPath - Asset path resolver
|
|
24
|
+
* @property {FormContext} [context] - the current form context
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {ReturnType<typeof config['getProperties']>} Config - Application config properties
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef NunjucksContext
|
|
33
|
+
* @property {ViewContext} ctx - the current nunjucks view context
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @import { CookieConsent } from '~/src/common/types.js'
|
|
38
|
+
* @import { config } from '~/src/config/index.js'
|
|
39
|
+
* @import { FormContext } from '~/src/server/plugins/engine/types.js'
|
|
40
|
+
*/
|