@defra/forms-engine-plugin 0.0.2 → 0.0.4

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 (330) hide show
  1. package/.public/assets/fonts/bold-affa96571d-v2.woff +0 -0
  2. package/.public/assets/fonts/bold-b542beb274-v2.woff2 +0 -0
  3. package/.public/assets/fonts/light-94a07e06a1-v2.woff2 +0 -0
  4. package/.public/assets/fonts/light-f591b13f7d-v2.woff +0 -0
  5. package/.public/assets/images/favicon.ico +0 -0
  6. package/.public/assets/images/favicon.svg +1 -0
  7. package/.public/assets/images/govuk-crest.svg +1 -0
  8. package/.public/assets/images/govuk-icon-180.png +0 -0
  9. package/.public/assets/images/govuk-icon-192.png +0 -0
  10. package/.public/assets/images/govuk-icon-512.png +0 -0
  11. package/.public/assets/images/govuk-icon-mask.svg +1 -0
  12. package/.public/assets/images/govuk-opengraph-image.png +0 -0
  13. package/.public/assets/manifest.json +39 -0
  14. package/.public/assets-manifest.json +24 -0
  15. package/.public/javascripts/application.0fd8c18.min.js +3 -0
  16. package/.public/javascripts/application.0fd8c18.min.js.LICENSE.txt +58 -0
  17. package/.public/javascripts/application.0fd8c18.min.js.map +1 -0
  18. package/.public/javascripts/file-upload.b2f18f5.min.js +2 -0
  19. package/.public/javascripts/file-upload.b2f18f5.min.js.map +1 -0
  20. package/.public/javascripts/vendor/accessible-autocomplete.275d332.min.js +2 -0
  21. package/.public/javascripts/vendor/accessible-autocomplete.275d332.min.js.map +1 -0
  22. package/.public/stylesheets/application.e340021.min.css +14 -0
  23. package/.public/stylesheets/application.e340021.min.css.map +1 -0
  24. package/.server/server/plugins/nunjucks/environment.js +3 -3
  25. package/.server/server/plugins/nunjucks/environment.js.map +1 -1
  26. package/package.json +6 -2
  27. package/.browserslistrc +0 -16
  28. package/.editorconfig +0 -9
  29. package/.eslintrc.cjs +0 -266
  30. package/.github/dependabot.yml +0 -85
  31. package/.github/workflows/check-pull-request.yml +0 -209
  32. package/.github/workflows/pr-notifier.yml +0 -98
  33. package/.github/workflows/publish.yml +0 -111
  34. package/.husky/pre-commit +0 -1
  35. package/.lintstagedrc.js +0 -4
  36. package/.nvmrc +0 -1
  37. package/.prettierignore +0 -8
  38. package/.prettierrc.cjs +0 -26
  39. package/Dockerfile +0 -61
  40. package/Procfile +0 -1
  41. package/babel.config.cjs +0 -55
  42. package/compose/aws.env +0 -4
  43. package/compose/start-localstack.sh +0 -26
  44. package/docker-compose.yaml +0 -86
  45. package/globals.d.ts +0 -1
  46. package/jest.config.cjs +0 -54
  47. package/jest.environment.js +0 -4
  48. package/jest.setup.cjs +0 -14
  49. package/postcss.config.js +0 -26
  50. package/sonar-project.properties +0 -17
  51. package/src/client/javascripts/application.js +0 -87
  52. package/src/client/javascripts/file-upload.js +0 -386
  53. package/src/client/stylesheets/_code.scss +0 -33
  54. package/src/client/stylesheets/_govuk-frontend.scss +0 -4
  55. package/src/client/stylesheets/_prose.scss +0 -56
  56. package/src/client/stylesheets/_service-banner.scss +0 -24
  57. package/src/client/stylesheets/_summary-list.scss +0 -28
  58. package/src/client/stylesheets/_tag-env.scss +0 -24
  59. package/src/client/stylesheets/application.scss +0 -14
  60. package/src/common/cookies.js +0 -58
  61. package/src/common/cookies.test.js +0 -23
  62. package/src/common/types.js +0 -5
  63. package/src/config/index.ts +0 -271
  64. package/src/index.ts +0 -31
  65. package/src/server/common/helpers/logging/logger-options.test.ts +0 -50
  66. package/src/server/common/helpers/logging/logger-options.ts +0 -46
  67. package/src/server/common/helpers/logging/logger.ts +0 -7
  68. package/src/server/common/helpers/logging/request-logger.ts +0 -9
  69. package/src/server/common/helpers/logging/request-tracing.js +0 -10
  70. package/src/server/common/helpers/redis-client.js +0 -70
  71. package/src/server/constants.js +0 -1
  72. package/src/server/forms/README.md +0 -10
  73. package/src/server/forms/components.json +0 -1015
  74. package/src/server/forms/report-a-terrorist.json +0 -270
  75. package/src/server/forms/runner-components-test.json +0 -365
  76. package/src/server/forms/test.json +0 -581
  77. package/src/server/index.test.ts +0 -582
  78. package/src/server/index.ts +0 -140
  79. package/src/server/plugins/blankie.test.ts +0 -73
  80. package/src/server/plugins/blankie.ts +0 -48
  81. package/src/server/plugins/crumb.ts +0 -20
  82. package/src/server/plugins/engine/README.md +0 -87
  83. package/src/server/plugins/engine/components/AutocompleteField.test.ts +0 -294
  84. package/src/server/plugins/engine/components/AutocompleteField.ts +0 -49
  85. package/src/server/plugins/engine/components/CheckboxesField.test.ts +0 -379
  86. package/src/server/plugins/engine/components/CheckboxesField.ts +0 -106
  87. package/src/server/plugins/engine/components/ComponentBase.ts +0 -97
  88. package/src/server/plugins/engine/components/ComponentCollection.ts +0 -278
  89. package/src/server/plugins/engine/components/DatePartsField.test.ts +0 -822
  90. package/src/server/plugins/engine/components/DatePartsField.ts +0 -264
  91. package/src/server/plugins/engine/components/Details.test.ts +0 -49
  92. package/src/server/plugins/engine/components/Details.ts +0 -30
  93. package/src/server/plugins/engine/components/EmailAddressField.test.ts +0 -395
  94. package/src/server/plugins/engine/components/EmailAddressField.ts +0 -55
  95. package/src/server/plugins/engine/components/FileUploadField.test.ts +0 -778
  96. package/src/server/plugins/engine/components/FileUploadField.ts +0 -262
  97. package/src/server/plugins/engine/components/FormComponent.ts +0 -249
  98. package/src/server/plugins/engine/components/Html.test.ts +0 -48
  99. package/src/server/plugins/engine/components/Html.ts +0 -29
  100. package/src/server/plugins/engine/components/InsetText.test.ts +0 -48
  101. package/src/server/plugins/engine/components/InsetText.ts +0 -27
  102. package/src/server/plugins/engine/components/List.test.ts +0 -76
  103. package/src/server/plugins/engine/components/List.ts +0 -72
  104. package/src/server/plugins/engine/components/ListFormComponent.ts +0 -140
  105. package/src/server/plugins/engine/components/MonthYearField.test.ts +0 -567
  106. package/src/server/plugins/engine/components/MonthYearField.ts +0 -222
  107. package/src/server/plugins/engine/components/MultilineTextField.test.ts +0 -558
  108. package/src/server/plugins/engine/components/MultilineTextField.ts +0 -138
  109. package/src/server/plugins/engine/components/NumberField.test.ts +0 -701
  110. package/src/server/plugins/engine/components/NumberField.ts +0 -163
  111. package/src/server/plugins/engine/components/RadiosField.test.ts +0 -288
  112. package/src/server/plugins/engine/components/RadiosField.ts +0 -24
  113. package/src/server/plugins/engine/components/SelectField.test.ts +0 -288
  114. package/src/server/plugins/engine/components/SelectField.ts +0 -47
  115. package/src/server/plugins/engine/components/SelectionControlField.ts +0 -43
  116. package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +0 -356
  117. package/src/server/plugins/engine/components/TelephoneNumberField.ts +0 -67
  118. package/src/server/plugins/engine/components/TextField.test.ts +0 -489
  119. package/src/server/plugins/engine/components/TextField.ts +0 -96
  120. package/src/server/plugins/engine/components/UkAddressField.test.ts +0 -623
  121. package/src/server/plugins/engine/components/UkAddressField.ts +0 -172
  122. package/src/server/plugins/engine/components/YesNoField.test.ts +0 -248
  123. package/src/server/plugins/engine/components/YesNoField.ts +0 -31
  124. package/src/server/plugins/engine/components/constants.ts +0 -1
  125. package/src/server/plugins/engine/components/helpers.ts +0 -330
  126. package/src/server/plugins/engine/components/index.ts +0 -24
  127. package/src/server/plugins/engine/components/types.ts +0 -117
  128. package/src/server/plugins/engine/configureEnginePlugin.ts +0 -47
  129. package/src/server/plugins/engine/helpers.test.ts +0 -791
  130. package/src/server/plugins/engine/helpers.ts +0 -379
  131. package/src/server/plugins/engine/index.ts +0 -7
  132. package/src/server/plugins/engine/models/FormModel.test.ts +0 -42
  133. package/src/server/plugins/engine/models/FormModel.ts +0 -443
  134. package/src/server/plugins/engine/models/RepeatingSummaryViewModel.ts +0 -0
  135. package/src/server/plugins/engine/models/Section.ts +0 -0
  136. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +0 -209
  137. package/src/server/plugins/engine/models/SummaryViewModel.ts +0 -220
  138. package/src/server/plugins/engine/models/index.ts +0 -2
  139. package/src/server/plugins/engine/models/types.ts +0 -114
  140. package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +0 -143
  141. package/src/server/plugins/engine/outputFormatters/human/v1.ts +0 -73
  142. package/src/server/plugins/engine/outputFormatters/index.test.ts +0 -17
  143. package/src/server/plugins/engine/outputFormatters/index.ts +0 -44
  144. package/src/server/plugins/engine/outputFormatters/machine/v1.test.ts +0 -229
  145. package/src/server/plugins/engine/outputFormatters/machine/v1.ts +0 -140
  146. package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +0 -229
  147. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +0 -153
  148. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +0 -1108
  149. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +0 -446
  150. package/src/server/plugins/engine/pageControllers/PageController.test.ts +0 -205
  151. package/src/server/plugins/engine/pageControllers/PageController.ts +0 -176
  152. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +0 -1264
  153. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +0 -561
  154. package/src/server/plugins/engine/pageControllers/README.md +0 -28
  155. package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +0 -264
  156. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +0 -458
  157. package/src/server/plugins/engine/pageControllers/StartPageController.ts +0 -18
  158. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +0 -50
  159. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +0 -261
  160. package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +0 -28
  161. package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +0 -19
  162. package/src/server/plugins/engine/pageControllers/helpers.test.ts +0 -198
  163. package/src/server/plugins/engine/pageControllers/helpers.ts +0 -101
  164. package/src/server/plugins/engine/pageControllers/index.ts +0 -10
  165. package/src/server/plugins/engine/pageControllers/validationOptions.ts +0 -89
  166. package/src/server/plugins/engine/plugin.ts +0 -673
  167. package/src/server/plugins/engine/services/formSubmissionService.js +0 -46
  168. package/src/server/plugins/engine/services/formsService.js +0 -46
  169. package/src/server/plugins/engine/services/formsService.test.js +0 -90
  170. package/src/server/plugins/engine/services/index.js +0 -3
  171. package/src/server/plugins/engine/services/notifyService.test.ts +0 -132
  172. package/src/server/plugins/engine/services/notifyService.ts +0 -64
  173. package/src/server/plugins/engine/services/uploadService.js +0 -60
  174. package/src/server/plugins/engine/types.ts +0 -315
  175. package/src/server/plugins/engine/views/components/autocompletefield.html +0 -5
  176. package/src/server/plugins/engine/views/components/checkboxesfield.html +0 -5
  177. package/src/server/plugins/engine/views/components/datepartsfield.html +0 -5
  178. package/src/server/plugins/engine/views/components/details.html +0 -6
  179. package/src/server/plugins/engine/views/components/emailaddressfield.html +0 -5
  180. package/src/server/plugins/engine/views/components/fileuploadfield-key.html +0 -8
  181. package/src/server/plugins/engine/views/components/fileuploadfield-value.html +0 -3
  182. package/src/server/plugins/engine/views/components/fileuploadfield.html +0 -24
  183. package/src/server/plugins/engine/views/components/html.html +0 -3
  184. package/src/server/plugins/engine/views/components/insettext.html +0 -7
  185. package/src/server/plugins/engine/views/components/list.html +0 -36
  186. package/src/server/plugins/engine/views/components/monthyearfield.html +0 -5
  187. package/src/server/plugins/engine/views/components/multilinetextfield.html +0 -10
  188. package/src/server/plugins/engine/views/components/numberfield.html +0 -5
  189. package/src/server/plugins/engine/views/components/radiosfield.html +0 -5
  190. package/src/server/plugins/engine/views/components/selectfield.html +0 -5
  191. package/src/server/plugins/engine/views/components/telephonenumberfield.html +0 -5
  192. package/src/server/plugins/engine/views/components/textfield.html +0 -5
  193. package/src/server/plugins/engine/views/components/ukaddressfield.html +0 -25
  194. package/src/server/plugins/engine/views/components/yesnofield.html +0 -5
  195. package/src/server/plugins/engine/views/file-upload.html +0 -45
  196. package/src/server/plugins/engine/views/index.html +0 -39
  197. package/src/server/plugins/engine/views/item-delete.html +0 -56
  198. package/src/server/plugins/engine/views/partials/components.html +0 -6
  199. package/src/server/plugins/engine/views/partials/conditional-components.html +0 -3
  200. package/src/server/plugins/engine/views/partials/debug.html +0 -44
  201. package/src/server/plugins/engine/views/partials/form.html +0 -15
  202. package/src/server/plugins/engine/views/partials/heading.html +0 -16
  203. package/src/server/plugins/engine/views/partials/preview-banner.html +0 -32
  204. package/src/server/plugins/engine/views/partials/preview-banner.test.js +0 -122
  205. package/src/server/plugins/engine/views/partials/warn-missing-notification-email.html +0 -10
  206. package/src/server/plugins/engine/views/repeat-list-summary.html +0 -53
  207. package/src/server/plugins/errorPages.ts +0 -58
  208. package/src/server/plugins/nunjucks/context.js +0 -88
  209. package/src/server/plugins/nunjucks/context.test.js +0 -142
  210. package/src/server/plugins/nunjucks/enviroment.test.js +0 -201
  211. package/src/server/plugins/nunjucks/environment.js +0 -116
  212. package/src/server/plugins/nunjucks/filters/answer.js +0 -27
  213. package/src/server/plugins/nunjucks/filters/answer.test.js +0 -89
  214. package/src/server/plugins/nunjucks/filters/evaluate.js +0 -21
  215. package/src/server/plugins/nunjucks/filters/field.js +0 -28
  216. package/src/server/plugins/nunjucks/filters/field.test.js +0 -75
  217. package/src/server/plugins/nunjucks/filters/highlight.js +0 -11
  218. package/src/server/plugins/nunjucks/filters/href.js +0 -30
  219. package/src/server/plugins/nunjucks/filters/href.test.js +0 -80
  220. package/src/server/plugins/nunjucks/filters/index.js +0 -8
  221. package/src/server/plugins/nunjucks/filters/inspect.js +0 -15
  222. package/src/server/plugins/nunjucks/filters/page.js +0 -24
  223. package/src/server/plugins/nunjucks/filters/page.test.js +0 -65
  224. package/src/server/plugins/nunjucks/index.js +0 -3
  225. package/src/server/plugins/nunjucks/plugin.js +0 -40
  226. package/src/server/plugins/nunjucks/render.js +0 -42
  227. package/src/server/plugins/nunjucks/types.js +0 -40
  228. package/src/server/plugins/pulse.ts +0 -11
  229. package/src/server/plugins/router.ts +0 -201
  230. package/src/server/plugins/session.ts +0 -28
  231. package/src/server/routes/health.js +0 -13
  232. package/src/server/routes/health.test.js +0 -35
  233. package/src/server/routes/index.test.ts +0 -125
  234. package/src/server/routes/index.ts +0 -2
  235. package/src/server/routes/public.ts +0 -47
  236. package/src/server/routes/types.ts +0 -48
  237. package/src/server/schemas/index.ts +0 -34
  238. package/src/server/secure-context.js +0 -43
  239. package/src/server/services/cacheService.test.ts +0 -276
  240. package/src/server/services/cacheService.ts +0 -131
  241. package/src/server/services/httpService.test.js +0 -491
  242. package/src/server/services/httpService.ts +0 -50
  243. package/src/server/services/index.ts +0 -1
  244. package/src/server/types.ts +0 -54
  245. package/src/server/utils/notify.test.ts +0 -37
  246. package/src/server/utils/notify.ts +0 -50
  247. package/src/server/utils/secure-context/get-trust-store-certs.js +0 -11
  248. package/src/server/utils/secure-context/get-trust-store-certs.test.js +0 -19
  249. package/src/server/utils/utils.js +0 -24
  250. package/src/server/utils/utils.test.js +0 -54
  251. package/src/server/views/404.html +0 -16
  252. package/src/server/views/500.html +0 -19
  253. package/src/server/views/components/debug/macro.njk +0 -3
  254. package/src/server/views/components/debug/template.njk +0 -13
  255. package/src/server/views/components/service-banner/macro.njk +0 -3
  256. package/src/server/views/components/service-banner/template.njk +0 -20
  257. package/src/server/views/components/service-banner/template.test.js +0 -43
  258. package/src/server/views/components/tag-env/macro.njk +0 -3
  259. package/src/server/views/components/tag-env/template.njk +0 -30
  260. package/src/server/views/components/tag-env/template.test.js +0 -66
  261. package/src/server/views/confirmation.html +0 -19
  262. package/src/server/views/help/accessibility-statement.html +0 -58
  263. package/src/server/views/help/cookie-preferences.html +0 -57
  264. package/src/server/views/help/cookies.html +0 -71
  265. package/src/server/views/help/get-support.html +0 -37
  266. package/src/server/views/help/privacy-notice.html +0 -68
  267. package/src/server/views/help/terms-and-conditions.html +0 -83
  268. package/src/server/views/layout.html +0 -199
  269. package/src/server/views/summary.html +0 -50
  270. package/src/typings/hapi/index.d.ts +0 -95
  271. package/src/typings/hapi-tracing/index.d.ts +0 -6
  272. package/src/typings/index.d.ts +0 -3
  273. package/src/typings/joi/index.d.ts +0 -22
  274. package/stylelint.config.js +0 -10
  275. package/test/client/javascripts/file-upload.test.js +0 -1197
  276. package/test/condition/checkboxes.test.js +0 -112
  277. package/test/condition/radios.test.js +0 -112
  278. package/test/condition/text.test.js +0 -103
  279. package/test/fixtures/assets-manifest.json +0 -4
  280. package/test/fixtures/form.js +0 -86
  281. package/test/fixtures/index.js +0 -2
  282. package/test/fixtures/list.js +0 -92
  283. package/test/form/cookies.test.js +0 -338
  284. package/test/form/csrf.test.js +0 -87
  285. package/test/form/definitions/basic.js +0 -101
  286. package/test/form/definitions/blank.js +0 -10
  287. package/test/form/definitions/checkboxes.json +0 -88
  288. package/test/form/definitions/components.json +0 -452
  289. package/test/form/definitions/conditional-reveal.js +0 -140
  290. package/test/form/definitions/conditions-basic.js +0 -187
  291. package/test/form/definitions/conditions-complex.js +0 -338
  292. package/test/form/definitions/conditions-dates.js +0 -78
  293. package/test/form/definitions/conditions-escaping.js +0 -143
  294. package/test/form/definitions/demo-cph-number.js +0 -3099
  295. package/test/form/definitions/feedback.json +0 -45
  296. package/test/form/definitions/fields-optional.js +0 -402
  297. package/test/form/definitions/fields-required.js +0 -402
  298. package/test/form/definitions/file-upload-basic.js +0 -44
  299. package/test/form/definitions/file-upload.js +0 -66
  300. package/test/form/definitions/minimal.js +0 -39
  301. package/test/form/definitions/phase-alpha.json +0 -33
  302. package/test/form/definitions/phase-default.json +0 -26
  303. package/test/form/definitions/radios.json +0 -88
  304. package/test/form/definitions/repeat-mixed.js +0 -54
  305. package/test/form/definitions/repeat.js +0 -70
  306. package/test/form/definitions/status.json +0 -126
  307. package/test/form/definitions/templates.js +0 -183
  308. package/test/form/definitions/test.json +0 -581
  309. package/test/form/definitions/text.json +0 -75
  310. package/test/form/definitions/titles.json +0 -170
  311. package/test/form/definitions.test.js +0 -47
  312. package/test/form/exit-page.test.js +0 -210
  313. package/test/form/feedback.test.js +0 -68
  314. package/test/form/fields-optional.test.js +0 -237
  315. package/test/form/fields-required.test.js +0 -294
  316. package/test/form/file-upload.test.js +0 -313
  317. package/test/form/govuk-notify.test.js +0 -449
  318. package/test/form/journey-basic.test.js +0 -444
  319. package/test/form/persist-files.test.js +0 -227
  320. package/test/form/phase-banner.test.js +0 -71
  321. package/test/form/repeat.test.js +0 -628
  322. package/test/form/summary-submission-email.test.js +0 -95
  323. package/test/form/template.test.js +0 -288
  324. package/test/form/titles.test.js +0 -204
  325. package/test/helpers/component-helpers.js +0 -74
  326. package/test/utils/get-cookie.js +0 -42
  327. package/test/utils/get-form-definitions.js +0 -18
  328. package/tmp.pdf +0 -1
  329. package/tsconfig.json +0 -28
  330. package/webpack.config.js +0 -208
