@defra/forms-engine-plugin 0.0.3 → 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 (328) 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/package.json +6 -2
  25. package/.browserslistrc +0 -16
  26. package/.editorconfig +0 -9
  27. package/.eslintrc.cjs +0 -266
  28. package/.github/dependabot.yml +0 -85
  29. package/.github/workflows/check-pull-request.yml +0 -209
  30. package/.github/workflows/pr-notifier.yml +0 -98
  31. package/.github/workflows/publish.yml +0 -111
  32. package/.husky/pre-commit +0 -1
  33. package/.lintstagedrc.js +0 -4
  34. package/.nvmrc +0 -1
  35. package/.prettierignore +0 -8
  36. package/.prettierrc.cjs +0 -26
  37. package/Dockerfile +0 -61
  38. package/Procfile +0 -1
  39. package/babel.config.cjs +0 -55
  40. package/compose/aws.env +0 -4
  41. package/compose/start-localstack.sh +0 -26
  42. package/docker-compose.yaml +0 -86
  43. package/globals.d.ts +0 -1
  44. package/jest.config.cjs +0 -54
  45. package/jest.environment.js +0 -4
  46. package/jest.setup.cjs +0 -14
  47. package/postcss.config.js +0 -26
  48. package/sonar-project.properties +0 -17
  49. package/src/client/javascripts/application.js +0 -87
  50. package/src/client/javascripts/file-upload.js +0 -386
  51. package/src/client/stylesheets/_code.scss +0 -33
  52. package/src/client/stylesheets/_govuk-frontend.scss +0 -4
  53. package/src/client/stylesheets/_prose.scss +0 -56
  54. package/src/client/stylesheets/_service-banner.scss +0 -24
  55. package/src/client/stylesheets/_summary-list.scss +0 -28
  56. package/src/client/stylesheets/_tag-env.scss +0 -24
  57. package/src/client/stylesheets/application.scss +0 -14
  58. package/src/common/cookies.js +0 -58
  59. package/src/common/cookies.test.js +0 -23
  60. package/src/common/types.js +0 -5
  61. package/src/config/index.ts +0 -271
  62. package/src/index.ts +0 -31
  63. package/src/server/common/helpers/logging/logger-options.test.ts +0 -50
  64. package/src/server/common/helpers/logging/logger-options.ts +0 -46
  65. package/src/server/common/helpers/logging/logger.ts +0 -7
  66. package/src/server/common/helpers/logging/request-logger.ts +0 -9
  67. package/src/server/common/helpers/logging/request-tracing.js +0 -10
  68. package/src/server/common/helpers/redis-client.js +0 -70
  69. package/src/server/constants.js +0 -1
  70. package/src/server/forms/README.md +0 -10
  71. package/src/server/forms/components.json +0 -1015
  72. package/src/server/forms/report-a-terrorist.json +0 -270
  73. package/src/server/forms/runner-components-test.json +0 -365
  74. package/src/server/forms/test.json +0 -581
  75. package/src/server/index.test.ts +0 -582
  76. package/src/server/index.ts +0 -140
  77. package/src/server/plugins/blankie.test.ts +0 -73
  78. package/src/server/plugins/blankie.ts +0 -48
  79. package/src/server/plugins/crumb.ts +0 -20
  80. package/src/server/plugins/engine/README.md +0 -87
  81. package/src/server/plugins/engine/components/AutocompleteField.test.ts +0 -294
  82. package/src/server/plugins/engine/components/AutocompleteField.ts +0 -49
  83. package/src/server/plugins/engine/components/CheckboxesField.test.ts +0 -379
  84. package/src/server/plugins/engine/components/CheckboxesField.ts +0 -106
  85. package/src/server/plugins/engine/components/ComponentBase.ts +0 -97
  86. package/src/server/plugins/engine/components/ComponentCollection.ts +0 -278
  87. package/src/server/plugins/engine/components/DatePartsField.test.ts +0 -822
  88. package/src/server/plugins/engine/components/DatePartsField.ts +0 -264
  89. package/src/server/plugins/engine/components/Details.test.ts +0 -49
  90. package/src/server/plugins/engine/components/Details.ts +0 -30
  91. package/src/server/plugins/engine/components/EmailAddressField.test.ts +0 -395
  92. package/src/server/plugins/engine/components/EmailAddressField.ts +0 -55
  93. package/src/server/plugins/engine/components/FileUploadField.test.ts +0 -778
  94. package/src/server/plugins/engine/components/FileUploadField.ts +0 -262
  95. package/src/server/plugins/engine/components/FormComponent.ts +0 -249
  96. package/src/server/plugins/engine/components/Html.test.ts +0 -48
  97. package/src/server/plugins/engine/components/Html.ts +0 -29
  98. package/src/server/plugins/engine/components/InsetText.test.ts +0 -48
  99. package/src/server/plugins/engine/components/InsetText.ts +0 -27
  100. package/src/server/plugins/engine/components/List.test.ts +0 -76
  101. package/src/server/plugins/engine/components/List.ts +0 -72
  102. package/src/server/plugins/engine/components/ListFormComponent.ts +0 -140
  103. package/src/server/plugins/engine/components/MonthYearField.test.ts +0 -567
  104. package/src/server/plugins/engine/components/MonthYearField.ts +0 -222
  105. package/src/server/plugins/engine/components/MultilineTextField.test.ts +0 -558
  106. package/src/server/plugins/engine/components/MultilineTextField.ts +0 -138
  107. package/src/server/plugins/engine/components/NumberField.test.ts +0 -701
  108. package/src/server/plugins/engine/components/NumberField.ts +0 -163
  109. package/src/server/plugins/engine/components/RadiosField.test.ts +0 -288
  110. package/src/server/plugins/engine/components/RadiosField.ts +0 -24
  111. package/src/server/plugins/engine/components/SelectField.test.ts +0 -288
  112. package/src/server/plugins/engine/components/SelectField.ts +0 -47
  113. package/src/server/plugins/engine/components/SelectionControlField.ts +0 -43
  114. package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +0 -356
  115. package/src/server/plugins/engine/components/TelephoneNumberField.ts +0 -67
  116. package/src/server/plugins/engine/components/TextField.test.ts +0 -489
  117. package/src/server/plugins/engine/components/TextField.ts +0 -96
  118. package/src/server/plugins/engine/components/UkAddressField.test.ts +0 -623
  119. package/src/server/plugins/engine/components/UkAddressField.ts +0 -172
  120. package/src/server/plugins/engine/components/YesNoField.test.ts +0 -248
  121. package/src/server/plugins/engine/components/YesNoField.ts +0 -31
  122. package/src/server/plugins/engine/components/constants.ts +0 -1
  123. package/src/server/plugins/engine/components/helpers.ts +0 -330
  124. package/src/server/plugins/engine/components/index.ts +0 -24
  125. package/src/server/plugins/engine/components/types.ts +0 -117
  126. package/src/server/plugins/engine/configureEnginePlugin.ts +0 -47
  127. package/src/server/plugins/engine/helpers.test.ts +0 -791
  128. package/src/server/plugins/engine/helpers.ts +0 -379
  129. package/src/server/plugins/engine/index.ts +0 -7
  130. package/src/server/plugins/engine/models/FormModel.test.ts +0 -42
  131. package/src/server/plugins/engine/models/FormModel.ts +0 -443
  132. package/src/server/plugins/engine/models/RepeatingSummaryViewModel.ts +0 -0
  133. package/src/server/plugins/engine/models/Section.ts +0 -0
  134. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +0 -209
  135. package/src/server/plugins/engine/models/SummaryViewModel.ts +0 -220
  136. package/src/server/plugins/engine/models/index.ts +0 -2
  137. package/src/server/plugins/engine/models/types.ts +0 -114
  138. package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +0 -143
  139. package/src/server/plugins/engine/outputFormatters/human/v1.ts +0 -73
  140. package/src/server/plugins/engine/outputFormatters/index.test.ts +0 -17
  141. package/src/server/plugins/engine/outputFormatters/index.ts +0 -44
  142. package/src/server/plugins/engine/outputFormatters/machine/v1.test.ts +0 -229
  143. package/src/server/plugins/engine/outputFormatters/machine/v1.ts +0 -140
  144. package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +0 -229
  145. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +0 -153
  146. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +0 -1108
  147. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +0 -446
  148. package/src/server/plugins/engine/pageControllers/PageController.test.ts +0 -205
  149. package/src/server/plugins/engine/pageControllers/PageController.ts +0 -176
  150. package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +0 -1264
  151. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +0 -561
  152. package/src/server/plugins/engine/pageControllers/README.md +0 -28
  153. package/src/server/plugins/engine/pageControllers/RepeatPageController.test.ts +0 -264
  154. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +0 -458
  155. package/src/server/plugins/engine/pageControllers/StartPageController.ts +0 -18
  156. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +0 -50
  157. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +0 -261
  158. package/src/server/plugins/engine/pageControllers/TerminalController.test.ts +0 -28
  159. package/src/server/plugins/engine/pageControllers/TerminalPageController.ts +0 -19
  160. package/src/server/plugins/engine/pageControllers/helpers.test.ts +0 -198
  161. package/src/server/plugins/engine/pageControllers/helpers.ts +0 -101
  162. package/src/server/plugins/engine/pageControllers/index.ts +0 -10
  163. package/src/server/plugins/engine/pageControllers/validationOptions.ts +0 -89
  164. package/src/server/plugins/engine/plugin.ts +0 -673
  165. package/src/server/plugins/engine/services/formSubmissionService.js +0 -46
  166. package/src/server/plugins/engine/services/formsService.js +0 -46
  167. package/src/server/plugins/engine/services/formsService.test.js +0 -90
  168. package/src/server/plugins/engine/services/index.js +0 -3
  169. package/src/server/plugins/engine/services/notifyService.test.ts +0 -132
  170. package/src/server/plugins/engine/services/notifyService.ts +0 -64
  171. package/src/server/plugins/engine/services/uploadService.js +0 -60
  172. package/src/server/plugins/engine/types.ts +0 -315
  173. package/src/server/plugins/engine/views/components/autocompletefield.html +0 -5
  174. package/src/server/plugins/engine/views/components/checkboxesfield.html +0 -5
  175. package/src/server/plugins/engine/views/components/datepartsfield.html +0 -5
  176. package/src/server/plugins/engine/views/components/details.html +0 -6
  177. package/src/server/plugins/engine/views/components/emailaddressfield.html +0 -5
  178. package/src/server/plugins/engine/views/components/fileuploadfield-key.html +0 -8
  179. package/src/server/plugins/engine/views/components/fileuploadfield-value.html +0 -3
  180. package/src/server/plugins/engine/views/components/fileuploadfield.html +0 -24
  181. package/src/server/plugins/engine/views/components/html.html +0 -3
  182. package/src/server/plugins/engine/views/components/insettext.html +0 -7
  183. package/src/server/plugins/engine/views/components/list.html +0 -36
  184. package/src/server/plugins/engine/views/components/monthyearfield.html +0 -5
  185. package/src/server/plugins/engine/views/components/multilinetextfield.html +0 -10
  186. package/src/server/plugins/engine/views/components/numberfield.html +0 -5
  187. package/src/server/plugins/engine/views/components/radiosfield.html +0 -5
  188. package/src/server/plugins/engine/views/components/selectfield.html +0 -5
  189. package/src/server/plugins/engine/views/components/telephonenumberfield.html +0 -5
  190. package/src/server/plugins/engine/views/components/textfield.html +0 -5
  191. package/src/server/plugins/engine/views/components/ukaddressfield.html +0 -25
  192. package/src/server/plugins/engine/views/components/yesnofield.html +0 -5
  193. package/src/server/plugins/engine/views/file-upload.html +0 -45
  194. package/src/server/plugins/engine/views/index.html +0 -39
  195. package/src/server/plugins/engine/views/item-delete.html +0 -56
  196. package/src/server/plugins/engine/views/partials/components.html +0 -6
  197. package/src/server/plugins/engine/views/partials/conditional-components.html +0 -3
  198. package/src/server/plugins/engine/views/partials/debug.html +0 -44
  199. package/src/server/plugins/engine/views/partials/form.html +0 -15
  200. package/src/server/plugins/engine/views/partials/heading.html +0 -16
  201. package/src/server/plugins/engine/views/partials/preview-banner.html +0 -32
  202. package/src/server/plugins/engine/views/partials/preview-banner.test.js +0 -122
  203. package/src/server/plugins/engine/views/partials/warn-missing-notification-email.html +0 -10
  204. package/src/server/plugins/engine/views/repeat-list-summary.html +0 -53
  205. package/src/server/plugins/errorPages.ts +0 -58
  206. package/src/server/plugins/nunjucks/context.js +0 -88
  207. package/src/server/plugins/nunjucks/context.test.js +0 -142
  208. package/src/server/plugins/nunjucks/enviroment.test.js +0 -201
  209. package/src/server/plugins/nunjucks/environment.js +0 -116
  210. package/src/server/plugins/nunjucks/filters/answer.js +0 -27
  211. package/src/server/plugins/nunjucks/filters/answer.test.js +0 -89
  212. package/src/server/plugins/nunjucks/filters/evaluate.js +0 -21
  213. package/src/server/plugins/nunjucks/filters/field.js +0 -28
  214. package/src/server/plugins/nunjucks/filters/field.test.js +0 -75
  215. package/src/server/plugins/nunjucks/filters/highlight.js +0 -11
  216. package/src/server/plugins/nunjucks/filters/href.js +0 -30
  217. package/src/server/plugins/nunjucks/filters/href.test.js +0 -80
  218. package/src/server/plugins/nunjucks/filters/index.js +0 -8
  219. package/src/server/plugins/nunjucks/filters/inspect.js +0 -15
  220. package/src/server/plugins/nunjucks/filters/page.js +0 -24
  221. package/src/server/plugins/nunjucks/filters/page.test.js +0 -65
  222. package/src/server/plugins/nunjucks/index.js +0 -3
  223. package/src/server/plugins/nunjucks/plugin.js +0 -40
  224. package/src/server/plugins/nunjucks/render.js +0 -42
  225. package/src/server/plugins/nunjucks/types.js +0 -40
  226. package/src/server/plugins/pulse.ts +0 -11
  227. package/src/server/plugins/router.ts +0 -201
  228. package/src/server/plugins/session.ts +0 -28
  229. package/src/server/routes/health.js +0 -13
  230. package/src/server/routes/health.test.js +0 -35
  231. package/src/server/routes/index.test.ts +0 -125
  232. package/src/server/routes/index.ts +0 -2
  233. package/src/server/routes/public.ts +0 -47
  234. package/src/server/routes/types.ts +0 -48
  235. package/src/server/schemas/index.ts +0 -34
  236. package/src/server/secure-context.js +0 -43
  237. package/src/server/services/cacheService.test.ts +0 -276
  238. package/src/server/services/cacheService.ts +0 -131
  239. package/src/server/services/httpService.test.js +0 -491
  240. package/src/server/services/httpService.ts +0 -50
  241. package/src/server/services/index.ts +0 -1
  242. package/src/server/types.ts +0 -54
  243. package/src/server/utils/notify.test.ts +0 -37
  244. package/src/server/utils/notify.ts +0 -50
  245. package/src/server/utils/secure-context/get-trust-store-certs.js +0 -11
  246. package/src/server/utils/secure-context/get-trust-store-certs.test.js +0 -19
  247. package/src/server/utils/utils.js +0 -24
  248. package/src/server/utils/utils.test.js +0 -54
  249. package/src/server/views/404.html +0 -16
  250. package/src/server/views/500.html +0 -19
  251. package/src/server/views/components/debug/macro.njk +0 -3
  252. package/src/server/views/components/debug/template.njk +0 -13
  253. package/src/server/views/components/service-banner/macro.njk +0 -3
  254. package/src/server/views/components/service-banner/template.njk +0 -20
  255. package/src/server/views/components/service-banner/template.test.js +0 -43
  256. package/src/server/views/components/tag-env/macro.njk +0 -3
  257. package/src/server/views/components/tag-env/template.njk +0 -30
  258. package/src/server/views/components/tag-env/template.test.js +0 -66
  259. package/src/server/views/confirmation.html +0 -19
  260. package/src/server/views/help/accessibility-statement.html +0 -58
  261. package/src/server/views/help/cookie-preferences.html +0 -57
  262. package/src/server/views/help/cookies.html +0 -71
  263. package/src/server/views/help/get-support.html +0 -37
  264. package/src/server/views/help/privacy-notice.html +0 -68
  265. package/src/server/views/help/terms-and-conditions.html +0 -83
  266. package/src/server/views/layout.html +0 -199
  267. package/src/server/views/summary.html +0 -50
  268. package/src/typings/hapi/index.d.ts +0 -95
  269. package/src/typings/hapi-tracing/index.d.ts +0 -6
  270. package/src/typings/index.d.ts +0 -3
  271. package/src/typings/joi/index.d.ts +0 -22
  272. package/stylelint.config.js +0 -10
  273. package/test/client/javascripts/file-upload.test.js +0 -1197
  274. package/test/condition/checkboxes.test.js +0 -112
  275. package/test/condition/radios.test.js +0 -112
  276. package/test/condition/text.test.js +0 -103
  277. package/test/fixtures/assets-manifest.json +0 -4
  278. package/test/fixtures/form.js +0 -86
  279. package/test/fixtures/index.js +0 -2
  280. package/test/fixtures/list.js +0 -92
  281. package/test/form/cookies.test.js +0 -338
  282. package/test/form/csrf.test.js +0 -87
  283. package/test/form/definitions/basic.js +0 -101
  284. package/test/form/definitions/blank.js +0 -10
  285. package/test/form/definitions/checkboxes.json +0 -88
  286. package/test/form/definitions/components.json +0 -452
  287. package/test/form/definitions/conditional-reveal.js +0 -140
  288. package/test/form/definitions/conditions-basic.js +0 -187
  289. package/test/form/definitions/conditions-complex.js +0 -338
  290. package/test/form/definitions/conditions-dates.js +0 -78
  291. package/test/form/definitions/conditions-escaping.js +0 -143
  292. package/test/form/definitions/demo-cph-number.js +0 -3099
  293. package/test/form/definitions/feedback.json +0 -45
  294. package/test/form/definitions/fields-optional.js +0 -402
  295. package/test/form/definitions/fields-required.js +0 -402
  296. package/test/form/definitions/file-upload-basic.js +0 -44
  297. package/test/form/definitions/file-upload.js +0 -66
  298. package/test/form/definitions/minimal.js +0 -39
  299. package/test/form/definitions/phase-alpha.json +0 -33
  300. package/test/form/definitions/phase-default.json +0 -26
  301. package/test/form/definitions/radios.json +0 -88
  302. package/test/form/definitions/repeat-mixed.js +0 -54
  303. package/test/form/definitions/repeat.js +0 -70
  304. package/test/form/definitions/status.json +0 -126
  305. package/test/form/definitions/templates.js +0 -183
  306. package/test/form/definitions/test.json +0 -581
  307. package/test/form/definitions/text.json +0 -75
  308. package/test/form/definitions/titles.json +0 -170
  309. package/test/form/definitions.test.js +0 -47
  310. package/test/form/exit-page.test.js +0 -210
  311. package/test/form/feedback.test.js +0 -68
  312. package/test/form/fields-optional.test.js +0 -237
  313. package/test/form/fields-required.test.js +0 -294
  314. package/test/form/file-upload.test.js +0 -313
  315. package/test/form/govuk-notify.test.js +0 -449
  316. package/test/form/journey-basic.test.js +0 -444
  317. package/test/form/persist-files.test.js +0 -227
  318. package/test/form/phase-banner.test.js +0 -71
  319. package/test/form/repeat.test.js +0 -628
  320. package/test/form/summary-submission-email.test.js +0 -95
  321. package/test/form/template.test.js +0 -288
  322. package/test/form/titles.test.js +0 -204
  323. package/test/helpers/component-helpers.js +0 -74
  324. package/test/utils/get-cookie.js +0 -42
  325. package/test/utils/get-form-definitions.js +0 -18
  326. package/tmp.pdf +0 -1
  327. package/tsconfig.json +0 -28
  328. package/webpack.config.js +0 -208
