@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.
Files changed (224) hide show
  1. package/package.json +3 -2
  2. package/src/client/javascripts/application.js +87 -0
  3. package/src/client/javascripts/file-upload.js +386 -0
  4. package/src/client/stylesheets/_code.scss +33 -0
  5. package/src/client/stylesheets/_govuk-frontend.scss +4 -0
  6. package/src/client/stylesheets/_prose.scss +56 -0
  7. package/src/client/stylesheets/_service-banner.scss +24 -0
  8. package/src/client/stylesheets/_summary-list.scss +28 -0
  9. package/src/client/stylesheets/_tag-env.scss +24 -0
  10. package/src/client/stylesheets/application.scss +14 -0
  11. package/src/common/cookies.js +58 -0
  12. package/src/common/cookies.test.js +23 -0
  13. package/src/common/types.js +5 -0
  14. package/src/config/index.ts +271 -0
  15. package/src/index.ts +31 -0
  16. package/src/server/common/helpers/logging/logger-options.test.ts +50 -0
  17. package/src/server/common/helpers/logging/logger-options.ts +46 -0
  18. package/src/server/common/helpers/logging/logger.ts +7 -0
  19. package/src/server/common/helpers/logging/request-logger.ts +9 -0
  20. package/src/server/common/helpers/logging/request-tracing.js +10 -0
  21. package/src/server/common/helpers/redis-client.js +70 -0
  22. package/src/server/constants.js +1 -0
  23. package/src/server/forms/README.md +10 -0
  24. package/src/server/forms/components.json +1015 -0
  25. package/src/server/forms/report-a-terrorist.json +270 -0
  26. package/src/server/forms/runner-components-test.json +365 -0
  27. package/src/server/forms/test.json +581 -0
  28. package/src/server/index.test.ts +582 -0
  29. package/src/server/index.ts +140 -0
  30. package/src/server/plugins/blankie.test.ts +73 -0
  31. package/src/server/plugins/blankie.ts +48 -0
  32. package/src/server/plugins/crumb.ts +20 -0
  33. package/src/server/plugins/engine/README.md +87 -0
  34. package/src/server/plugins/engine/components/AutocompleteField.test.ts +294 -0
  35. package/src/server/plugins/engine/components/AutocompleteField.ts +49 -0
  36. package/src/server/plugins/engine/components/CheckboxesField.test.ts +379 -0
  37. package/src/server/plugins/engine/components/CheckboxesField.ts +106 -0
  38. package/src/server/plugins/engine/components/ComponentBase.ts +97 -0
  39. package/src/server/plugins/engine/components/ComponentCollection.ts +278 -0
  40. package/src/server/plugins/engine/components/DatePartsField.test.ts +822 -0
  41. package/src/server/plugins/engine/components/DatePartsField.ts +264 -0
  42. package/src/server/plugins/engine/components/Details.test.ts +49 -0
  43. package/src/server/plugins/engine/components/Details.ts +30 -0
  44. package/src/server/plugins/engine/components/EmailAddressField.test.ts +395 -0
  45. package/src/server/plugins/engine/components/EmailAddressField.ts +55 -0
  46. package/src/server/plugins/engine/components/FileUploadField.test.ts +778 -0
  47. package/src/server/plugins/engine/components/FileUploadField.ts +262 -0
  48. package/src/server/plugins/engine/components/FormComponent.ts +249 -0
  49. package/src/server/plugins/engine/components/Html.test.ts +48 -0
  50. package/src/server/plugins/engine/components/Html.ts +29 -0
  51. package/src/server/plugins/engine/components/InsetText.test.ts +48 -0
  52. package/src/server/plugins/engine/components/InsetText.ts +27 -0
  53. package/src/server/plugins/engine/components/List.test.ts +76 -0
  54. package/src/server/plugins/engine/components/List.ts +72 -0
  55. package/src/server/plugins/engine/components/ListFormComponent.ts +140 -0
  56. package/src/server/plugins/engine/components/MonthYearField.test.ts +567 -0
  57. package/src/server/plugins/engine/components/MonthYearField.ts +222 -0
  58. package/src/server/plugins/engine/components/MultilineTextField.test.ts +558 -0
  59. package/src/server/plugins/engine/components/MultilineTextField.ts +138 -0
  60. package/src/server/plugins/engine/components/NumberField.test.ts +701 -0
  61. package/src/server/plugins/engine/components/NumberField.ts +163 -0
  62. package/src/server/plugins/engine/components/RadiosField.test.ts +288 -0
  63. package/src/server/plugins/engine/components/RadiosField.ts +24 -0
  64. package/src/server/plugins/engine/components/SelectField.test.ts +288 -0
  65. package/src/server/plugins/engine/components/SelectField.ts +47 -0
  66. package/src/server/plugins/engine/components/SelectionControlField.ts +43 -0
  67. package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +356 -0
  68. package/src/server/plugins/engine/components/TelephoneNumberField.ts +67 -0
  69. package/src/server/plugins/engine/components/TextField.test.ts +489 -0
  70. package/src/server/plugins/engine/components/TextField.ts +96 -0
  71. package/src/server/plugins/engine/components/UkAddressField.test.ts +623 -0
  72. package/src/server/plugins/engine/components/UkAddressField.ts +172 -0
  73. package/src/server/plugins/engine/components/YesNoField.test.ts +248 -0
  74. package/src/server/plugins/engine/components/YesNoField.ts +31 -0
  75. package/src/server/plugins/engine/components/constants.ts +1 -0
  76. package/src/server/plugins/engine/components/helpers.ts +330 -0
  77. package/src/server/plugins/engine/components/index.ts +24 -0
  78. package/src/server/plugins/engine/components/types.ts +117 -0
  79. package/src/server/plugins/engine/configureEnginePlugin.ts +47 -0
  80. package/src/server/plugins/engine/helpers.test.ts +791 -0
  81. package/src/server/plugins/engine/helpers.ts +379 -0
  82. package/src/server/plugins/engine/index.ts +7 -0
  83. package/src/server/plugins/engine/models/FormModel.test.ts +42 -0
  84. package/src/server/plugins/engine/models/FormModel.ts +443 -0
  85. package/src/server/plugins/engine/models/RepeatingSummaryViewModel.ts +0 -0
  86. package/src/server/plugins/engine/models/Section.ts +0 -0
  87. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +209 -0
  88. package/src/server/plugins/engine/models/SummaryViewModel.ts +220 -0
  89. package/src/server/plugins/engine/models/index.ts +2 -0
  90. package/src/server/plugins/engine/models/types.ts +114 -0
  91. package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +143 -0
  92. package/src/server/plugins/engine/outputFormatters/human/v1.ts +73 -0
  93. package/src/server/plugins/engine/outputFormatters/index.test.ts +17 -0
  94. package/src/server/plugins/engine/outputFormatters/index.ts +44 -0
  95. package/src/server/plugins/engine/outputFormatters/machine/v1.test.ts +229 -0
  96. package/src/server/plugins/engine/outputFormatters/machine/v1.ts +140 -0
  97. package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +229 -0
  98. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +153 -0
  99. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1108 -0
  100. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +446 -0
  101. package/src/server/plugins/engine/pageControllers/PageController.test.ts +205 -0
  102. package/src/server/plugins/engine/pageControllers/PageController.ts +176 -0
  103. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +1264 -0
  104. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +561 -0
  105. package/src/server/plugins/engine/pageControllers/README.md +28 -0
  106. package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +264 -0
  107. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +458 -0
  108. package/src/server/plugins/engine/pageControllers/StartPageController.ts +18 -0
  109. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +50 -0
  110. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +261 -0
  111. package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +28 -0
  112. package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +19 -0
  113. package/src/server/plugins/engine/pageControllers/helpers.test.ts +198 -0
  114. package/src/server/plugins/engine/pageControllers/helpers.ts +101 -0
  115. package/src/server/plugins/engine/pageControllers/index.ts +10 -0
  116. package/src/server/plugins/engine/pageControllers/validationOptions.ts +89 -0
  117. package/src/server/plugins/engine/plugin.ts +673 -0
  118. package/src/server/plugins/engine/services/formSubmissionService.js +46 -0
  119. package/src/server/plugins/engine/services/formsService.js +46 -0
  120. package/src/server/plugins/engine/services/formsService.test.js +90 -0
  121. package/src/server/plugins/engine/services/index.js +3 -0
  122. package/src/server/plugins/engine/services/notifyService.test.ts +132 -0
  123. package/src/server/plugins/engine/services/notifyService.ts +64 -0
  124. package/src/server/plugins/engine/services/uploadService.js +60 -0
  125. package/src/server/plugins/engine/types.ts +315 -0
  126. package/src/server/plugins/engine/views/components/autocompletefield.html +5 -0
  127. package/src/server/plugins/engine/views/components/checkboxesfield.html +5 -0
  128. package/src/server/plugins/engine/views/components/datepartsfield.html +5 -0
  129. package/src/server/plugins/engine/views/components/details.html +6 -0
  130. package/src/server/plugins/engine/views/components/emailaddressfield.html +5 -0
  131. package/src/server/plugins/engine/views/components/fileuploadfield-key.html +8 -0
  132. package/src/server/plugins/engine/views/components/fileuploadfield-value.html +3 -0
  133. package/src/server/plugins/engine/views/components/fileuploadfield.html +24 -0
  134. package/src/server/plugins/engine/views/components/html.html +3 -0
  135. package/src/server/plugins/engine/views/components/insettext.html +7 -0
  136. package/src/server/plugins/engine/views/components/list.html +36 -0
  137. package/src/server/plugins/engine/views/components/monthyearfield.html +5 -0
  138. package/src/server/plugins/engine/views/components/multilinetextfield.html +10 -0
  139. package/src/server/plugins/engine/views/components/numberfield.html +5 -0
  140. package/src/server/plugins/engine/views/components/radiosfield.html +5 -0
  141. package/src/server/plugins/engine/views/components/selectfield.html +5 -0
  142. package/src/server/plugins/engine/views/components/telephonenumberfield.html +5 -0
  143. package/src/server/plugins/engine/views/components/textfield.html +5 -0
  144. package/src/server/plugins/engine/views/components/ukaddressfield.html +25 -0
  145. package/src/server/plugins/engine/views/components/yesnofield.html +5 -0
  146. package/src/server/plugins/engine/views/file-upload.html +45 -0
  147. package/src/server/plugins/engine/views/index.html +39 -0
  148. package/src/server/plugins/engine/views/item-delete.html +56 -0
  149. package/src/server/plugins/engine/views/partials/components.html +6 -0
  150. package/src/server/plugins/engine/views/partials/conditional-components.html +3 -0
  151. package/src/server/plugins/engine/views/partials/debug.html +44 -0
  152. package/src/server/plugins/engine/views/partials/form.html +15 -0
  153. package/src/server/plugins/engine/views/partials/heading.html +16 -0
  154. package/src/server/plugins/engine/views/partials/preview-banner.html +32 -0
  155. package/src/server/plugins/engine/views/partials/preview-banner.test.js +122 -0
  156. package/src/server/plugins/engine/views/partials/warn-missing-notification-email.html +10 -0
  157. package/src/server/plugins/engine/views/repeat-list-summary.html +53 -0
  158. package/src/server/plugins/errorPages.ts +58 -0
  159. package/src/server/plugins/nunjucks/context.js +88 -0
  160. package/src/server/plugins/nunjucks/context.test.js +142 -0
  161. package/src/server/plugins/nunjucks/enviroment.test.js +201 -0
  162. package/src/server/plugins/nunjucks/environment.js +116 -0
  163. package/src/server/plugins/nunjucks/filters/answer.js +27 -0
  164. package/src/server/plugins/nunjucks/filters/answer.test.js +89 -0
  165. package/src/server/plugins/nunjucks/filters/evaluate.js +21 -0
  166. package/src/server/plugins/nunjucks/filters/field.js +28 -0
  167. package/src/server/plugins/nunjucks/filters/field.test.js +75 -0
  168. package/src/server/plugins/nunjucks/filters/highlight.js +11 -0
  169. package/src/server/plugins/nunjucks/filters/href.js +30 -0
  170. package/src/server/plugins/nunjucks/filters/href.test.js +80 -0
  171. package/src/server/plugins/nunjucks/filters/index.js +8 -0
  172. package/src/server/plugins/nunjucks/filters/inspect.js +15 -0
  173. package/src/server/plugins/nunjucks/filters/page.js +24 -0
  174. package/src/server/plugins/nunjucks/filters/page.test.js +65 -0
  175. package/src/server/plugins/nunjucks/index.js +3 -0
  176. package/src/server/plugins/nunjucks/plugin.js +40 -0
  177. package/src/server/plugins/nunjucks/render.js +42 -0
  178. package/src/server/plugins/nunjucks/types.js +40 -0
  179. package/src/server/plugins/pulse.ts +11 -0
  180. package/src/server/plugins/router.ts +201 -0
  181. package/src/server/plugins/session.ts +28 -0
  182. package/src/server/routes/health.js +13 -0
  183. package/src/server/routes/health.test.js +35 -0
  184. package/src/server/routes/index.test.ts +125 -0
  185. package/src/server/routes/index.ts +2 -0
  186. package/src/server/routes/public.ts +47 -0
  187. package/src/server/routes/types.ts +48 -0
  188. package/src/server/schemas/index.ts +34 -0
  189. package/src/server/secure-context.js +43 -0
  190. package/src/server/services/cacheService.test.ts +276 -0
  191. package/src/server/services/cacheService.ts +131 -0
  192. package/src/server/services/httpService.test.js +491 -0
  193. package/src/server/services/httpService.ts +50 -0
  194. package/src/server/services/index.ts +1 -0
  195. package/src/server/types.ts +54 -0
  196. package/src/server/utils/notify.test.ts +37 -0
  197. package/src/server/utils/notify.ts +50 -0
  198. package/src/server/utils/secure-context/get-trust-store-certs.js +11 -0
  199. package/src/server/utils/secure-context/get-trust-store-certs.test.js +19 -0
  200. package/src/server/utils/utils.js +24 -0
  201. package/src/server/utils/utils.test.js +54 -0
  202. package/src/server/views/404.html +16 -0
  203. package/src/server/views/500.html +19 -0
  204. package/src/server/views/components/debug/macro.njk +3 -0
  205. package/src/server/views/components/debug/template.njk +13 -0
  206. package/src/server/views/components/service-banner/macro.njk +3 -0
  207. package/src/server/views/components/service-banner/template.njk +20 -0
  208. package/src/server/views/components/service-banner/template.test.js +43 -0
  209. package/src/server/views/components/tag-env/macro.njk +3 -0
  210. package/src/server/views/components/tag-env/template.njk +30 -0
  211. package/src/server/views/components/tag-env/template.test.js +66 -0
  212. package/src/server/views/confirmation.html +19 -0
  213. package/src/server/views/help/accessibility-statement.html +58 -0
  214. package/src/server/views/help/cookie-preferences.html +57 -0
  215. package/src/server/views/help/cookies.html +71 -0
  216. package/src/server/views/help/get-support.html +37 -0
  217. package/src/server/views/help/privacy-notice.html +68 -0
  218. package/src/server/views/help/terms-and-conditions.html +83 -0
  219. package/src/server/views/layout.html +199 -0
  220. package/src/server/views/summary.html +50 -0
  221. package/src/typings/hapi/index.d.ts +95 -0
  222. package/src/typings/hapi-tracing/index.d.ts +6 -0
  223. package/src/typings/index.d.ts +3 -0
  224. 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,3 @@
1
+ export * as render from '~/src/server/plugins/nunjucks/render.js'
2
+ export { environment } from '~/src/server/plugins/nunjucks/environment.js'
3
+ export { plugin } from '~/src/server/plugins/nunjucks/plugin.js'
@@ -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
+ */
@@ -0,0 +1,11 @@
1
+ import { type ServerRegisterPluginObject } from '@hapi/hapi'
2
+ import pulse from 'hapi-pulse'
3
+
4
+ export default {
5
+ plugin: pulse,
6
+ options: {
7
+ timeout: 800
8
+ }
9
+ } satisfies ServerRegisterPluginObject<{
10
+ timeout: number
11
+ }>