@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,264 +0,0 @@
1
- import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
2
- import { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'
3
- import {
4
- type FormContextRequest,
5
- type FormPageViewModel,
6
- type FormSubmissionError,
7
- type RepeatListState,
8
- type RepeaterSummaryPageViewModel
9
- } from '~/src/server/plugins/engine/types.js'
10
- import definition from '~/test/form/definitions/repeat.js'
11
-
12
- describe('RepeatPageController', () => {
13
- const itemId1 = 'abc-123'
14
- const itemId2 = 'xyz-987'
15
-
16
- let pageUrl: URL
17
- let pageItemUrl: URL
18
- let pageSummaryUrl: URL
19
-
20
- let model: FormModel
21
- let controller: RepeatPageController
22
- let requestPage: FormContextRequest
23
- let requestPageItem: FormContextRequest
24
- let requestPageSummary: FormContextRequest
25
-
26
- beforeEach(() => {
27
- const { pages } = definition
28
-
29
- pageUrl = new URL('/repeat/pizza-order', 'http://example.com')
30
-
31
- pageItemUrl = new URL(
32
- `${pageUrl.pathname}/${itemId1}`,
33
- 'http://example.com'
34
- )
35
-
36
- pageSummaryUrl = new URL(
37
- `${pageUrl.pathname}/summary`,
38
- 'http://example.com'
39
- )
40
-
41
- model = new FormModel(definition, {
42
- basePath: 'test'
43
- })
44
-
45
- controller = new RepeatPageController(model, pages[0])
46
-
47
- requestPage = {
48
- method: 'get',
49
- url: pageUrl,
50
- path: pageUrl.pathname,
51
- params: {
52
- path: 'pizza-order',
53
- slug: 'repeat'
54
- },
55
- query: {},
56
- app: { model }
57
- }
58
-
59
- requestPageItem = {
60
- method: 'get',
61
- url: pageItemUrl,
62
- path: pageItemUrl.pathname,
63
- params: {
64
- path: 'pizza-order',
65
- slug: 'repeat',
66
- itemId: itemId1
67
- },
68
- query: {},
69
- app: { model }
70
- }
71
-
72
- requestPageSummary = {
73
- method: 'get',
74
- url: pageSummaryUrl,
75
- path: pageSummaryUrl.pathname,
76
- params: {
77
- path: 'pizza-order',
78
- slug: 'repeat'
79
- },
80
- query: {},
81
- app: { model }
82
- }
83
- })
84
-
85
- describe('Properties', () => {
86
- it('returns summary view name', () => {
87
- expect(controller).toHaveProperty(
88
- 'listSummaryViewName',
89
- 'repeat-list-summary'
90
- )
91
- })
92
-
93
- it('returns delete view name', () => {
94
- expect(controller).toHaveProperty('listDeleteViewName', 'item-delete')
95
- })
96
-
97
- it('returns repeater config', () => {
98
- expect(controller).toHaveProperty('repeat', {
99
- options: {
100
- name: 'pizza',
101
- title: 'Pizza'
102
- },
103
- schema: {
104
- max: 3,
105
- min: 2
106
- }
107
- })
108
- })
109
- })
110
-
111
- describe('Path methods', () => {
112
- describe('Summary path', () => {
113
- it('returns path to summary page', () => {
114
- expect(controller.getSummaryPath()).toBe('/summary')
115
- })
116
-
117
- it('returns path to repeater summary page', () => {
118
- expect(controller.getSummaryPath(requestPage)).toBe(
119
- '/pizza-order/summary'
120
- )
121
- })
122
- })
123
- })
124
-
125
- describe('Item view model', () => {
126
- let viewModel: FormPageViewModel
127
-
128
- beforeEach(() => {
129
- viewModel = controller.getViewModel(
130
- requestPageItem,
131
- model.getFormContext(requestPageItem, {})
132
- )
133
- })
134
-
135
- it('updates section title with repeater title and count', () => {
136
- expect(viewModel).toHaveProperty('sectionTitle', 'Food: Pizza 1')
137
- })
138
- })
139
-
140
- describe.each([
141
- {
142
- description: 'No items',
143
- list: [] satisfies RepeatListState,
144
- viewModel: {
145
- pageTitle: 'You have added 0 Pizzas',
146
- showTitle: true,
147
- sectionTitle: 'Food'
148
- }
149
- },
150
- {
151
- description: '1 item',
152
- list: [
153
- {
154
- itemId: itemId1,
155
- toppings: 'Ham',
156
- quantity: 2
157
- }
158
- ] satisfies RepeatListState,
159
- viewModel: {
160
- pageTitle: 'You have added 1 Pizza',
161
- showTitle: true,
162
- sectionTitle: 'Food'
163
- }
164
- },
165
- {
166
- description: '2 items',
167
- list: [
168
- {
169
- itemId: itemId1,
170
- toppings: 'Ham',
171
- quantity: 2
172
- },
173
- {
174
- itemId: itemId2,
175
- toppings: 'Cheese',
176
- quantity: 1
177
- }
178
- ] satisfies RepeatListState,
179
- viewModel: {
180
- pageTitle: 'You have added 2 Pizzas',
181
- showTitle: true,
182
- sectionTitle: 'Food'
183
- }
184
- }
185
- ])(
186
- 'List summary view model ($description)',
187
- ({ list, viewModel: expected }) => {
188
- let viewModel: RepeaterSummaryPageViewModel
189
-
190
- beforeEach(() => {
191
- viewModel = controller.getListSummaryViewModel(
192
- requestPageSummary,
193
- model.getFormContext(requestPageSummary, {}),
194
- list
195
- )
196
- })
197
-
198
- it('should customise page title', () => {
199
- expect(viewModel).toHaveProperty('pageTitle', expected.pageTitle)
200
- expect(viewModel).toHaveProperty('showTitle', expected.showTitle)
201
- })
202
-
203
- it('should extend default view model', () => {
204
- const defaults = controller.viewModel
205
-
206
- expect(viewModel).toHaveProperty('name', defaults.name)
207
- expect(viewModel).toHaveProperty('page', defaults.page)
208
- expect(viewModel).toHaveProperty('sectionTitle', expected.sectionTitle)
209
- expect(viewModel).toHaveProperty('isStartPage', defaults.isStartPage)
210
- expect(viewModel).toHaveProperty('serviceUrl', defaults.serviceUrl)
211
- expect(viewModel).toHaveProperty('feedbackLink', defaults.feedbackLink)
212
-
213
- // Unless overridden
214
- expect(viewModel).not.toHaveProperty('pageTitle', defaults.pageTitle)
215
- })
216
- }
217
- )
218
-
219
- describe('Form validation', () => {
220
- it('includes title text and errors', () => {
221
- const result = controller.collection.validate()
222
-
223
- expect(result.errors).toEqual<FormSubmissionError[]>([
224
- {
225
- path: ['toppings'],
226
- href: '#toppings',
227
- name: 'toppings',
228
- text: 'Select toppings',
229
- context: {
230
- key: 'toppings',
231
- label: 'Toppings',
232
- title: 'Toppings'
233
- }
234
- },
235
- {
236
- path: ['quantity'],
237
- href: '#quantity',
238
- name: 'quantity',
239
- text: 'Enter quantity',
240
- context: {
241
- key: 'quantity',
242
- label: 'Quantity',
243
- title: 'Quantity'
244
- }
245
- },
246
- {
247
- path: ['itemId'],
248
- href: '#itemId',
249
- name: 'itemId',
250
- text: 'Select itemId',
251
- context: {
252
- key: 'itemId',
253
- label: 'itemId'
254
- }
255
- }
256
- ])
257
- })
258
-
259
- it('includes all field errors', () => {
260
- const result = controller.collection.validate()
261
- expect(result.errors).toHaveLength(3)
262
- })
263
- })
264
- })
@@ -1,458 +0,0 @@
1
- import { randomUUID } from 'crypto'
2
-
3
- import { type PageRepeat, type Repeat } from '@defra/forms-model'
4
- import Boom from '@hapi/boom'
5
- import { type ResponseToolkit } from '@hapi/hapi'
6
- import Joi from 'joi'
7
-
8
- import { isRepeatState } from '~/src/server/plugins/engine/components/FormComponent.js'
9
- import { redirectPath } from '~/src/server/plugins/engine/helpers.js'
10
- import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
11
- import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
12
- import {
13
- type FormContext,
14
- type FormContextRequest,
15
- type FormPageViewModel,
16
- type FormPayload,
17
- type FormSubmissionState,
18
- type ItemDeletePageViewModel,
19
- type RepeatItemState,
20
- type RepeatListState,
21
- type RepeaterSummaryPageViewModel,
22
- type SummaryList,
23
- type SummaryListAction
24
- } from '~/src/server/plugins/engine/types.js'
25
- import {
26
- FormAction,
27
- type FormRequest,
28
- type FormRequestPayload
29
- } from '~/src/server/routes/types.js'
30
-
31
- export class RepeatPageController extends QuestionPageController {
32
- declare pageDef: PageRepeat
33
-
34
- listSummaryViewName = 'repeat-list-summary'
35
- listDeleteViewName = 'item-delete'
36
- repeat: Repeat
37
-
38
- constructor(model: FormModel, pageDef: PageRepeat) {
39
- super(model, pageDef)
40
-
41
- this.repeat = pageDef.repeat
42
-
43
- const { options, schema } = this.repeat
44
- const itemId = Joi.string().uuid().required()
45
-
46
- this.collection.formSchema = this.collection.formSchema.append({ itemId })
47
- this.collection.stateSchema = Joi.object<RepeatItemState>().keys({
48
- [options.name]: Joi.array()
49
- .items(this.collection.stateSchema.append({ itemId }))
50
- .min(schema.min)
51
- .max(schema.max)
52
- .label(`${options.title} list`)
53
- .required()
54
- })
55
- }
56
-
57
- get keys() {
58
- const { repeat } = this
59
- return [repeat.options.name, ...super.keys]
60
- }
61
-
62
- getFormParams(request?: FormContextRequest) {
63
- const params = super.getFormParams(request)
64
-
65
- // Apply an itemId to the form payload
66
- if (request?.payload) {
67
- params.itemId = request.params.itemId ?? randomUUID()
68
- }
69
-
70
- return params
71
- }
72
-
73
- getFormDataFromState(
74
- request: FormContextRequest | undefined,
75
- state: FormSubmissionState
76
- ) {
77
- const { repeat } = this
78
-
79
- const params = this.getFormParams(request)
80
- const list = this.getListFromState(state)
81
- const itemId = this.getItemId(request)
82
-
83
- // Create payload with repeater list state
84
- if (!itemId) {
85
- return {
86
- ...params,
87
- [repeat.options.name]: list
88
- }
89
- }
90
-
91
- // Create payload with repeater item state
92
- const item = this.getItemFromList(list, itemId)
93
-
94
- return {
95
- ...params,
96
- ...item
97
- }
98
- }
99
-
100
- getStateFromValidForm(
101
- request: FormContextRequest,
102
- state: FormSubmissionState,
103
- payload: FormPayload
104
- ) {
105
- const itemId = this.getItemId(request)
106
-
107
- if (!itemId) {
108
- throw Boom.badRequest('No item ID found')
109
- }
110
-
111
- const list = this.getListFromState(state)
112
- const item = this.getItemFromList(list, itemId)
113
-
114
- const itemState = super.getStateFromValidForm(request, state, payload)
115
- const updated: RepeatItemState = { ...itemState, itemId }
116
- const newList = [...list]
117
-
118
- if (!item) {
119
- // Adding a new item
120
- newList.push(updated)
121
- } else {
122
- // Update an existing item
123
- newList[list.indexOf(item)] = updated
124
- }
125
-
126
- return {
127
- [this.repeat.options.name]: newList
128
- }
129
- }
130
-
131
- proceed(
132
- request: FormContextRequest,
133
- h: Pick<ResponseToolkit, 'redirect' | 'view'>
134
- ) {
135
- const nextPath = this.getSummaryPath(request)
136
- return super.proceed(request, h, nextPath)
137
- }
138
-
139
- getItemFromList(list: RepeatListState, itemId?: string) {
140
- return list.find((item) => item.itemId === itemId)
141
- }
142
-
143
- getListFromState(state: FormSubmissionState) {
144
- const { name } = this.repeat.options
145
- const values = state[name]
146
-
147
- return isRepeatState(values) ? values : []
148
- }
149
-
150
- makeGetRouteHandler() {
151
- return async (
152
- request: FormRequest,
153
- context: FormContext,
154
- h: Pick<ResponseToolkit, 'redirect' | 'view'>
155
- ) => {
156
- const { path } = this
157
- const { query } = request
158
- const { state } = context
159
-
160
- const itemId = this.getItemId(request)
161
- const list = this.getListFromState(state)
162
-
163
- if (!itemId) {
164
- const summaryPath = this.getSummaryPath(request)
165
- const nextPath = redirectPath(`${path}/${randomUUID()}`, {
166
- returnUrl: query.returnUrl,
167
- force: query.force
168
- })
169
-
170
- // Only redirect to new item when list is empty
171
- return super.proceed(request, h, list.length ? summaryPath : nextPath)
172
- }
173
-
174
- return super.makeGetRouteHandler()(request, context, h)
175
- }
176
- }
177
-
178
- makeGetListSummaryRouteHandler() {
179
- return (
180
- request: FormRequest,
181
- context: FormContext,
182
- h: Pick<ResponseToolkit, 'redirect' | 'view'>
183
- ) => {
184
- const { path } = this
185
- const { query } = request
186
- const { state } = context
187
-
188
- const list = this.getListFromState(state)
189
-
190
- if (!list.length) {
191
- const nextPath = redirectPath(`${path}/${randomUUID()}`, {
192
- returnUrl: query.returnUrl
193
- })
194
-
195
- return super.proceed(request, h, nextPath)
196
- }
197
-
198
- const viewModel = this.getListSummaryViewModel(request, context, list)
199
-
200
- return h.view(this.listSummaryViewName, viewModel)
201
- }
202
- }
203
-
204
- makePostListSummaryRouteHandler() {
205
- return (
206
- request: FormRequestPayload,
207
- context: FormContext,
208
- h: Pick<ResponseToolkit, 'redirect' | 'view'>
209
- ) => {
210
- const { path, repeat } = this
211
- const { query } = request
212
- const { schema, options } = repeat
213
- const { state } = context
214
-
215
- const list = this.getListFromState(state)
216
-
217
- if (!list.length) {
218
- const nextPath = redirectPath(`${path}/${randomUUID()}`, {
219
- returnUrl: query.returnUrl
220
- })
221
-
222
- return super.proceed(request, h, nextPath)
223
- }
224
-
225
- const { action } = this.getFormParams(request)
226
-
227
- const hasErrorMin =
228
- action === FormAction.Continue && list.length < schema.min
229
-
230
- const hasErrorMax =
231
- (action === FormAction.AddAnother && list.length >= schema.max) ||
232
- (action === FormAction.Continue && list.length > schema.max)
233
-
234
- // Show error if repeat limits apply
235
- if (hasErrorMin || hasErrorMax) {
236
- const count = hasErrorMax ? schema.max : schema.min
237
- const itemTitle = `${options.title}${count === 1 ? '' : 's'}`
238
-
239
- context.errors = [
240
- {
241
- path: [],
242
- href: '',
243
- name: '',
244
- text: hasErrorMax
245
- ? `You can only add up to ${count} ${itemTitle}`
246
- : `You must add at least ${count} ${itemTitle}`
247
- }
248
- ]
249
-
250
- const viewModel = this.getListSummaryViewModel(request, context, list)
251
-
252
- return h.view(this.listSummaryViewName, viewModel)
253
- }
254
-
255
- if (action === FormAction.AddAnother) {
256
- const nextPath = redirectPath(`${path}/${randomUUID()}`, {
257
- returnUrl: query.returnUrl
258
- })
259
-
260
- return super.proceed(request, h, nextPath)
261
- }
262
-
263
- const nextPath = this.getNextPath(context)
264
- return super.proceed(request, h, nextPath)
265
- }
266
- }
267
-
268
- makeGetItemDeleteRouteHandler() {
269
- return (
270
- request: FormRequest,
271
- context: FormContext,
272
- h: Pick<ResponseToolkit, 'redirect' | 'view'>
273
- ) => {
274
- const { viewModel } = this
275
- const { state } = context
276
-
277
- const list = this.getListFromState(state)
278
- const itemId = this.getItemId(request)
279
- const item = this.getItemFromList(list, itemId)
280
-
281
- if (!item || list.length === 1) {
282
- throw Boom.notFound(
283
- item
284
- ? 'Last list item cannot be removed'
285
- : 'List item to remove not found'
286
- )
287
- }
288
-
289
- const { title } = this.repeat.options
290
-
291
- return h.view(this.listDeleteViewName, {
292
- ...viewModel,
293
- context,
294
- backLink: this.getBackLink(request, context),
295
- pageTitle: `Are you sure you want to remove this ${title}?`,
296
- itemTitle: `${title} ${list.indexOf(item) + 1}`,
297
- buttonConfirm: { text: `Remove ${title}` },
298
- buttonCancel: { text: 'Cancel' }
299
- } satisfies ItemDeletePageViewModel)
300
- }
301
- }
302
-
303
- makePostItemDeleteRouteHandler() {
304
- return async (
305
- request: FormRequestPayload,
306
- context: FormContext,
307
- h: Pick<ResponseToolkit, 'redirect' | 'view'>
308
- ) => {
309
- const { repeat } = this
310
- const { state } = context
311
-
312
- const { confirm } = this.getFormParams(request)
313
-
314
- const list = this.getListFromState(state)
315
- const itemId = this.getItemId(request)
316
- const item = this.getItemFromList(list, itemId)
317
-
318
- if (!item || list.length === 1) {
319
- throw Boom.notFound(
320
- item
321
- ? 'Last list item cannot be removed'
322
- : 'List item to remove not found'
323
- )
324
- }
325
-
326
- // Remove the item from the list
327
- if (confirm) {
328
- list.splice(list.indexOf(item), 1)
329
-
330
- const update = {
331
- [repeat.options.name]: list
332
- }
333
-
334
- await this.mergeState(request, state, update)
335
- }
336
-
337
- return this.proceed(request, h)
338
- }
339
- }
340
-
341
- getViewModel(
342
- request: FormContextRequest,
343
- context: FormContext
344
- ): FormPageViewModel {
345
- const { state } = context
346
-
347
- const list = this.getListFromState(state)
348
- const itemId = this.getItemId(request)
349
- const item = this.getItemFromList(list, itemId)
350
-
351
- const viewModel = super.getViewModel(request, context)
352
- const itemNumber = item ? list.indexOf(item) + 1 : list.length + 1
353
- const repeatCaption = `${this.repeat.options.title} ${itemNumber}`
354
-
355
- return {
356
- ...viewModel,
357
-
358
- sectionTitle: viewModel.sectionTitle
359
- ? `${viewModel.sectionTitle}: ${repeatCaption}`
360
- : repeatCaption
361
- }
362
- }
363
-
364
- getListSummaryViewModel(
365
- request: FormContextRequest,
366
- context: FormContext,
367
- list: RepeatListState
368
- ): RepeaterSummaryPageViewModel {
369
- const { collection, href, repeat } = this
370
- const { query } = request
371
- const { isForceAccess, errors } = context
372
-
373
- const { title } = repeat.options
374
-
375
- const summaryList: SummaryList = {
376
- classes: 'govuk-summary-list--long-actions',
377
- rows: []
378
- }
379
-
380
- let count = 0
381
-
382
- if (Array.isArray(list)) {
383
- count = list.length
384
-
385
- const summaryPath = this.getSummaryPath(request)
386
-
387
- list.forEach((item, index) => {
388
- const items: SummaryListAction[] = []
389
-
390
- // Remove summary list actions from previews
391
- if (!isForceAccess) {
392
- items.push({
393
- href: redirectPath(`${href}/${item.itemId}`, {
394
- returnUrl: query.returnUrl ?? this.getHref(summaryPath)
395
- }),
396
- text: 'Change',
397
- classes: 'govuk-link--no-visited-state',
398
- visuallyHiddenText: `item ${index + 1}`
399
- })
400
-
401
- if (count > 1) {
402
- items.push({
403
- href: redirectPath(`${href}/${item.itemId}/confirm-delete`, {
404
- returnUrl: query.returnUrl
405
- }),
406
- text: 'Remove',
407
- classes: 'govuk-link--no-visited-state',
408
- visuallyHiddenText: `item ${index + 1}`
409
- })
410
- }
411
- }
412
-
413
- const itemDisplayText = collection.fields.length
414
- ? collection.fields[0].getDisplayStringFromState(item)
415
- : ''
416
-
417
- summaryList.rows.push({
418
- key: {
419
- text: `${title} ${index + 1}`
420
- },
421
- value: {
422
- text: itemDisplayText || 'Not supplied'
423
- },
424
- actions: {
425
- items
426
- }
427
- })
428
- })
429
- }
430
-
431
- return {
432
- ...this.viewModel,
433
- backLink: this.getBackLink(request, context),
434
- repeatTitle: title,
435
- pageTitle: `You have added ${count} ${title}${count === 1 ? '' : 's'}`,
436
- showTitle: true,
437
- context,
438
- errors,
439
- checkAnswers: [{ summaryList }]
440
- }
441
- }
442
-
443
- getSummaryPath(request?: FormContextRequest) {
444
- const { path } = this
445
-
446
- const summaryPath = super.getSummaryPath()
447
-
448
- if (!request) {
449
- return summaryPath
450
- }
451
-
452
- const { query } = request
453
-
454
- return redirectPath(`${path}${summaryPath}`, {
455
- returnUrl: query.returnUrl
456
- })
457
- }
458
- }