@@ -1,1264 +0,0 @@
1
- import { type PageQuestion } from '@defra/forms-model'
2
- import { type ResponseToolkit } from '@hapi/hapi'
3
-
4
- import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
5
- import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
6
- import {
7
- type FormContext,
8
- type FormContextRequest,
9
- type FormPageViewModel,
10
- type FormState,
11
- type FormSubmissionState
12
- } from '~/src/server/plugins/engine/types.js'
13
- import { type FormRequest } from '~/src/server/routes/types.js'
14
- import { CacheService } from '~/src/server/services/cacheService.js'
15
- import conditionalReveal from '~/test/form/definitions/conditional-reveal.js'
16
- import definitionConditionsBasic, {
17
- V2 as definitionConditionsBasicV2
18
- } from '~/test/form/definitions/conditions-basic.js'
19
- import definitionConditionsComplex from '~/test/form/definitions/conditions-complex.js'
20
- import definitionConditionsDates from '~/test/form/definitions/conditions-dates.js'
21
-
22
- describe('QuestionPageController', () => {
23
- let page1: PageQuestion
24
- let page1Url: URL
25
-
26
- let page2: PageQuestion
27
- let page2Url: URL
28
-
29
- let model: FormModel
30
- let controller1: QuestionPageController
31
- let controller2: QuestionPageController
32
- let requestPage1: FormRequest
33
- let requestPage2: FormRequest
34
-
35
- beforeEach(() => {
36
- const { pages } = definitionConditionsBasic
37
-
38
- page1 = pages[0]
39
- page1Url = new URL('http://example.com/test/first-page')
40
-
41
- page2 = pages[1]
42
- page2Url = new URL('http://example.com/test/second-page')
43
-
44
- model = new FormModel(definitionConditionsBasic, {
45
- basePath: 'test'
46
- })
47
-
48
- controller1 = new QuestionPageController(model, page1)
49
- controller2 = new QuestionPageController(model, page2)
50
-
51
- requestPage1 = {
52
- method: 'get',
53
- url: page1Url,
54
- path: page1Url.pathname,
55
- params: {
56
- path: 'first-page',
57
- slug: 'test'
58
- },
59
- query: {},
60
- app: { model }
61
- } as FormRequest
62
-
63
- requestPage2 = {
64
- method: 'get',
65
- url: page2Url,
66
- path: page2Url.pathname,
67
- params: {
68
- path: 'second-page',
69
- slug: 'test'
70
- },
71
- query: {},
72
- app: { model }
73
- } as FormRequest
74
- })
75
-
76
- describe('Properties', () => {
77
- it('returns path', () => {
78
- expect(controller1).toHaveProperty('path', '/first-page')
79
- expect(controller2).toHaveProperty('path', '/second-page')
80
- })
81
-
82
- it('returns href', () => {
83
- expect(controller1).toHaveProperty('href', '/test/first-page')
84
- expect(controller2).toHaveProperty('href', '/test/second-page')
85
- })
86
-
87
- it('returns keys', () => {
88
- expect(controller1).toHaveProperty('keys', ['yesNoField'])
89
- expect(controller2).toHaveProperty('keys', [
90
- 'dateField',
91
- 'dateField__day',
92
- 'dateField__month',
93
- 'dateField__year',
94
- 'multilineTextField'
95
- ])
96
- })
97
-
98
- it('returns the page section', () => {
99
- expect(controller1).toHaveProperty('section', undefined)
100
- expect(controller2).toHaveProperty('section', {
101
- name: 'marriage',
102
- title: 'Your marriage',
103
- hideTitle: false
104
- })
105
- })
106
- })
107
-
108
- describe('Path methods', () => {
109
- describe('Next getter', () => {
110
- it('returns page links', () => {
111
- expect(controller1).toHaveProperty('next', [
112
- { path: '/second-page' },
113
- { path: '/summary', condition: 'isPreviouslyMarried' }
114
- ])
115
-
116
- expect(controller2).toHaveProperty('next', [{ path: '/summary' }])
117
- })
118
-
119
- it('returns page links (none found)', () => {
120
- const pageDef1 = structuredClone(controller1.pageDef)
121
- const pageDef2 = structuredClone(controller2.pageDef)
122
-
123
- controller1.pageDef = pageDef1
124
- controller2.pageDef = pageDef2
125
-
126
- // @ts-expect-error - Allow invalid property for test
127
- controller1.pageDef.next = []
128
-
129
- // @ts-expect-error - Allow invalid property for test
130
- delete controller2.pageDef.next
131
-
132
- expect(controller1).toHaveProperty('next', [])
133
- expect(controller2).toHaveProperty('next', [])
134
- })
135
- })
136
- })
137
-
138
- describe('Component collection', () => {
139
- it('returns the components for the page', () => {
140
- const { components: components1 } = controller1.collection
141
- const { components: components2 } = controller2.collection
142
-
143
- expect(components1).toHaveLength(1)
144
- expect(components1[0].name).toBe('yesNoField')
145
-
146
- expect(components2).toHaveLength(3)
147
- expect(components2[0].name).toBe('detailsField')
148
- expect(components2[1].name).toBe('dateField')
149
- expect(components2[2].name).toBe('multilineTextField')
150
- })
151
-
152
- it('returns the fields for the page', () => {
153
- const { fields: fields1 } = controller1.collection
154
- const { fields: fields2 } = controller2.collection
155
-
156
- expect(fields1).toHaveLength(1)
157
- expect(fields1[0].name).toBe('yesNoField')
158
-
159
- expect(fields2).toHaveLength(2)
160
- expect(fields2[0].name).toBe('dateField')
161
- expect(fields2[1].name).toBe('multilineTextField')
162
- })
163
-
164
- it('returns the guidance for the page', () => {
165
- const { guidance: guidance1 } = controller1.collection
166
- const { guidance: guidance2 } = controller2.collection
167
-
168
- expect(guidance1).toHaveLength(0)
169
- expect(guidance2).toHaveLength(1)
170
- expect(guidance2[0].name).toBe('detailsField')
171
- })
172
- })
173
-
174
- describe('Component view models', () => {
175
- let viewModel1: FormPageViewModel
176
- let viewModel2: FormPageViewModel
177
-
178
- beforeEach(() => {
179
- viewModel1 = controller1.getViewModel(
180
- requestPage1,
181
- model.getFormContext(requestPage1, {})
182
- )
183
-
184
- viewModel2 = controller2.getViewModel(
185
- requestPage2,
186
- model.getFormContext(requestPage2, {})
187
- )
188
- })
189
-
190
- it('hides the page title for single form component pages', () => {
191
- // Page 1 hides page title (single component)
192
- expect(viewModel1).toHaveProperty('showTitle', false)
193
- expect(viewModel1).toHaveProperty('pageTitle', 'Previous marriages')
194
-
195
- // Page 2 shows page title (multiple components)
196
- expect(viewModel2).toHaveProperty('showTitle', true)
197
- expect(viewModel2).toHaveProperty(
198
- 'pageTitle',
199
- 'When will you get married?'
200
- )
201
- })
202
-
203
- it('returns the component view models for the page', () => {
204
- const { components: components1 } = viewModel1
205
- const { components: components2 } = viewModel2
206
-
207
- expect(components1).toHaveLength(1)
208
- expect(components2).toHaveLength(3)
209
-
210
- // Page 1, component 1, default label
211
- expect(components1[0].model).toHaveProperty('label', {
212
- text: 'Have you previously been married?'
213
- })
214
-
215
- // Page 1, component 1, optional legend
216
- expect(components1[0].model).toHaveProperty('fieldset', {
217
- legend: {
218
- text: 'Previous marriages',
219
- classes: 'govuk-fieldset__legend--l',
220
- isPageHeading: true
221
- }
222
- })
223
-
224
- // Page 2, component 1, content only
225
- expect(components2[0].model).toEqual({
226
- attributes: {},
227
- summaryHtml: 'Find out more',
228
- html: 'Some content goes here'
229
- })
230
-
231
- // Page 2, component 2, default label
232
- expect(components2[1].model).toHaveProperty('label', {
233
- text: 'Date of marriage',
234
- classes: 'govuk-label--m'
235
- })
236
-
237
- // Page 2, component 2, optional legend
238
- expect(components2[1].model).toHaveProperty('fieldset', {
239
- legend: {
240
- text: 'Date of marriage',
241
- classes: 'govuk-fieldset__legend--m'
242
- }
243
- })
244
-
245
- // Page 2, component 3, default label
246
- expect(components2[2].model).toHaveProperty('label', {
247
- text: 'Remarks',
248
- classes: 'govuk-label--m'
249
- })
250
-
251
- // Page 2, component 3, optional legend
252
- expect(components2[2].model).not.toHaveProperty('fieldset')
253
- })
254
- })
255
-
256
- describe('Condition evaluation context', () => {
257
- it('filters state by journey pages', () => {
258
- const { pages } = definitionConditionsComplex
259
-
260
- const model = new FormModel(definitionConditionsComplex, {
261
- basePath: 'test'
262
- })
263
-
264
- // Selected page appears after convergence and contains a conditional field
265
- // This is the page we're theoretically browsing to
266
- const controller = new QuestionPageController(model, pages[7])
267
-
268
- // The state below shows we said we had a UKPassport and entered details for an applicant
269
- const state: FormSubmissionState = {
270
- ukPassport: true,
271
- numberOfApplicants: 2,
272
- applicantOneFirstName: 'Enrique',
273
- applicantOneMiddleName: null,
274
- applicantOneLastName: 'Chase',
275
- applicantOneAddress__addressLine1: 'AddressLine1',
276
- applicantOneAddress__addressLine2: 'AddressLine2',
277
- applicantOneAddress__town: 'Town',
278
- applicantOneAddress__postcode: 'Postcode',
279
- applicantTwoFirstName: 'John',
280
- applicantTwoMiddleName: null,
281
- applicantTwoLastName: 'Doe',
282
- applicantTwoAddress__addressLine1: 'AddressLine1',
283
- applicantTwoAddress__addressLine2: 'AddressLine2',
284
- applicantTwoAddress__town: 'Town',
285
- applicantTwoAddress__postcode: 'Postcode'
286
- }
287
-
288
- let request = {
289
- method: 'get',
290
- url: new URL('http://example.com/test/applicant-one-address'),
291
- path: '/test/applicant-one-address',
292
- params: {
293
- path: 'applicant-one-address',
294
- slug: 'test'
295
- },
296
- query: {},
297
- app: { model }
298
- } satisfies FormContextRequest
299
-
300
- // Calculate our context based on the page we're attempting to load and the above state we provide
301
- let context = controller.model.getFormContext(request, state)
302
- const { evaluationState: stateBefore } = context
303
-
304
- // Our context should know our first applicant
305
- expect(stateBefore).toEqual({
306
- numberOfApplicants: 2,
307
- ukPassport: true,
308
- applicantOneFirstName: 'Enrique',
309
- applicantOneMiddleName: null,
310
- applicantOneLastName: 'Chase',
311
- applicantOneAddress: [
312
- 'AddressLine1',
313
- 'AddressLine2',
314
- 'Town',
315
- 'Postcode'
316
- ]
317
- })
318
-
319
- // Our context should know which pages are relevant
320
- expect(context.paths).toEqual([
321
- '/uk-passport',
322
- '/how-many-people',
323
- '/applicant-one-name',
324
- '/applicant-one-address'
325
- ])
326
-
327
- // Now mark that we don't have a UK Passport
328
- state.ukPassport = false
329
-
330
- request = {
331
- method: 'get',
332
- url: new URL('http://example.com/test/summary'),
333
- path: '/test/summary',
334
- params: {
335
- path: 'summary',
336
- slug: 'test'
337
- },
338
- query: {},
339
- app: { model }
340
- } satisfies FormContextRequest
341
-
342
- // And recalculate our context
343
- context = controller.model.getFormContext(request, state)
344
- const { evaluationState: stateAfter } = context
345
-
346
- // Our context should no longer list pages about our applicant
347
- expect(context.paths).toEqual([
348
- '/uk-passport',
349
- '/testconditions',
350
- '/summary'
351
- ])
352
-
353
- // Our context should no longer know anything about our applicant
354
- expect(stateAfter).not.toHaveProperty('numberOfApplicants')
355
- expect(stateAfter).not.toHaveProperty('applicantOneFirstName')
356
- expect(stateAfter).not.toHaveProperty('applicantOneMiddleName')
357
- expect(stateAfter).not.toHaveProperty('applicantOneLastName')
358
- expect(stateAfter).not.toHaveProperty('applicantOneAddress')
359
-
360
- expect(stateAfter).toEqual({
361
- ukPassport: false
362
- })
363
- })
364
-
365
- it('combines state values for date fields', () => {
366
- const { pages } = definitionConditionsDates
367
-
368
- const model = new FormModel(definitionConditionsDates, {
369
- basePath: 'test'
370
- })
371
-
372
- const controller = new QuestionPageController(model, pages[0])
373
-
374
- const request = {
375
- method: 'get',
376
- url: new URL('http://example.com/test/page-one'),
377
- path: '/test/page-one',
378
- params: {
379
- path: 'page-one',
380
- slug: 'test'
381
- },
382
- query: {},
383
- app: { model }
384
- } satisfies FormContextRequest
385
-
386
- const context = controller.model.getFormContext(request, {
387
- dateField__day: 5,
388
- dateField__month: 1,
389
- dateField__year: 2024
390
- })
391
-
392
- // Ensure dates are transformed to yyyy-MM-dd format
393
- expect(context.evaluationState).toHaveProperty('dateField', '2024-01-05')
394
-
395
- // Unlike relevant state which has the individual date parts
396
- expect(context.relevantState).not.toHaveProperty('dateField')
397
- expect(context.relevantState).toMatchObject({
398
- dateField__day: 5,
399
- dateField__month: 1,
400
- dateField__year: 2024
401
- })
402
- })
403
-
404
- it('filters on condition A', () => {
405
- const { pages } = conditionalReveal
406
-
407
- const model = new FormModel(conditionalReveal, {
408
- basePath: 'test'
409
- })
410
-
411
- const controller = new QuestionPageController(model, pages[0])
412
-
413
- // The state below shows we said we had a UKPassport and entered details for an applicant
414
- const state: FormSubmissionState = {}
415
-
416
- const request = {
417
- method: 'get',
418
- url: new URL('http://example.com/test/page-one'),
419
- path: '/test/first-page',
420
- params: {
421
- path: 'first-page',
422
- slug: 'test'
423
- },
424
- query: {},
425
- app: { model }
426
- } satisfies FormContextRequest
427
-
428
- const context = controller.model.getFormContext(request, state)
429
- const evaluationState = { animalType: 'Barn owl' }
430
-
431
- const viewModel = controller.getViewModel(request, context)
432
-
433
- const filtered = controller.filterConditionalComponents(
434
- viewModel,
435
- model,
436
- evaluationState
437
- )
438
-
439
- expect(filtered).toHaveLength(2)
440
- expect(filtered[0].model.content).toBe('This is info for Barn owls')
441
- expect(filtered[1].model.label?.text).toBe('Select from the list')
442
- expect(filtered[1].model.items).toEqual([
443
- {
444
- checked: false,
445
- condition: 'isBarnOwl',
446
- selected: false,
447
- text: 'Option 1',
448
- value: '1'
449
- },
450
- {
451
- checked: false,
452
- condition: 'isBarnOwl',
453
- selected: false,
454
- text: 'Option 2',
455
- value: '2'
456
- }
457
- ])
458
- })
459
-
460
- it('filters on condition B', () => {
461
- const { pages } = conditionalReveal
462
-
463
- const model = new FormModel(conditionalReveal, {
464
- basePath: 'test'
465
- })
466
-
467
- const controller = new QuestionPageController(model, pages[0])
468
-
469
- // The state below shows we said we had a UKPassport and entered details for an applicant
470
- const state: FormSubmissionState = {}
471
-
472
- const request = {
473
- method: 'get',
474
- url: new URL('http://example.com/test/page-one'),
475
- path: '/test/first-page',
476
- params: {
477
- path: 'first-page',
478
- slug: 'test'
479
- },
480
- query: {},
481
- app: { model }
482
- } satisfies FormContextRequest
483
-
484
- const context = controller.model.getFormContext(request, state)
485
- const evaluationState = { animalType: 'Swan' }
486
-
487
- const viewModel = controller.getViewModel(request, context)
488
-
489
- const filtered = controller.filterConditionalComponents(
490
- viewModel,
491
- model,
492
- evaluationState
493
- )
494
-
495
- expect(filtered).toHaveLength(2)
496
- expect(filtered[0].model.content).toBe('This is info for other breeds')
497
- expect(filtered[1].model.label?.text).toBe('Select from the list')
498
- expect(filtered[1].model.items).toEqual([
499
- {
500
- checked: false,
501
- condition: 'notBarnOwl',
502
- selected: false,
503
- text: 'Option 3',
504
- value: '3'
505
- },
506
- {
507
- checked: false,
508
- condition: 'notBarnOwl',
509
- selected: false,
510
- text: 'Option 4',
511
- value: '4'
512
- }
513
- ])
514
- })
515
- })
516
-
517
- describe('Form validation', () => {
518
- it('includes all field errors', () => {
519
- const result1 = controller1.collection.validate()
520
- const result2 = controller2.collection.validate()
521
-
522
- expect(result1.errors).toHaveLength(1)
523
- expect(result2.errors).toHaveLength(4)
524
- })
525
- })
526
-
527
- describe('Form journey', () => {
528
- let context: FormContext
529
- let contextNo: FormContext
530
- let contextYes: FormContext
531
-
532
- beforeEach(() => {
533
- // Empty state
534
- context = model.getFormContext(requestPage1, {})
535
-
536
- // Question 1: Selected 'No'
537
- contextNo = model.getFormContext(requestPage1, {
538
- yesNoField: false
539
- })
540
-
541
- // Question 1: Selected 'Yes'
542
- contextYes = model.getFormContext(requestPage1, {
543
- yesNoField: true
544
- })
545
- })
546
-
547
- describe('Next getter', () => {
548
- it('returns the next page links', () => {
549
- expect(controller1).toHaveProperty('next', [
550
- { path: '/second-page' },
551
- { path: '/summary', condition: 'isPreviouslyMarried' }
552
- ])
553
-
554
- expect(controller2).toHaveProperty('next', [{ path: '/summary' }])
555
- })
556
- })
557
-
558
- describe('Next', () => {
559
- it('returns the next page path', () => {
560
- expect(controller1.getNextPath(context)).toBe('/second-page')
561
- expect(controller1.getNextPath(contextNo)).toBe('/second-page')
562
- expect(controller1.getNextPath(contextYes)).toBe('/summary')
563
-
564
- expect(controller2.getNextPath(context)).toBe('/summary')
565
- expect(controller2.getNextPath(contextNo)).toBe('/summary')
566
- expect(controller2.getNextPath(contextYes)).toBe('/summary')
567
- })
568
- })
569
-
570
- describe('Summary', () => {
571
- it('returns the summary path', () => {
572
- expect(controller1.getSummaryPath()).toBe('/summary')
573
- expect(controller2.getSummaryPath()).toBe('/summary')
574
- })
575
- })
576
- })
577
-
578
- describe('Route handlers', () => {
579
- const response = {
580
- code: jest.fn().mockImplementation(() => response)
581
- }
582
-
583
- const h: Pick<ResponseToolkit, 'redirect' | 'view'> = {
584
- redirect: jest.fn().mockReturnValue(response),
585
- view: jest.fn()
586
- }
587
-
588
- it('returns default route options', () => {
589
- expect(controller1.getRouteOptions).toMatchObject({
590
- ext: {
591
- onPostHandler: {
592
- method: expect.any(Function)
593
- }
594
- }
595
- })
596
-
597
- expect(controller1.postRouteOptions).toMatchObject({
598
- ext: {
599
- onPostHandler: {
600
- method: expect.any(Function)
601
- }
602
- }
603
- })
604
- })
605
-
606
- it('supports GET route handler', async () => {
607
- const state: FormState = { yesNoField: false }
608
-
609
- for (const controller of [controller1, controller2]) {
610
- jest
611
- .spyOn(controller, 'hasMissingNotificationEmail')
612
- .mockResolvedValue(false)
613
-
614
- jest.spyOn(controller, 'getState').mockResolvedValue(state)
615
- }
616
-
617
- expect(() => controller1.makeGetRouteHandler()).not.toThrow()
618
- expect(() => controller1.makeGetRouteHandler()).toBeInstanceOf(Function)
619
-
620
- await controller1.makeGetRouteHandler()(
621
- requestPage1,
622
- model.getFormContext(requestPage1, {}),
623
- h
624
- )
625
-
626
- await controller2.makeGetRouteHandler()(
627
- requestPage2,
628
- model.getFormContext(requestPage2, {}),
629
- h
630
- )
631
-
632
- expect(h.view).toHaveBeenNthCalledWith(
633
- 1,
634
- controller1.viewName,
635
- expect.objectContaining({
636
- pageTitle: 'Previous marriages',
637
- sectionTitle: undefined
638
- })
639
- )
640
-
641
- expect(h.view).toHaveBeenNthCalledWith(
642
- 2,
643
- controller2.viewName,
644
- expect.objectContaining({
645
- pageTitle: 'When will you get married?',
646
- sectionTitle: 'Your marriage'
647
- })
648
- )
649
- })
650
- })
651
-
652
- describe('State', () => {
653
- beforeEach(() => {
654
- jest.spyOn(CacheService.prototype, 'getState')
655
- jest.spyOn(CacheService.prototype, 'setState')
656
-
657
- // Preview URL '?force'
658
- requestPage1.query.force = ''
659
- })
660
-
661
- describe('getState', () => {
662
- it('should skip get for preview URL direct access', async () => {
663
- const state = await controller1.getState(requestPage1)
664
-
665
- expect(state).toEqual({})
666
- expect(CacheService.prototype.getState).not.toHaveBeenCalled()
667
- })
668
- })
669
-
670
- describe('setState', () => {
671
- it('should skip set for preview URL direct access', async () => {
672
- const state: FormSubmissionState = { yesNoField: false }
673
- const updated = await controller1.setState(requestPage1, state)
674
-
675
- expect(updated).toBe(state)
676
- expect(CacheService.prototype.setState).not.toHaveBeenCalled()
677
- })
678
- })
679
-
680
- describe('mergeState', () => {
681
- it('should skip merge for preview URL direct access', async () => {
682
- const state: FormSubmissionState = {
683
- yesNoField: false
684
- }
685
-
686
- const update: FormSubmissionState = {
687
- dateField__day: 5,
688
- dateField__month: 1,
689
- dateField__year: 2024
690
- }
691
-
692
- const updated = await controller1.mergeState(
693
- requestPage1,
694
- state,
695
- update
696
- )
697
-
698
- expect(updated).toEqual({
699
- yesNoField: false,
700
- dateField__day: 5,
701
- dateField__month: 1,
702
- dateField__year: 2024
703
- })
704
-
705
- expect(CacheService.prototype.setState).not.toHaveBeenCalled()
706
- })
707
- })
708
- })
709
- })
710
-
711
- describe('QuestionPageController V2', () => {
712
- let page1Url: URL
713
- let page2Url: URL
714
-
715
- let model: FormModel
716
- let controller1: QuestionPageController
717
- let controller2: QuestionPageController
718
- let requestPage1: FormRequest
719
- let requestPage2: FormRequest
720
-
721
- beforeEach(() => {
722
- page1Url = new URL('http://example.com/test/first-page')
723
- page2Url = new URL('http://example.com/test/second-page')
724
-
725
- model = new FormModel(definitionConditionsBasicV2, {
726
- basePath: 'test'
727
- })
728
-
729
- controller1 = model.pages[0] // new QuestionPageController(model, page1)
730
- controller2 = model.pages[1] // new QuestionPageController(model, page2)
731
-
732
- requestPage1 = {
733
- method: 'get',
734
- url: page1Url,
735
- path: page1Url.pathname,
736
- params: {
737
- path: 'first-page',
738
- slug: 'test'
739
- },
740
- query: {},
741
- app: { model }
742
- } as FormRequest
743
-
744
- requestPage2 = {
745
- method: 'get',
746
- url: page2Url,
747
- path: page2Url.pathname,
748
- params: {
749
- path: 'second-page',
750
- slug: 'test'
751
- },
752
- query: {},
753
- app: { model }
754
- } as FormRequest
755
- })
756
-
757
- describe('Properties', () => {
758
- it('returns path', () => {
759
- expect(controller1).toHaveProperty('path', '/first-page')
760
- expect(controller2).toHaveProperty('path', '/second-page')
761
- })
762
-
763
- it('returns href', () => {
764
- expect(controller1).toHaveProperty('href', '/test/first-page')
765
- expect(controller2).toHaveProperty('href', '/test/second-page')
766
- })
767
-
768
- it('returns keys', () => {
769
- expect(controller1).toHaveProperty('keys', ['yesNoField'])
770
- expect(controller2).toHaveProperty('keys', [
771
- 'dateField',
772
- 'dateField__day',
773
- 'dateField__month',
774
- 'dateField__year',
775
- 'multilineTextField'
776
- ])
777
- })
778
-
779
- it('returns the page section', () => {
780
- expect(controller1).toHaveProperty('section', undefined)
781
- expect(controller2).toHaveProperty('section', {
782
- name: 'marriage',
783
- title: 'Your marriage',
784
- hideTitle: false
785
- })
786
- })
787
- })
788
-
789
- describe('Path methods', () => {
790
- describe('Next getter', () => {
791
- it('returns page links', () => {
792
- expect(controller1).toHaveProperty('next', [])
793
-
794
- expect(controller2).toHaveProperty('next', [])
795
- })
796
-
797
- it('returns page links (none found)', () => {
798
- const pageDef1 = structuredClone(controller1.pageDef)
799
- const pageDef2 = structuredClone(controller2.pageDef)
800
-
801
- controller1.pageDef = pageDef1
802
- controller2.pageDef = pageDef2
803
-
804
- // @ts-expect-error - Allow invalid property for test
805
- controller1.pageDef.next = []
806
-
807
- // @ts-expect-error - Allow invalid property for test
808
- delete controller2.pageDef.next
809
-
810
- expect(controller1).toHaveProperty('next', [])
811
- expect(controller2).toHaveProperty('next', [])
812
- })
813
- })
814
- })
815
-
816
- describe('Component collection', () => {
817
- it('returns the components for the page', () => {
818
- const { components: components1 } = controller1.collection
819
- const { components: components2 } = controller2.collection
820
-
821
- expect(components1).toHaveLength(1)
822
- expect(components1[0].name).toBe('yesNoField')
823
-
824
- expect(components2).toHaveLength(3)
825
- expect(components2[0].name).toBe('detailsField')
826
- expect(components2[1].name).toBe('dateField')
827
- expect(components2[2].name).toBe('multilineTextField')
828
- })
829
-
830
- it('returns the fields for the page', () => {
831
- const { fields: fields1 } = controller1.collection
832
- const { fields: fields2 } = controller2.collection
833
-
834
- expect(fields1).toHaveLength(1)
835
- expect(fields1[0].name).toBe('yesNoField')
836
-
837
- expect(fields2).toHaveLength(2)
838
- expect(fields2[0].name).toBe('dateField')
839
- expect(fields2[1].name).toBe('multilineTextField')
840
- })
841
-
842
- it('returns the guidance for the page', () => {
843
- const { guidance: guidance1 } = controller1.collection
844
- const { guidance: guidance2 } = controller2.collection
845
-
846
- expect(guidance1).toHaveLength(0)
847
- expect(guidance2).toHaveLength(1)
848
- expect(guidance2[0].name).toBe('detailsField')
849
- })
850
- })
851
-
852
- describe('Component view models', () => {
853
- let viewModel1: FormPageViewModel
854
- let viewModel2: FormPageViewModel
855
-
856
- beforeEach(() => {
857
- viewModel1 = controller1.getViewModel(
858
- requestPage1,
859
- model.getFormContext(requestPage1, {})
860
- )
861
-
862
- viewModel2 = controller2.getViewModel(
863
- requestPage2,
864
- model.getFormContext(requestPage2, {})
865
- )
866
- })
867
-
868
- it('hides the page title for single form component pages', () => {
869
- // Page 1 hides page title (single component)
870
- expect(viewModel1).toHaveProperty('showTitle', false)
871
- expect(viewModel1).toHaveProperty('pageTitle', 'Previous marriages')
872
-
873
- // Page 2 shows page title (multiple components)
874
- expect(viewModel2).toHaveProperty('showTitle', true)
875
- expect(viewModel2).toHaveProperty(
876
- 'pageTitle',
877
- 'When will you get married?'
878
- )
879
- })
880
-
881
- it('returns the component view models for the page', () => {
882
- const { components: components1 } = viewModel1
883
- const { components: components2 } = viewModel2
884
-
885
- expect(components1).toHaveLength(1)
886
- expect(components2).toHaveLength(3)
887
-
888
- // Page 1, component 1, default label
889
- expect(components1[0].model).toHaveProperty('label', {
890
- text: 'Have you previously been married?'
891
- })
892
-
893
- // Page 1, component 1, optional legend
894
- expect(components1[0].model).toHaveProperty('fieldset', {
895
- legend: {
896
- text: 'Previous marriages',
897
- classes: 'govuk-fieldset__legend--l',
898
- isPageHeading: true
899
- }
900
- })
901
-
902
- // Page 2, component 1, content only
903
- expect(components2[0].model).toEqual({
904
- attributes: {},
905
- summaryHtml: 'Find out more',
906
- html: 'Some content goes here'
907
- })
908
-
909
- // Page 2, component 2, default label
910
- expect(components2[1].model).toHaveProperty('label', {
911
- text: 'Date of marriage',
912
- classes: 'govuk-label--m'
913
- })
914
-
915
- // Page 2, component 2, optional legend
916
- expect(components2[1].model).toHaveProperty('fieldset', {
917
- legend: {
918
- text: 'Date of marriage',
919
- classes: 'govuk-fieldset__legend--m'
920
- }
921
- })
922
-
923
- // Page 2, component 3, default label
924
- expect(components2[2].model).toHaveProperty('label', {
925
- text: 'Remarks',
926
- classes: 'govuk-label--m'
927
- })
928
-
929
- // Page 2, component 3, optional legend
930
- expect(components2[2].model).not.toHaveProperty('fieldset')
931
- })
932
- })
933
-
934
- describe('Condition evaluation context', () => {
935
- it('filters state by journey pages', () => {
936
- const { pages } = definitionConditionsComplex
937
-
938
- const model = new FormModel(definitionConditionsComplex, {
939
- basePath: 'test'
940
- })
941
-
942
- // Selected page appears after convergence and contains a conditional field
943
- // This is the page we're theoretically browsing to
944
- const controller = new QuestionPageController(model, pages[7])
945
-
946
- // The state below shows we said we had a UKPassport and entered details for an applicant
947
- const state: FormSubmissionState = {
948
- ukPassport: true,
949
- numberOfApplicants: 2,
950
- applicantOneFirstName: 'Enrique',
951
- applicantOneMiddleName: null,
952
- applicantOneLastName: 'Chase',
953
- applicantOneAddress__addressLine1: 'AddressLine1',
954
- applicantOneAddress__addressLine2: 'AddressLine2',
955
- applicantOneAddress__town: 'Town',
956
- applicantOneAddress__postcode: 'Postcode',
957
- applicantTwoFirstName: 'John',
958
- applicantTwoMiddleName: null,
959
- applicantTwoLastName: 'Doe',
960
- applicantTwoAddress__addressLine1: 'AddressLine1',
961
- applicantTwoAddress__addressLine2: 'AddressLine2',
962
- applicantTwoAddress__town: 'Town',
963
- applicantTwoAddress__postcode: 'Postcode'
964
- }
965
-
966
- let request = {
967
- method: 'get',
968
- url: new URL('http://example.com/test/applicant-one-address'),
969
- path: '/test/applicant-one-address',
970
- params: {
971
- path: 'applicant-one-address',
972
- slug: 'test'
973
- },
974
- query: {},
975
- app: { model }
976
- } satisfies FormContextRequest
977
-
978
- // Calculate our context based on the page we're attempting to load and the above state we provide
979
- let context = controller.model.getFormContext(request, state)
980
- const { evaluationState: stateBefore } = context
981
-
982
- // Our context should know our first applicant
983
- expect(stateBefore).toEqual({
984
- numberOfApplicants: 2,
985
- ukPassport: true,
986
- applicantOneFirstName: 'Enrique',
987
- applicantOneMiddleName: null,
988
- applicantOneLastName: 'Chase',
989
- applicantOneAddress: [
990
- 'AddressLine1',
991
- 'AddressLine2',
992
- 'Town',
993
- 'Postcode'
994
- ]
995
- })
996
-
997
- // Our context should know which pages are relevant
998
- expect(context.paths).toEqual([
999
- '/uk-passport',
1000
- '/how-many-people',
1001
- '/applicant-one-name',
1002
- '/applicant-one-address'
1003
- ])
1004
-
1005
- // Now mark that we don't have a UK Passport
1006
- state.ukPassport = false
1007
-
1008
- request = {
1009
- method: 'get',
1010
- url: new URL('http://example.com/test/summary'),
1011
- path: '/test/summary',
1012
- params: {
1013
- path: 'summary',
1014
- slug: 'test'
1015
- },
1016
- query: {},
1017
- app: { model }
1018
- } satisfies FormContextRequest
1019
-
1020
- // And recalculate our context
1021
- context = controller.model.getFormContext(request, state)
1022
- const { evaluationState: stateAfter } = context
1023
-
1024
- // Our context should no longer list pages about our applicant
1025
- expect(context.paths).toEqual([
1026
- '/uk-passport',
1027
- '/testconditions',
1028
- '/summary'
1029
- ])
1030
-
1031
- // Our context should no longer know anything about our applicant
1032
- expect(stateAfter).not.toHaveProperty('numberOfApplicants')
1033
- expect(stateAfter).not.toHaveProperty('applicantOneFirstName')
1034
- expect(stateAfter).not.toHaveProperty('applicantOneMiddleName')
1035
- expect(stateAfter).not.toHaveProperty('applicantOneLastName')
1036
- expect(stateAfter).not.toHaveProperty('applicantOneAddress')
1037
-
1038
- expect(stateAfter).toEqual({
1039
- ukPassport: false
1040
- })
1041
- })
1042
-
1043
- it('combines state values for date fields', () => {
1044
- const { pages } = definitionConditionsDates
1045
-
1046
- const model = new FormModel(definitionConditionsDates, {
1047
- basePath: 'test'
1048
- })
1049
-
1050
- const controller = new QuestionPageController(model, pages[0])
1051
-
1052
- const request = {
1053
- method: 'get',
1054
- url: new URL('http://example.com/test/page-one'),
1055
- path: '/test/page-one',
1056
- params: {
1057
- path: 'page-one',
1058
- slug: 'test'
1059
- },
1060
- query: {},
1061
- app: { model }
1062
- } satisfies FormContextRequest
1063
-
1064
- const context = controller.model.getFormContext(request, {
1065
- dateField__day: 5,
1066
- dateField__month: 1,
1067
- dateField__year: 2024
1068
- })
1069
-
1070
- // Ensure dates are transformed to yyyy-MM-dd format
1071
- expect(context.evaluationState).toHaveProperty('dateField', '2024-01-05')
1072
-
1073
- // Unlike relevant state which has the individual date parts
1074
- expect(context.relevantState).not.toHaveProperty('dateField')
1075
- expect(context.relevantState).toMatchObject({
1076
- dateField__day: 5,
1077
- dateField__month: 1,
1078
- dateField__year: 2024
1079
- })
1080
- })
1081
- })
1082
-
1083
- describe('Form validation', () => {
1084
- it('includes all field errors', () => {
1085
- const result1 = controller1.collection.validate()
1086
- const result2 = controller2.collection.validate()
1087
-
1088
- expect(result1.errors).toHaveLength(1)
1089
- expect(result2.errors).toHaveLength(4)
1090
- })
1091
- })
1092
-
1093
- describe('Form journey', () => {
1094
- let context: FormContext
1095
- let contextNo: FormContext
1096
- let contextYes: FormContext
1097
-
1098
- beforeEach(() => {
1099
- // Empty state
1100
- context = model.getFormContext(requestPage1, {})
1101
-
1102
- // Question 1: Selected 'No'
1103
- contextNo = model.getFormContext(requestPage1, {
1104
- yesNoField: false
1105
- })
1106
-
1107
- // Question 1: Selected 'Yes'
1108
- contextYes = model.getFormContext(requestPage1, {
1109
- yesNoField: true
1110
- })
1111
- })
1112
-
1113
- describe('Next', () => {
1114
- it('returns the next page path', () => {
1115
- expect(controller1.getNextPath(context)).toBe('/summary')
1116
- expect(controller1.getNextPath(contextNo)).toBe('/second-page')
1117
- expect(controller1.getNextPath(contextYes)).toBe('/summary')
1118
-
1119
- expect(controller2.getNextPath(context)).toBe('/summary')
1120
- expect(controller2.getNextPath(contextNo)).toBe('/summary')
1121
- expect(controller2.getNextPath(contextYes)).toBe('/summary')
1122
- })
1123
- })
1124
-
1125
- describe('Summary', () => {
1126
- it('returns the summary path', () => {
1127
- expect(controller1.getSummaryPath()).toBe('/summary')
1128
- expect(controller2.getSummaryPath()).toBe('/summary')
1129
- })
1130
- })
1131
- })
1132
-
1133
- describe('Route handlers', () => {
1134
- const response = {
1135
- code: jest.fn().mockImplementation(() => response)
1136
- }
1137
-
1138
- const h: Pick<ResponseToolkit, 'redirect' | 'view'> = {
1139
- redirect: jest.fn().mockReturnValue(response),
1140
- view: jest.fn()
1141
- }
1142
-
1143
- it('returns default route options', () => {
1144
- expect(controller1.getRouteOptions).toMatchObject({
1145
- ext: {
1146
- onPostHandler: {
1147
- method: expect.any(Function)
1148
- }
1149
- }
1150
- })
1151
-
1152
- expect(controller1.postRouteOptions).toMatchObject({
1153
- ext: {
1154
- onPostHandler: {
1155
- method: expect.any(Function)
1156
- }
1157
- }
1158
- })
1159
- })
1160
-
1161
- it('supports GET route handler', async () => {
1162
- const state: FormState = { yesNoField: false }
1163
-
1164
- for (const controller of [controller1, controller2]) {
1165
- jest
1166
- .spyOn(controller, 'hasMissingNotificationEmail')
1167
- .mockResolvedValue(false)
1168
-
1169
- jest.spyOn(controller, 'getState').mockResolvedValue(state)
1170
- }
1171
-
1172
- expect(() => controller1.makeGetRouteHandler()).not.toThrow()
1173
- expect(() => controller1.makeGetRouteHandler()).toBeInstanceOf(Function)
1174
-
1175
- await controller1.makeGetRouteHandler()(
1176
- requestPage1,
1177
- model.getFormContext(requestPage1, {}),
1178
- h
1179
- )
1180
-
1181
- await controller2.makeGetRouteHandler()(
1182
- requestPage2,
1183
- model.getFormContext(requestPage2, {}),
1184
- h
1185
- )
1186
-
1187
- expect(h.view).toHaveBeenNthCalledWith(
1188
- 1,
1189
- controller1.viewName,
1190
- expect.objectContaining({
1191
- pageTitle: 'Previous marriages',
1192
- sectionTitle: undefined
1193
- })
1194
- )
1195
-
1196
- expect(h.view).toHaveBeenNthCalledWith(
1197
- 2,
1198
- controller2.viewName,
1199
- expect.objectContaining({
1200
- pageTitle: 'When will you get married?',
1201
- sectionTitle: 'Your marriage'
1202
- })
1203
- )
1204
- })
1205
- })
1206
-
1207
- describe('State', () => {
1208
- beforeEach(() => {
1209
- jest.spyOn(CacheService.prototype, 'getState')
1210
- jest.spyOn(CacheService.prototype, 'setState')
1211
-
1212
- // Preview URL '?force'
1213
- requestPage1.query.force = ''
1214
- })
1215
-
1216
- describe('getState', () => {
1217
- it('should skip get for preview URL direct access', async () => {
1218
- const state = await controller1.getState(requestPage1)
1219
-
1220
- expect(state).toEqual({})
1221
- expect(CacheService.prototype.getState).not.toHaveBeenCalled()
1222
- })
1223
- })
1224
-
1225
- describe('setState', () => {
1226
- it('should skip set for preview URL direct access', async () => {
1227
- const state: FormSubmissionState = { yesNoField: false }
1228
- const updated = await controller1.setState(requestPage1, state)
1229
-
1230
- expect(updated).toBe(state)
1231
- expect(CacheService.prototype.setState).not.toHaveBeenCalled()
1232
- })
1233
- })
1234
-
1235
- describe('mergeState', () => {
1236
- it('should skip merge for preview URL direct access', async () => {
1237
- const state: FormSubmissionState = {
1238
- yesNoField: false
1239
- }
1240
-
1241
- const update: FormSubmissionState = {
1242
- dateField__day: 5,
1243
- dateField__month: 1,
1244
- dateField__year: 2024
1245
- }
1246
-
1247
- const updated = await controller1.mergeState(
1248
- requestPage1,
1249
- state,
1250
- update
1251
- )
1252
-
1253
- expect(updated).toEqual({
1254
- yesNoField: false,
1255
- dateField__day: 5,
1256
- dateField__month: 1,
1257
- dateField__year: 2024
1258
- })
1259
-
1260
- expect(CacheService.prototype.setState).not.toHaveBeenCalled()
1261
- })
1262
- })
1263
- })
1264
- })