@@ -1,443 +0,0 @@
1
- import {
2
- ComponentType,
3
- ConditionsModel,
4
- ControllerPath,
5
- ControllerType,
6
- Engine,
7
- formDefinitionSchema,
8
- hasComponents,
9
- hasRepeater,
10
- type ComponentDef,
11
- type ConditionWrapper,
12
- type ConditionsModelData,
13
- type DateUnits,
14
- type FormDefinition,
15
- type List,
16
- type Page
17
- } from '@defra/forms-model'
18
- import { add } from 'date-fns'
19
- import { Parser, type Value } from 'expr-eval'
20
- import joi from 'joi'
21
-
22
- import { type Component } from '~/src/server/plugins/engine/components/helpers.js'
23
- import {
24
- findPage,
25
- getError,
26
- getPage
27
- } from '~/src/server/plugins/engine/helpers.js'
28
- import { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'
29
- import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
30
- import {
31
- createPage,
32
- type PageControllerClass
33
- } from '~/src/server/plugins/engine/pageControllers/helpers.js'
34
- import { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
35
- import * as defaultServices from '~/src/server/plugins/engine/services/index.js'
36
- import {
37
- type FormContext,
38
- type FormContextRequest,
39
- type FormState,
40
- type FormSubmissionError,
41
- type FormSubmissionState
42
- } from '~/src/server/plugins/engine/types.js'
43
- import { FormAction } from '~/src/server/routes/types.js'
44
- import { merge } from '~/src/server/services/cacheService.js'
45
- import { type Services } from '~/src/server/types.js'
46
-
47
- export class FormModel {
48
- /** The runtime engine that should be used */
49
- engine?: Engine
50
-
51
- /** the entire form JSON as an object */
52
- def: FormDefinition
53
-
54
- lists: FormDefinition['lists']
55
- sections: FormDefinition['sections'] = []
56
- name: string
57
- values: FormDefinition
58
- basePath: string
59
- conditions: Partial<Record<string, ExecutableCondition>>
60
- pages: PageControllerClass[]
61
- services: Services
62
-
63
- controllers?: Record<string, typeof PageController>
64
- pageDefMap: Map<string, Page>
65
- listDefMap: Map<string, List>
66
- componentDefMap: Map<string, ComponentDef>
67
- pageMap: Map<string, PageControllerClass>
68
- componentMap: Map<string, Component>
69
-
70
- constructor(
71
- def: typeof this.def,
72
- options: { basePath: string },
73
- services: Services = defaultServices,
74
- controllers?: Record<string, typeof PageController>
75
- ) {
76
- const result = formDefinitionSchema.validate(def, { abortEarly: false })
77
-
78
- if (result.error) {
79
- throw result.error
80
- }
81
-
82
- // Make a clone of the shallow copy returned
83
- // by joi so as not to change the source data.
84
- def = structuredClone(result.value)
85
-
86
- // Add default lists
87
- def.lists.push({
88
- name: '__yesNo',
89
- title: 'Yes/No',
90
- type: 'boolean',
91
- items: [
92
- {
93
- text: 'Yes',
94
- value: true
95
- },
96
- {
97
- text: 'No',
98
- value: false
99
- }
100
- ]
101
- })
102
-
103
- this.engine = def.engine
104
- this.def = def
105
- this.lists = def.lists
106
- this.sections = def.sections
107
- this.name = def.name ?? ''
108
- this.values = result.value
109
- this.basePath = options.basePath
110
- this.conditions = {}
111
- this.services = services
112
- this.controllers = controllers
113
-
114
- def.conditions.forEach((conditionDef) => {
115
- const condition = this.makeCondition(conditionDef)
116
- this.conditions[condition.name] = condition
117
- })
118
-
119
- this.pages = def.pages.map((pageDef) => createPage(this, pageDef))
120
-
121
- if (
122
- !def.pages.some(
123
- ({ controller }) =>
124
- // Check for user-provided status page (optional)
125
- controller === ControllerType.Status
126
- )
127
- ) {
128
- this.pages.push(
129
- createPage(this, {
130
- title: 'Form submitted',
131
- path: ControllerPath.Status,
132
- controller: ControllerType.Status
133
- })
134
- )
135
- }
136
-
137
- this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))
138
- this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))
139
- this.componentDefMap = new Map(
140
- def.pages
141
- .filter(hasComponents)
142
- .flatMap((page) =>
143
- page.components.map((component) => [component.name, component])
144
- )
145
- )
146
-
147
- this.pageMap = new Map(this.pages.map((page) => [page.path, page]))
148
- this.componentMap = new Map(
149
- this.pages.flatMap((page) =>
150
- page.collection.components.map((component) => [
151
- component.name,
152
- component
153
- ])
154
- )
155
- )
156
- }
157
-
158
- /**
159
- * build the entire model schema from individual pages/sections
160
- */
161
- makeSchema() {
162
- return this.makeFilteredSchema(this.pages)
163
- }
164
-
165
- /**
166
- * build the entire model schema from individual pages/sections and filter out answers
167
- * for pages which are no longer accessible due to an answer that has been changed
168
- */
169
- makeFilteredSchema(relevantPages: PageControllerClass[]) {
170
- // Build the entire model schema
171
- // from the individual pages/sections
172
- let schema = joi.object<FormSubmissionState>().required()
173
-
174
- relevantPages.forEach((page) => {
175
- schema = schema.concat(page.collection.stateSchema)
176
- })
177
-
178
- return schema
179
- }
180
-
181
- /**
182
- * Instantiates a Condition based on {@link ConditionWrapper}
183
- * @param condition
184
- */
185
- makeCondition(condition: ConditionWrapper): ExecutableCondition {
186
- const parser = new Parser({
187
- operators: {
188
- logical: true
189
- }
190
- })
191
-
192
- Object.assign(parser.functions, {
193
- dateForComparison(timePeriod: number, timeUnit: DateUnits) {
194
- return add(new Date(), { [timeUnit]: timePeriod }).toISOString()
195
- }
196
- })
197
-
198
- const { name, displayName, value } = condition
199
- const expr = this.toConditionExpression(value, parser)
200
-
201
- const fn = (evaluationState: FormState) => {
202
- const ctx = this.toConditionContext(evaluationState, this.conditions)
203
- try {
204
- return expr.evaluate(ctx) as boolean
205
- } catch {
206
- return false
207
- }
208
- }
209
-
210
- return {
211
- name,
212
- displayName,
213
- value,
214
- expr,
215
- fn
216
- }
217
- }
218
-
219
- toConditionContext(
220
- evaluationState: FormState,
221
- conditions: Partial<Record<string, ExecutableCondition>>
222
- ) {
223
- const context = { ...evaluationState }
224
-
225
- for (const key in conditions) {
226
- Object.defineProperty(context, key, {
227
- get() {
228
- return conditions[key]?.fn(evaluationState)
229
- }
230
- })
231
- }
232
-
233
- return context as Extract<Value, Record<string, Value>>
234
- }
235
-
236
- toConditionExpression(value: ConditionsModelData, parser: Parser) {
237
- const conditions = ConditionsModel.from(value)
238
- return parser.parse(conditions.toExpression())
239
- }
240
-
241
- getList(name: string): List | undefined {
242
- return this.lists.find((list) => list.name === name)
243
- }
244
-
245
- /**
246
- * Form context for the current page
247
- */
248
- getFormContext(
249
- request: FormContextRequest,
250
- state: FormState,
251
- errors?: FormSubmissionError[]
252
- ): FormContext {
253
- const { query } = request
254
-
255
- const page = getPage(this, request)
256
-
257
- // Determine form paths
258
- const currentPath = page.path
259
- const startPath = page.getStartPath()
260
-
261
- // Preview URL direct access is allowed
262
- const isForceAccess = 'force' in query
263
-
264
- let context: FormContext = {
265
- evaluationState: {},
266
- relevantState: {},
267
- relevantPages: [],
268
- payload: page.getFormDataFromState(request, state),
269
- state,
270
- paths: [],
271
- errors,
272
- isForceAccess,
273
- data: {},
274
- pageDefMap: this.pageDefMap,
275
- listDefMap: this.listDefMap,
276
- componentDefMap: this.componentDefMap,
277
- pageMap: this.pageMap,
278
- componentMap: this.componentMap
279
- }
280
-
281
- // Validate current page
282
- context = validateFormPayload(request, page, context)
283
-
284
- // Find start page
285
- let nextPage = findPage(this, startPath)
286
-
287
- this.initialiseContext(context)
288
-
289
- // Walk form pages from start
290
- while (nextPage) {
291
- // Add page to context
292
- context.relevantPages.push(nextPage)
293
-
294
- this.assignEvaluationState(context, nextPage)
295
-
296
- this.assignRelevantState(context, nextPage)
297
-
298
- // Stop at current page
299
- if (nextPage.path === currentPath) {
300
- break
301
- }
302
-
303
- // Apply conditions to determine next page
304
- nextPage = findPage(this, nextPage.getNextPath(context))
305
- }
306
-
307
- // Validate form state
308
- context = validateFormState(request, page, context)
309
-
310
- // Add paths for navigation
311
- this.assignPaths(context)
312
-
313
- return context
314
- }
315
-
316
- private initialiseContext(context: FormContext) {
317
- // For the V2 engine, we need to initialise `evaluationState` to null
318
- // for all keys. This is because the current condition evaluation
319
- // library (eval-expr) will throw if an expression uses a key that is undefined.
320
- if (this.engine === Engine.V2) {
321
- for (const page of this.pages) {
322
- for (const key of page.keys) {
323
- context.evaluationState[key] = null
324
- }
325
- }
326
- }
327
- }
328
-
329
- private assignEvaluationState(
330
- context: FormContext,
331
- page: PageControllerClass
332
- ) {
333
- const { collection, pageDef } = page
334
- // Skip evaluation state for repeater pages
335
-
336
- if (!hasRepeater(pageDef)) {
337
- Object.assign(
338
- context.evaluationState,
339
- collection.getContextValueFromState(context.state)
340
- )
341
- }
342
- }
343
-
344
- private assignRelevantState(context: FormContext, page: PageControllerClass) {
345
- // Copy relevant state by expected keys
346
- for (const key of page.keys) {
347
- if (typeof context.state[key] !== 'undefined') {
348
- context.relevantState[key] = context.state[key]
349
- }
350
- }
351
- }
352
-
353
- private assignPaths(context: FormContext) {
354
- for (const { keys, path } of context.relevantPages) {
355
- context.paths.push(path)
356
-
357
- // Stop at page with errors
358
- if (
359
- context.errors?.some(({ name, path }) => {
360
- return keys.includes(name) || keys.some((key) => path.includes(key))
361
- })
362
- ) {
363
- break
364
- }
365
- }
366
- }
367
- }
368
-
369
- /**
370
- * Validate current page only
371
- */
372
- function validateFormPayload(
373
- request: FormContextRequest,
374
- page: PageControllerClass,
375
- context: FormContext
376
- ): FormContext {
377
- const { collection } = page
378
- const { payload, state } = context
379
-
380
- const { action } = page.getFormParams(request)
381
-
382
- // Skip validation GET requests or other actions
383
- if (!request.payload || action !== FormAction.Validate) {
384
- return context
385
- }
386
-
387
- // For checkbox fields missing in the payload (i.e. unchecked),
388
- // explicitly set their value to undefined so that any previously
389
- // stored value is cleared and required field validation is enforced.
390
- const update = { ...request.payload }
391
- collection.fields.forEach((field) => {
392
- if (
393
- field.type === ComponentType.CheckboxesField &&
394
- !(field.name in update)
395
- ) {
396
- update[field.name] = undefined
397
- }
398
- })
399
-
400
- const { value, errors } = collection.validate({
401
- ...payload,
402
- ...update
403
- })
404
-
405
- // Add sanitised payload (ready to save)
406
- const formState = page.getStateFromValidForm(request, state, value)
407
-
408
- return {
409
- ...context,
410
- payload: merge(payload, value),
411
- state: merge(state, formState),
412
- errors
413
- }
414
- }
415
-
416
- /**
417
- * Validate entire form state
418
- */
419
- function validateFormState(
420
- request: FormContextRequest,
421
- page: PageControllerClass,
422
- context: FormContext
423
- ): FormContext {
424
- const { errors = [], relevantPages, relevantState } = context
425
-
426
- // Exclude current page
427
- const previousPages = relevantPages.filter(
428
- (relevantPage) => relevantPage !== page
429
- )
430
-
431
- // Validate relevant state
432
- const { error } = page.model
433
- .makeFilteredSchema(previousPages)
434
- .validate(relevantState, { ...opts, stripUnknown: true })
435
-
436
- // Add relevant state errors
437
- if (error) {
438
- const errorsState = error.details.map(getError)
439
- return { ...context, errors: errors.concat(errorsState) }
440
- }
441
-
442
- return context
443
- }
File without changes
@@ -1,209 +0,0 @@
1
- import {
2
- FormModel,
3
- SummaryViewModel
4
- } from '~/src/server/plugins/engine/models/index.js'
5
- import {
6
- createPage,
7
- type PageControllerClass
8
- } from '~/src/server/plugins/engine/pageControllers/helpers.js'
9
- import {
10
- type FormContext,
11
- type FormContextRequest,
12
- type FormState
13
- } from '~/src/server/plugins/engine/types.js'
14
- import definition from '~/test/form/definitions/repeat-mixed.js'
15
-
16
- const basePath = '/test'
17
-
18
- describe('SummaryViewModel', () => {
19
- const itemId1 = 'abc-123'
20
- const itemId2 = 'xyz-987'
21
-
22
- let model: FormModel
23
- let page: PageControllerClass
24
- let pageUrl: URL
25
- let request: FormContextRequest
26
- let context: FormContext
27
- let summaryViewModel: SummaryViewModel
28
-
29
- beforeEach(() => {
30
- model = new FormModel(definition, {
31
- basePath: 'test'
32
- })
33
-
34
- page = createPage(model, definition.pages[2])
35
- pageUrl = new URL('http://example.com/repeat/pizza-order/summary')
36
-
37
- request = {
38
- method: 'get',
39
- url: pageUrl,
40
- path: pageUrl.pathname,
41
- params: {
42
- path: 'pizza-order',
43
- slug: 'repeat'
44
- },
45
- query: {},
46
- app: { model }
47
- }
48
- })
49
-
50
- describe.each([
51
- {
52
- description: '0 items',
53
- state: {
54
- orderType: 'collection',
55
- pizza: []
56
- } satisfies FormState,
57
- keys: ['How would you like to receive your pizza?', 'Pizzas'],
58
- values: ['Collection', 'Not supplied']
59
- },
60
- {
61
- description: '1 item',
62
- state: {
63
- orderType: 'delivery',
64
- pizza: [
65
- {
66
- toppings: 'Ham',
67
- quantity: 2,
68
- itemId: itemId1
69
- }
70
- ]
71
- } satisfies FormState,
72
- keys: ['How would you like to receive your pizza?', 'Pizza added'],
73
- values: ['Delivery', 'You added 1 Pizza']
74
- },
75
- {
76
- description: '2 items',
77
- state: {
78
- orderType: 'delivery',
79
- pizza: [
80
- {
81
- toppings: 'Ham',
82
- quantity: 2,
83
- itemId: itemId1
84
- },
85
- {
86
- toppings: 'Pepperoni',
87
- quantity: 1,
88
- itemId: itemId2
89
- }
90
- ]
91
- } satisfies FormState,
92
- keys: ['How would you like to receive your pizza?', 'Pizzas added'],
93
- values: ['Delivery', 'You added 2 Pizzas']
94
- }
95
- ])('Check answers ($description)', ({ state, keys, values }) => {
96
- beforeEach(() => {
97
- context = model.getFormContext(request, state)
98
- summaryViewModel = new SummaryViewModel(request, page, context)
99
- })
100
-
101
- it('should add title for each section', () => {
102
- const [checkAnswers1, checkAnswers2] = summaryViewModel.checkAnswers
103
-
104
- // 1st summary list has no title
105
- expect(checkAnswers1).toHaveProperty('title', undefined)
106
-
107
- // 2nd summary list has section title
108
- expect(checkAnswers2).toHaveProperty('title', {
109
- text: 'Food'
110
- })
111
- })
112
-
113
- it('should add summary list for each section', () => {
114
- expect(summaryViewModel.checkAnswers).toHaveLength(2)
115
-
116
- const [checkAnswers1, checkAnswers2] = summaryViewModel.checkAnswers
117
-
118
- const { summaryList: summaryList1 } = checkAnswers1
119
- const { summaryList: summaryList2 } = checkAnswers2
120
-
121
- expect(summaryList1).toHaveProperty('rows', [
122
- {
123
- key: {
124
- text: keys[0]
125
- },
126
- value: {
127
- classes: 'app-prose-scope',
128
- html: values[0]
129
- },
130
- actions: {
131
- items: [
132
- {
133
- classes: 'govuk-link--no-visited-state',
134
- href: `${basePath}/delivery-or-collection?returnUrl=${encodeURIComponent(`${basePath}/summary`)}`,
135
- text: 'Change',
136
- visuallyHiddenText: keys[0]
137
- }
138
- ]
139
- }
140
- }
141
- ])
142
-
143
- expect(summaryList2).toHaveProperty('rows', [
144
- {
145
- key: {
146
- text: keys[1]
147
- },
148
- value: {
149
- classes: 'app-prose-scope',
150
- html: values[1]
151
- },
152
- actions: {
153
- items: [
154
- {
155
- classes: 'govuk-link--no-visited-state',
156
- href: `${basePath}/pizza-order/summary?returnUrl=${encodeURIComponent(`${basePath}/summary`)}`,
157
- text: 'Change',
158
- visuallyHiddenText: 'Pizza'
159
- }
160
- ]
161
- }
162
- }
163
- ])
164
- })
165
-
166
- it('should add summary list for each section (preview URL direct access)', () => {
167
- request.query.force = '' // Preview URL '?force'
168
- context = model.getFormContext(request, state)
169
- summaryViewModel = new SummaryViewModel(request, page, context)
170
-
171
- expect(summaryViewModel.checkAnswers).toHaveLength(2)
172
-
173
- const [checkAnswers1, checkAnswers2] = summaryViewModel.checkAnswers
174
-
175
- const { summaryList: summaryList1 } = checkAnswers1
176
- const { summaryList: summaryList2 } = checkAnswers2
177
-
178
- expect(summaryList1).toHaveProperty('rows', [
179
- {
180
- key: {
181
- text: keys[0]
182
- },
183
- value: {
184
- classes: 'app-prose-scope',
185
- html: values[0]
186
- },
187
- actions: {
188
- items: []
189
- }
190
- }
191
- ])
192
-
193
- expect(summaryList2).toHaveProperty('rows', [
194
- {
195
- key: {
196
- text: keys[1]
197
- },
198
- value: {
199
- classes: 'app-prose-scope',
200
- html: values[1]
201
- },
202
- actions: {
203
- items: []
204
- }
205
- }
206
- ])
207
- })
208
- })
209
- })