@defra/forms-engine-plugin 0.1.10 → 0.1.12

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 (247) hide show
  1. package/.public/javascripts/file-upload.min.js +1 -1
  2. package/.public/javascripts/file-upload.min.js.map +1 -1
  3. package/.public/stylesheets/application.min.css +1 -1
  4. package/.public/stylesheets/application.min.css.map +1 -1
  5. package/.server/client/javascripts/file-upload.js +45 -4
  6. package/.server/client/javascripts/file-upload.js.map +1 -1
  7. package/.server/client/stylesheets/application.scss +10 -0
  8. package/.server/config/index.js +3 -14
  9. package/.server/config/index.js.map +1 -1
  10. package/.server/server/constants.js +2 -0
  11. package/.server/server/constants.js.map +1 -1
  12. package/.server/server/devserver/dxt-devtool-baselayout.html +71 -0
  13. package/.server/server/forms/register-as-a-unicorn-breeder.json +393 -0
  14. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +251 -0
  15. package/.server/server/index.js +12 -17
  16. package/.server/server/index.js.map +1 -1
  17. package/.server/server/plugins/engine/components/AutocompleteField.js +2 -0
  18. package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
  19. package/.server/server/plugins/engine/components/CheckboxesField.js +3 -4
  20. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  21. package/.server/server/plugins/engine/components/ComponentCollection.js +37 -16
  22. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  23. package/.server/server/plugins/engine/components/DatePartsField.js +36 -2
  24. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
  25. package/.server/server/plugins/engine/components/EmailAddressField.js +19 -3
  26. package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
  27. package/.server/server/plugins/engine/components/FileUploadField.js +44 -4
  28. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  29. package/.server/server/plugins/engine/components/FormComponent.js +14 -2
  30. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  31. package/.server/server/plugins/engine/components/ListFormComponent.js +16 -3
  32. package/.server/server/plugins/engine/components/ListFormComponent.js.map +1 -1
  33. package/.server/server/plugins/engine/components/Markdown.js +24 -0
  34. package/.server/server/plugins/engine/components/Markdown.js.map +1 -0
  35. package/.server/server/plugins/engine/components/MonthYearField.js +30 -2
  36. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
  37. package/.server/server/plugins/engine/components/MultilineTextField.js +32 -3
  38. package/.server/server/plugins/engine/components/MultilineTextField.js.map +1 -1
  39. package/.server/server/plugins/engine/components/NumberField.js +28 -3
  40. package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
  41. package/.server/server/plugins/engine/components/SelectionControlField.js +14 -0
  42. package/.server/server/plugins/engine/components/SelectionControlField.js.map +1 -1
  43. package/.server/server/plugins/engine/components/TelephoneNumberField.js +19 -3
  44. package/.server/server/plugins/engine/components/TelephoneNumberField.js.map +1 -1
  45. package/.server/server/plugins/engine/components/TextField.js +22 -3
  46. package/.server/server/plugins/engine/components/TextField.js.map +1 -1
  47. package/.server/server/plugins/engine/components/UkAddressField.js +29 -0
  48. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
  49. package/.server/server/plugins/engine/components/YesNoField.js +18 -0
  50. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  51. package/.server/server/plugins/engine/components/helpers.js +16 -0
  52. package/.server/server/plugins/engine/components/helpers.js.map +1 -1
  53. package/.server/server/plugins/engine/components/index.js +1 -0
  54. package/.server/server/plugins/engine/components/index.js.map +1 -1
  55. package/.server/server/plugins/engine/configureEnginePlugin.js +19 -3
  56. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  57. package/.server/server/plugins/engine/helpers.js +38 -18
  58. package/.server/server/plugins/engine/helpers.js.map +1 -1
  59. package/.server/server/plugins/engine/models/FormModel.js +60 -2
  60. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  61. package/.server/server/plugins/engine/models/SummaryViewModel.js +3 -2
  62. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  63. package/.server/server/plugins/engine/outputFormatters/human/v1.js +1 -1
  64. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  65. package/.server/server/plugins/engine/pageControllers/PageController.js +13 -5
  66. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
  67. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +2 -2
  68. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  69. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +19 -5
  70. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  71. package/.server/server/plugins/engine/pageControllers/validationOptions.js +6 -11
  72. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  73. package/.server/server/plugins/engine/plugin.js +32 -20
  74. package/.server/server/plugins/engine/plugin.js.map +1 -1
  75. package/.server/server/plugins/engine/services/formsService.js +15 -29
  76. package/.server/server/plugins/engine/services/formsService.js.map +1 -1
  77. package/.server/server/plugins/engine/services/localFormsService.js +52 -0
  78. package/.server/server/plugins/engine/services/localFormsService.js.map +1 -0
  79. package/.server/server/plugins/engine/services/notifyService.js +1 -4
  80. package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
  81. package/.server/server/plugins/engine/services/uploadService.js +5 -3
  82. package/.server/server/plugins/engine/services/uploadService.js.map +1 -1
  83. package/.server/server/plugins/engine/types.js.map +1 -1
  84. package/.server/server/plugins/engine/views/components/html.html +1 -1
  85. package/.server/server/plugins/engine/views/components/markdown.html +5 -0
  86. package/.server/server/plugins/engine/views/confirmation.html +1 -1
  87. package/.server/server/plugins/engine/views/file-upload.html +1 -1
  88. package/.server/server/plugins/engine/views/index.html +1 -1
  89. package/.server/server/plugins/engine/views/item-delete.html +1 -1
  90. package/.server/server/plugins/engine/views/repeat-list-summary.html +1 -1
  91. package/.server/server/plugins/engine/views/summary.html +8 -2
  92. package/.server/server/plugins/errorPages.js +4 -26
  93. package/.server/server/plugins/errorPages.js.map +1 -1
  94. package/.server/server/plugins/nunjucks/context.js +43 -33
  95. package/.server/server/plugins/nunjucks/context.js.map +1 -1
  96. package/.server/server/plugins/nunjucks/context.test.js +23 -28
  97. package/.server/server/plugins/nunjucks/context.test.js.map +1 -1
  98. package/.server/server/plugins/nunjucks/enviroment.test.js +6 -3
  99. package/.server/server/plugins/nunjucks/enviroment.test.js.map +1 -1
  100. package/.server/server/plugins/nunjucks/types.js +3 -4
  101. package/.server/server/plugins/nunjucks/types.js.map +1 -1
  102. package/.server/server/routes/index.js +0 -1
  103. package/.server/server/routes/index.js.map +1 -1
  104. package/.server/server/utils/type-utils.js +8 -0
  105. package/.server/server/utils/type-utils.js.map +1 -0
  106. package/.server/typings/hapi/index.d.js.map +1 -1
  107. package/.server/typings/joi/index.d.js.map +1 -1
  108. package/package.json +4 -3
  109. package/src/client/javascripts/file-upload.js +60 -4
  110. package/src/client/stylesheets/application.scss +10 -0
  111. package/src/config/index.ts +4 -17
  112. package/src/server/constants.js +2 -0
  113. package/src/server/devserver/dxt-devtool-baselayout.html +71 -0
  114. package/src/server/forms/register-as-a-unicorn-breeder.json +393 -0
  115. package/src/server/forms/register-as-a-unicorn-breeder.yaml +251 -0
  116. package/src/server/index.test.ts +38 -66
  117. package/src/server/index.ts +15 -17
  118. package/src/server/plugins/engine/components/AutocompleteField.test.ts +71 -3
  119. package/src/server/plugins/engine/components/AutocompleteField.ts +6 -2
  120. package/src/server/plugins/engine/components/CheckboxesField.test.ts +40 -8
  121. package/src/server/plugins/engine/components/CheckboxesField.ts +7 -3
  122. package/src/server/plugins/engine/components/ComponentCollection.ts +45 -18
  123. package/src/server/plugins/engine/components/DatePartsField.test.ts +13 -4
  124. package/src/server/plugins/engine/components/DatePartsField.ts +29 -8
  125. package/src/server/plugins/engine/components/EmailAddressField.test.ts +51 -1
  126. package/src/server/plugins/engine/components/EmailAddressField.ts +17 -2
  127. package/src/server/plugins/engine/components/FileUploadField.test.ts +53 -0
  128. package/src/server/plugins/engine/components/FileUploadField.ts +52 -3
  129. package/src/server/plugins/engine/components/FormComponent.ts +24 -2
  130. package/src/server/plugins/engine/components/ListFormComponent.ts +16 -2
  131. package/src/server/plugins/engine/components/Markdown.test.ts +48 -0
  132. package/src/server/plugins/engine/components/Markdown.ts +29 -0
  133. package/src/server/plugins/engine/components/MonthYearField.test.ts +35 -0
  134. package/src/server/plugins/engine/components/MonthYearField.ts +34 -9
  135. package/src/server/plugins/engine/components/MultilineTextField.test.ts +83 -5
  136. package/src/server/plugins/engine/components/MultilineTextField.ts +37 -2
  137. package/src/server/plugins/engine/components/NumberField.test.ts +24 -2
  138. package/src/server/plugins/engine/components/NumberField.ts +23 -3
  139. package/src/server/plugins/engine/components/RadiosField.test.ts +10 -1
  140. package/src/server/plugins/engine/components/SelectField.test.ts +2 -1
  141. package/src/server/plugins/engine/components/SelectionControlField.ts +14 -0
  142. package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +30 -2
  143. package/src/server/plugins/engine/components/TelephoneNumberField.ts +17 -2
  144. package/src/server/plugins/engine/components/TextField.test.ts +33 -1
  145. package/src/server/plugins/engine/components/TextField.ts +17 -2
  146. package/src/server/plugins/engine/components/UkAddressField.test.ts +46 -3
  147. package/src/server/plugins/engine/components/UkAddressField.ts +28 -0
  148. package/src/server/plugins/engine/components/YesNoField.test.ts +9 -1
  149. package/src/server/plugins/engine/components/YesNoField.ts +24 -0
  150. package/src/server/plugins/engine/components/helpers.test.ts +24 -0
  151. package/src/server/plugins/engine/components/helpers.ts +39 -0
  152. package/src/server/plugins/engine/components/index.ts +1 -0
  153. package/src/server/plugins/engine/configureEnginePlugin.ts +32 -4
  154. package/src/server/plugins/engine/helpers.test.ts +71 -20
  155. package/src/server/plugins/engine/helpers.ts +46 -19
  156. package/src/server/plugins/engine/models/FormModel.test.ts +91 -1
  157. package/src/server/plugins/engine/models/FormModel.ts +86 -3
  158. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +46 -7
  159. package/src/server/plugins/engine/models/SummaryViewModel.ts +7 -3
  160. package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +1 -2
  161. package/src/server/plugins/engine/outputFormatters/human/v1.ts +1 -1
  162. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1 -0
  163. package/src/server/plugins/engine/pageControllers/PageController.test.ts +9 -6
  164. package/src/server/plugins/engine/pageControllers/PageController.ts +15 -5
  165. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +2 -2
  166. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +21 -6
  167. package/src/server/plugins/engine/pageControllers/validationOptions.ts +31 -17
  168. package/src/server/plugins/engine/plugin.ts +52 -22
  169. package/src/server/plugins/engine/services/formsService.js +17 -35
  170. package/src/server/plugins/engine/services/localFormsService.js +49 -0
  171. package/src/server/plugins/engine/services/notifyService.ts +1 -2
  172. package/src/server/plugins/engine/services/uploadService.js +10 -6
  173. package/src/server/plugins/engine/types.ts +10 -1
  174. package/src/server/plugins/engine/views/components/html.html +1 -1
  175. package/src/server/plugins/engine/views/components/markdown.html +5 -0
  176. package/src/server/plugins/engine/views/confirmation.html +1 -1
  177. package/src/server/plugins/engine/views/file-upload.html +1 -1
  178. package/src/server/plugins/engine/views/index.html +1 -1
  179. package/src/server/plugins/engine/views/item-delete.html +1 -1
  180. package/src/server/plugins/engine/views/repeat-list-summary.html +1 -1
  181. package/src/server/plugins/engine/views/summary.html +8 -2
  182. package/src/server/plugins/errorPages.ts +4 -26
  183. package/src/server/plugins/nunjucks/context.js +44 -34
  184. package/src/server/plugins/nunjucks/context.test.js +24 -27
  185. package/src/server/plugins/nunjucks/enviroment.test.js +9 -3
  186. package/src/server/plugins/nunjucks/types.js +3 -4
  187. package/src/server/routes/index.ts +0 -1
  188. package/src/server/utils/type-utils.ts +15 -0
  189. package/src/typings/hapi/index.d.ts +3 -9
  190. package/src/typings/joi/index.d.ts +8 -0
  191. package/.server/common/cookies.js +0 -55
  192. package/.server/common/cookies.js.map +0 -1
  193. package/.server/common/cookies.test.js +0 -15
  194. package/.server/common/cookies.test.js.map +0 -1
  195. package/.server/common/types.js +0 -6
  196. package/.server/common/types.js.map +0 -1
  197. package/.server/server/forms/README.md +0 -10
  198. package/.server/server/forms/report-a-terrorist.json +0 -270
  199. package/.server/server/forms/runner-components-test.json +0 -365
  200. package/.server/server/forms/test.json +0 -581
  201. package/.server/server/forms/test.yaml +0 -363
  202. package/.server/server/plugins/blankie.js +0 -29
  203. package/.server/server/plugins/blankie.js.map +0 -1
  204. package/.server/server/plugins/engine/services/formsService.test.js +0 -71
  205. package/.server/server/plugins/engine/services/formsService.test.js.map +0 -1
  206. package/.server/server/plugins/engine/views/layout.html +0 -199
  207. package/.server/server/plugins/router.js +0 -169
  208. package/.server/server/plugins/router.js.map +0 -1
  209. package/.server/server/routes/health.js +0 -15
  210. package/.server/server/routes/health.js.map +0 -1
  211. package/.server/server/routes/health.test.js +0 -32
  212. package/.server/server/routes/health.test.js.map +0 -1
  213. package/.server/server/utils/file-form-service.test.js +0 -52
  214. package/.server/server/utils/file-form-service.test.js.map +0 -1
  215. package/.server/server/views/404.html +0 -16
  216. package/.server/server/views/500.html +0 -19
  217. package/.server/server/views/help/accessibility-statement.html +0 -58
  218. package/.server/server/views/help/cookie-preferences.html +0 -57
  219. package/.server/server/views/help/cookies.html +0 -71
  220. package/.server/server/views/help/get-support.html +0 -37
  221. package/.server/server/views/help/privacy-notice.html +0 -68
  222. package/.server/server/views/help/terms-and-conditions.html +0 -83
  223. package/src/common/cookies.js +0 -58
  224. package/src/common/cookies.test.js +0 -23
  225. package/src/common/types.js +0 -5
  226. package/src/server/forms/README.md +0 -10
  227. package/src/server/forms/report-a-terrorist.json +0 -270
  228. package/src/server/forms/runner-components-test.json +0 -365
  229. package/src/server/forms/test.json +0 -581
  230. package/src/server/forms/test.yaml +0 -363
  231. package/src/server/plugins/blankie.test.ts +0 -73
  232. package/src/server/plugins/blankie.ts +0 -48
  233. package/src/server/plugins/engine/services/formsService.test.js +0 -90
  234. package/src/server/plugins/engine/views/layout.html +0 -199
  235. package/src/server/plugins/router.ts +0 -201
  236. package/src/server/routes/health.js +0 -13
  237. package/src/server/routes/health.test.js +0 -35
  238. package/src/server/routes/index.test.ts +0 -125
  239. package/src/server/utils/file-form-service.test.js +0 -79
  240. package/src/server/views/404.html +0 -16
  241. package/src/server/views/500.html +0 -19
  242. package/src/server/views/help/accessibility-statement.html +0 -58
  243. package/src/server/views/help/cookie-preferences.html +0 -57
  244. package/src/server/views/help/cookies.html +0 -71
  245. package/src/server/views/help/get-support.html +0 -37
  246. package/src/server/views/help/privacy-notice.html +0 -68
  247. package/src/server/views/help/terms-and-conditions.html +0 -83
@@ -317,7 +317,7 @@ export class QuestionPageController extends PageController {
317
317
  evaluationState
318
318
  } = context;
319
319
  const viewModel = this.getViewModel(request, context);
320
- viewModel.errors = collection.getErrors(viewModel.errors);
320
+ viewModel.errors = collection.getViewErrors(viewModel.errors);
321
321
 
322
322
  /**
323
323
  * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it
@@ -419,7 +419,7 @@ export class QuestionPageController extends PageController {
419
419
  */
420
420
  if (context.errors || isForceAccess) {
421
421
  const viewModel = this.getViewModel(request, context);
422
- viewModel.errors = collection.getErrors(viewModel.errors);
422
+ viewModel.errors = collection.getViewErrors(viewModel.errors);
423
423
 
424
424
  // Filter our components based on their conditions using our evaluated state
425
425
  viewModel.components = this.filterConditionalComponents(viewModel, model, evaluationState);
@@ -1 +1 @@
1
- {"version":3,"file":"QuestionPageController.js","names":["ComponentType","ControllerType","Engine","hasComponents","hasNext","hasRepeater","ComponentCollection","optionalText","getCacheService","getErrors","normalisePath","proceed","PageController","actionSchema","crumbSchema","paramsSchema","merge","QuestionPageController","collection","errorSummaryTitle","constructor","model","pageDef","components","page","formSchema","keys","crumb","action","next","def","filter","path","linkPath","pages","some","pagePath","allowContinue","engine","V2","controller","Terminal","length","getItemId","request","itemId","getFormParams","params","getViewModel","context","viewModel","query","payload","errors","pageTitle","showTitle","formComponents","isFormComponent","fieldset","label","isPageHeading","labelOrLegend","legend","size","classes","isOptional","fields","at","options","required","text","backLink","getBackLink","getRelevantPath","paths","startPath","getStartPath","relevantPath","getNextPath","evaluationState","summaryPath","getSummaryPath","statusPath","getStatusPath","defaultPath","undefined","pageIndex","indexOf","nextPage","slice","find","condition","conditionResult","fn","nextLink","link","conditions","getFormDataFromState","state","result","validate","abortEarly","stripUnknown","value","getStateFromValidForm","details","getState","cacheService","server","setState","mergeState","update","updated","filterConditionalComponents","filtered","component","content","type","Details","map","evaluatedComponent","Array","isArray","item","items","makeGetRouteHandler","h","viewName","hasMissingNotificationEmail","view","isForceAccess","formsService","services","getFormMetadata","includes","notificationEmail","slug","returnUrl","href","backPath","endsWith","getHref","makePostRouteHandler","nextPath","nextUrl","getRouteOptions","ext","onPostHandler","method","_request","continue","postRouteOptions","parse","maxBytes","Number","MAX_SAFE_INTEGER","failAction"],"sources":["../../../../../src/server/plugins/engine/pageControllers/QuestionPageController.ts"],"sourcesContent":["import {\n ComponentType,\n ControllerType,\n Engine,\n hasComponents,\n hasNext,\n hasRepeater,\n type Link,\n type Page\n} from '@defra/forms-model'\nimport { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'\nimport { type ValidationErrorItem } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { optionalText } from '~/src/server/plugins/engine/components/constants.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n getCacheService,\n getErrors,\n normalisePath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormParams,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n paramsSchema\n} from '~/src/server/schemas/index.js'\nimport { merge } from '~/src/server/services/cacheService.js'\n\nexport class QuestionPageController extends PageController {\n collection: ComponentCollection\n errorSummaryTitle = 'There is a problem'\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponents(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n\n this.collection.formSchema = this.collection.formSchema.keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n }\n\n get next(): Link[] {\n const { def, pageDef } = this\n\n if (!hasNext(pageDef)) {\n return []\n }\n\n // Remove stale links\n return pageDef.next.filter(({ path }) => {\n const linkPath = normalisePath(path)\n\n return def.pages.some((page) => {\n const pagePath = normalisePath(page.path)\n return pagePath === linkPath\n })\n })\n }\n\n get allowContinue(): boolean {\n if (this.model.engine === Engine.V2) {\n return this.pageDef.controller !== ControllerType.Terminal\n }\n\n return this.next.length > 0\n }\n\n getItemId(request?: FormContextRequest) {\n const { itemId } = this.getFormParams(request)\n return itemId ?? request?.params.itemId\n }\n\n /**\n * Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered\n * @param request - the hapi request\n * @param context - the form context\n */\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { collection, viewModel } = this\n const { query } = request\n const { payload, errors } = context\n\n let { pageTitle, showTitle } = viewModel\n\n const components = collection.getViewModel(payload, errors, query)\n const formComponents = components.filter(\n ({ isFormComponent }) => isFormComponent\n )\n\n // Single form component? Hide title and customise label or legend instead\n if (formComponents.length === 1) {\n const { model } = formComponents[0]\n const { fieldset, label } = model\n\n // Set as page heading when not following other content\n const isPageHeading = formComponents[0] === components[0]\n\n // Check for legend or label\n const labelOrLegend = fieldset?.legend ?? label\n\n // Use legend or label as page heading\n if (labelOrLegend) {\n const size = isPageHeading ? 'l' : 'm'\n\n labelOrLegend.classes =\n labelOrLegend === label\n ? `govuk-label--${size}`\n : `govuk-fieldset__legend--${size}`\n\n if (isPageHeading) {\n labelOrLegend.isPageHeading = isPageHeading\n\n // Check for optional in label\n const isOptional =\n this.collection.fields.at(0)?.options.required === false\n\n if (pageTitle) {\n labelOrLegend.text = isOptional\n ? `${pageTitle}${optionalText}`\n : pageTitle\n }\n\n pageTitle = pageTitle || labelOrLegend.text\n }\n }\n\n showTitle = !isPageHeading\n } else if (formComponents.length > 1) {\n // When there is more than one form component,\n // adjust the label/legends to give equal prominence\n for (const { model } of formComponents) {\n if (model.fieldset?.legend) {\n model.fieldset.legend.classes = 'govuk-fieldset__legend--m'\n }\n if (model.label) {\n model.label.classes = 'govuk-label--m'\n }\n }\n }\n\n return {\n ...viewModel,\n backLink: this.getBackLink(request, context),\n context,\n showTitle,\n components,\n errors\n }\n }\n\n getRelevantPath(\n request: FormRequest | FormRequestPayload,\n context: FormContext\n ) {\n const { paths } = context\n\n const startPath = this.getStartPath()\n const relevantPath = paths.at(-1) ?? startPath\n\n return !paths.length\n ? startPath // First possible path\n : relevantPath // Last possible path\n }\n\n /**\n * Apply conditions to evaluation state to determine next page path\n */\n getNextPath(context: FormContext) {\n const { model, next, path } = this\n const { evaluationState } = context\n\n const summaryPath = this.getSummaryPath()\n const statusPath = this.getStatusPath()\n\n // Walk from summary page (no next links) to status page\n let defaultPath = path === summaryPath ? statusPath : undefined\n\n if (model.engine === Engine.V2) {\n if (this.pageDef.controller !== ControllerType.Terminal) {\n const { pages } = this.model\n const pageIndex = pages.indexOf(this)\n\n // The \"next\" page is the first found after the current which is\n // either unconditional or has a condition that evaluates to \"true\"\n const nextPage = pages.slice(pageIndex + 1).find((page) => {\n const { condition } = page\n\n if (condition) {\n const conditionResult = condition.fn(evaluationState)\n\n if (!conditionResult) {\n return false\n }\n }\n\n return true\n })\n\n return nextPage?.path ?? defaultPath\n } else {\n return defaultPath\n }\n }\n\n const nextLink = next.find((link) => {\n const { condition } = link\n\n if (condition) {\n return model.conditions[condition]?.fn(evaluationState) ?? false\n }\n\n defaultPath = link.path\n return false\n })\n\n return nextLink?.path ?? defaultPath\n }\n\n /**\n * Gets the form payload (from state) for this page only\n */\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ): FormPayload {\n const { collection } = this\n\n // Form params from request\n const params = this.getFormParams(request)\n\n // Form payload from state\n const payload = collection.getFormDataFromState(state)\n\n return {\n ...params,\n ...payload\n }\n }\n\n /**\n * Gets form params (from payload) for this page only\n */\n getFormParams(request?: FormContextRequest): FormParams {\n const { payload } = request ?? {}\n\n const result = paramsSchema.validate(payload, {\n abortEarly: false,\n stripUnknown: true\n })\n\n return result.value as FormParams\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ): FormState {\n return this.collection.getStateFromValidForm(payload)\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n return getErrors(details)\n }\n\n async getState(request: FormRequest | FormRequestPayload) {\n const { query } = request\n\n // Skip get for preview URL direct access\n if ('force' in query) {\n return {}\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.getState(request)\n }\n\n async setState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { query } = request\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return state\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, state)\n }\n\n async mergeState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState,\n update: object\n ) {\n const { query } = request\n\n // Merge state before set\n const updated = merge(state, update)\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return updated\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, updated)\n }\n\n filterConditionalComponents(\n viewModel: FormPageViewModel,\n model: FormModel,\n evaluationState: Partial<Record<string, FormStateValue>>\n ) {\n // Filter our components based on their conditions using our evaluated state\n let filtered = viewModel.components.filter((component) => {\n if (\n (!!component.model.content ||\n component.type === ComponentType.Details) &&\n component.model.condition\n ) {\n const condition = model.conditions[component.model.condition]\n return condition?.fn(evaluationState)\n }\n return true\n })\n\n /**\n * For conditional reveal components (which we no longer support until GDS resolves the related accessibility issues {@link https://github.com/alphagov/govuk-frontend/issues/1991}\n */\n filtered = filtered.map((component) => {\n const evaluatedComponent = component\n const content = evaluatedComponent.model.content\n if (Array.isArray(content)) {\n evaluatedComponent.model.content = content.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n // apply condition to items for radios, checkboxes etc\n const items = evaluatedComponent.model.items\n\n if (Array.isArray(items)) {\n evaluatedComponent.model.items = items.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n\n return evaluatedComponent\n })\n\n return filtered\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, model, viewName } = this\n const { evaluationState } = context\n\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getErrors(viewModel.errors)\n\n /**\n * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it\n */\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n async hasMissingNotificationEmail(\n request: FormRequest,\n context: FormContext\n ) {\n const { path } = this\n const { params } = request\n const { isForceAccess } = context\n\n const startPath = this.getStartPath()\n const summaryPath = this.getSummaryPath()\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Warn the user if the form has no notification email set only on start page and summary page\n if ([startPath, summaryPath].includes(path) && !isForceAccess) {\n const { notificationEmail } = await getFormMetadata(params.slug)\n return !notificationEmail\n }\n\n return false\n }\n\n /**\n * Get the back link for a given progress.\n */\n protected getBackLink(\n request: FormContextRequest,\n context: FormContext\n ): BackLink | undefined {\n const { pageDef } = this\n const { path, query } = request\n const { returnUrl } = query\n const { paths } = context\n\n const itemId = this.getItemId(request)\n\n // Check answers back link\n if (returnUrl) {\n return {\n text:\n hasRepeater(pageDef) && itemId\n ? 'Go back to add another'\n : 'Go back to check answers',\n href: returnUrl\n }\n }\n\n // Item delete pages etc\n const backPath =\n itemId && !path.endsWith(itemId)\n ? paths.at(-1) // Back to main page\n : paths.at(-2) // Back to previous page\n\n // No back link\n if (!backPath) {\n return\n }\n\n // Default back link\n return {\n text: 'Back',\n href: this.getHref(backPath)\n }\n }\n\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, viewName, model } = this\n const { isForceAccess, state, evaluationState } = context\n\n /**\n * If there are any errors, render the page with the parsed errors\n * @todo Refactor to match POST REDIRECT GET pattern\n */\n if (context.errors || isForceAccess) {\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getErrors(viewModel.errors)\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n return h.view(viewName, viewModel)\n }\n\n // Save and proceed\n await this.setState(request, state)\n return this.proceed(request, h, this.getNextPath(context))\n }\n }\n\n proceed(\n request: FormContextRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n nextPath?: string\n ) {\n const nextUrl = nextPath\n ? this.getHref(nextPath) // Redirect to next page\n : this.href // Redirect to current page (refresh)\n\n return proceed(request, h, nextUrl)\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n payload: {\n parse: true,\n maxBytes: Number.MAX_SAFE_INTEGER,\n failAction: 'ignore'\n },\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"QuestionPageController.js","names":["ComponentType","ControllerType","Engine","hasComponents","hasNext","hasRepeater","ComponentCollection","optionalText","getCacheService","getErrors","normalisePath","proceed","PageController","actionSchema","crumbSchema","paramsSchema","merge","QuestionPageController","collection","errorSummaryTitle","constructor","model","pageDef","components","page","formSchema","keys","crumb","action","next","def","filter","path","linkPath","pages","some","pagePath","allowContinue","engine","V2","controller","Terminal","length","getItemId","request","itemId","getFormParams","params","getViewModel","context","viewModel","query","payload","errors","pageTitle","showTitle","formComponents","isFormComponent","fieldset","label","isPageHeading","labelOrLegend","legend","size","classes","isOptional","fields","at","options","required","text","backLink","getBackLink","getRelevantPath","paths","startPath","getStartPath","relevantPath","getNextPath","evaluationState","summaryPath","getSummaryPath","statusPath","getStatusPath","defaultPath","undefined","pageIndex","indexOf","nextPage","slice","find","condition","conditionResult","fn","nextLink","link","conditions","getFormDataFromState","state","result","validate","abortEarly","stripUnknown","value","getStateFromValidForm","details","getState","cacheService","server","setState","mergeState","update","updated","filterConditionalComponents","filtered","component","content","type","Details","map","evaluatedComponent","Array","isArray","item","items","makeGetRouteHandler","h","viewName","getViewErrors","hasMissingNotificationEmail","view","isForceAccess","formsService","services","getFormMetadata","includes","notificationEmail","slug","returnUrl","href","backPath","endsWith","getHref","makePostRouteHandler","nextPath","nextUrl","getRouteOptions","ext","onPostHandler","method","_request","continue","postRouteOptions","parse","maxBytes","Number","MAX_SAFE_INTEGER","failAction"],"sources":["../../../../../src/server/plugins/engine/pageControllers/QuestionPageController.ts"],"sourcesContent":["import {\n ComponentType,\n ControllerType,\n Engine,\n hasComponents,\n hasNext,\n hasRepeater,\n type Link,\n type Page\n} from '@defra/forms-model'\nimport { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'\nimport { type ValidationErrorItem } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { optionalText } from '~/src/server/plugins/engine/components/constants.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n getCacheService,\n getErrors,\n normalisePath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormParams,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n paramsSchema\n} from '~/src/server/schemas/index.js'\nimport { merge } from '~/src/server/services/cacheService.js'\n\nexport class QuestionPageController extends PageController {\n collection: ComponentCollection\n errorSummaryTitle = 'There is a problem'\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponents(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n\n this.collection.formSchema = this.collection.formSchema.keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n }\n\n get next(): Link[] {\n const { def, pageDef } = this\n\n if (!hasNext(pageDef)) {\n return []\n }\n\n // Remove stale links\n return pageDef.next.filter(({ path }) => {\n const linkPath = normalisePath(path)\n\n return def.pages.some((page) => {\n const pagePath = normalisePath(page.path)\n return pagePath === linkPath\n })\n })\n }\n\n get allowContinue(): boolean {\n if (this.model.engine === Engine.V2) {\n return this.pageDef.controller !== ControllerType.Terminal\n }\n\n return this.next.length > 0\n }\n\n getItemId(request?: FormContextRequest) {\n const { itemId } = this.getFormParams(request)\n return itemId ?? request?.params.itemId\n }\n\n /**\n * Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered\n * @param request - the hapi request\n * @param context - the form context\n */\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { collection, viewModel } = this\n const { query } = request\n const { payload, errors } = context\n\n let { pageTitle, showTitle } = viewModel\n\n const components = collection.getViewModel(payload, errors, query)\n const formComponents = components.filter(\n ({ isFormComponent }) => isFormComponent\n )\n\n // Single form component? Hide title and customise label or legend instead\n if (formComponents.length === 1) {\n const { model } = formComponents[0]\n const { fieldset, label } = model\n\n // Set as page heading when not following other content\n const isPageHeading = formComponents[0] === components[0]\n\n // Check for legend or label\n const labelOrLegend = fieldset?.legend ?? label\n\n // Use legend or label as page heading\n if (labelOrLegend) {\n const size = isPageHeading ? 'l' : 'm'\n\n labelOrLegend.classes =\n labelOrLegend === label\n ? `govuk-label--${size}`\n : `govuk-fieldset__legend--${size}`\n\n if (isPageHeading) {\n labelOrLegend.isPageHeading = isPageHeading\n\n // Check for optional in label\n const isOptional =\n this.collection.fields.at(0)?.options.required === false\n\n if (pageTitle) {\n labelOrLegend.text = isOptional\n ? `${pageTitle}${optionalText}`\n : pageTitle\n }\n\n pageTitle = pageTitle || labelOrLegend.text\n }\n }\n\n showTitle = !isPageHeading\n } else if (formComponents.length > 1) {\n // When there is more than one form component,\n // adjust the label/legends to give equal prominence\n for (const { model } of formComponents) {\n if (model.fieldset?.legend) {\n model.fieldset.legend.classes = 'govuk-fieldset__legend--m'\n }\n if (model.label) {\n model.label.classes = 'govuk-label--m'\n }\n }\n }\n\n return {\n ...viewModel,\n backLink: this.getBackLink(request, context),\n context,\n showTitle,\n components,\n errors\n }\n }\n\n getRelevantPath(\n request: FormRequest | FormRequestPayload,\n context: FormContext\n ) {\n const { paths } = context\n\n const startPath = this.getStartPath()\n const relevantPath = paths.at(-1) ?? startPath\n\n return !paths.length\n ? startPath // First possible path\n : relevantPath // Last possible path\n }\n\n /**\n * Apply conditions to evaluation state to determine next page path\n */\n getNextPath(context: FormContext) {\n const { model, next, path } = this\n const { evaluationState } = context\n\n const summaryPath = this.getSummaryPath()\n const statusPath = this.getStatusPath()\n\n // Walk from summary page (no next links) to status page\n let defaultPath = path === summaryPath ? statusPath : undefined\n\n if (model.engine === Engine.V2) {\n if (this.pageDef.controller !== ControllerType.Terminal) {\n const { pages } = this.model\n const pageIndex = pages.indexOf(this)\n\n // The \"next\" page is the first found after the current which is\n // either unconditional or has a condition that evaluates to \"true\"\n const nextPage = pages.slice(pageIndex + 1).find((page) => {\n const { condition } = page\n\n if (condition) {\n const conditionResult = condition.fn(evaluationState)\n\n if (!conditionResult) {\n return false\n }\n }\n\n return true\n })\n\n return nextPage?.path ?? defaultPath\n } else {\n return defaultPath\n }\n }\n\n const nextLink = next.find((link) => {\n const { condition } = link\n\n if (condition) {\n return model.conditions[condition]?.fn(evaluationState) ?? false\n }\n\n defaultPath = link.path\n return false\n })\n\n return nextLink?.path ?? defaultPath\n }\n\n /**\n * Gets the form payload (from state) for this page only\n */\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ): FormPayload {\n const { collection } = this\n\n // Form params from request\n const params = this.getFormParams(request)\n\n // Form payload from state\n const payload = collection.getFormDataFromState(state)\n\n return {\n ...params,\n ...payload\n }\n }\n\n /**\n * Gets form params (from payload) for this page only\n */\n getFormParams(request?: FormContextRequest): FormParams {\n const { payload } = request ?? {}\n\n const result = paramsSchema.validate(payload, {\n abortEarly: false,\n stripUnknown: true\n })\n\n return result.value as FormParams\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ): FormState {\n return this.collection.getStateFromValidForm(payload)\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n return getErrors(details)\n }\n\n async getState(request: FormRequest | FormRequestPayload) {\n const { query } = request\n\n // Skip get for preview URL direct access\n if ('force' in query) {\n return {}\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.getState(request)\n }\n\n async setState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { query } = request\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return state\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, state)\n }\n\n async mergeState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState,\n update: object\n ) {\n const { query } = request\n\n // Merge state before set\n const updated = merge(state, update)\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return updated\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, updated)\n }\n\n filterConditionalComponents(\n viewModel: FormPageViewModel,\n model: FormModel,\n evaluationState: Partial<Record<string, FormStateValue>>\n ) {\n // Filter our components based on their conditions using our evaluated state\n let filtered = viewModel.components.filter((component) => {\n if (\n (!!component.model.content ||\n component.type === ComponentType.Details) &&\n component.model.condition\n ) {\n const condition = model.conditions[component.model.condition]\n return condition?.fn(evaluationState)\n }\n return true\n })\n\n /**\n * For conditional reveal components (which we no longer support until GDS resolves the related accessibility issues {@link https://github.com/alphagov/govuk-frontend/issues/1991}\n */\n filtered = filtered.map((component) => {\n const evaluatedComponent = component\n const content = evaluatedComponent.model.content\n if (Array.isArray(content)) {\n evaluatedComponent.model.content = content.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n // apply condition to items for radios, checkboxes etc\n const items = evaluatedComponent.model.items\n\n if (Array.isArray(items)) {\n evaluatedComponent.model.items = items.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n\n return evaluatedComponent\n })\n\n return filtered\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, model, viewName } = this\n const { evaluationState } = context\n\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n /**\n * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it\n */\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n async hasMissingNotificationEmail(\n request: FormRequest,\n context: FormContext\n ) {\n const { path } = this\n const { params } = request\n const { isForceAccess } = context\n\n const startPath = this.getStartPath()\n const summaryPath = this.getSummaryPath()\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Warn the user if the form has no notification email set only on start page and summary page\n if ([startPath, summaryPath].includes(path) && !isForceAccess) {\n const { notificationEmail } = await getFormMetadata(params.slug)\n return !notificationEmail\n }\n\n return false\n }\n\n /**\n * Get the back link for a given progress.\n */\n protected getBackLink(\n request: FormContextRequest,\n context: FormContext\n ): BackLink | undefined {\n const { pageDef } = this\n const { path, query } = request\n const { returnUrl } = query\n const { paths } = context\n\n const itemId = this.getItemId(request)\n\n // Check answers back link\n if (returnUrl) {\n return {\n text:\n hasRepeater(pageDef) && itemId\n ? 'Go back to add another'\n : 'Go back to check answers',\n href: returnUrl\n }\n }\n\n // Item delete pages etc\n const backPath =\n itemId && !path.endsWith(itemId)\n ? paths.at(-1) // Back to main page\n : paths.at(-2) // Back to previous page\n\n // No back link\n if (!backPath) {\n return\n }\n\n // Default back link\n return {\n text: 'Back',\n href: this.getHref(backPath)\n }\n }\n\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, viewName, model } = this\n const { isForceAccess, state, evaluationState } = context\n\n /**\n * If there are any errors, render the page with the parsed errors\n * @todo Refactor to match POST REDIRECT GET pattern\n */\n if (context.errors || isForceAccess) {\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n return h.view(viewName, viewModel)\n }\n\n // Save and proceed\n await this.setState(request, state)\n return this.proceed(request, h, this.getNextPath(context))\n }\n }\n\n proceed(\n request: FormContextRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n nextPath?: string\n ) {\n const nextUrl = nextPath\n ? this.getHref(nextPath) // Redirect to next page\n : this.href // Redirect to current page (refresh)\n\n return proceed(request, h, nextUrl)\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n payload: {\n parse: true,\n maxBytes: Number.MAX_SAFE_INTEGER,\n failAction: 'ignore'\n },\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,4 +1,6 @@
1
+ import { hasComponentsEvenIfNoNext } from '@defra/forms-model';
1
2
  import Boom from '@hapi/boom';
3
+ import { ComponentCollection } from "../components/ComponentCollection.js";
2
4
  import { FileUploadField } from "../components/FileUploadField.js";
3
5
  import { getAnswer } from "../components/helpers.js";
4
6
  import { checkEmailAddressForLiveFormSubmission, checkFormStatus, getCacheService } from "../helpers.js";
@@ -12,15 +14,30 @@ export class SummaryPageController extends QuestionPageController {
12
14
  constructor(model, pageDef) {
13
15
  super(model, pageDef);
14
16
  this.viewName = 'summary';
17
+
18
+ // Components collection
19
+ this.collection = new ComponentCollection(hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [], {
20
+ model,
21
+ page: this
22
+ });
15
23
  }
16
24
  getSummaryViewModel(request, context) {
17
25
  const viewModel = new SummaryViewModel(request, this, context);
26
+ const {
27
+ query
28
+ } = request;
29
+ const {
30
+ payload,
31
+ errors
32
+ } = context;
33
+ const components = this.collection.getViewModel(payload, errors, query);
18
34
 
19
35
  // We already figure these out in the base page controller. Take them and apply them to our page-specific model.
20
36
  // This is a stop-gap until we can add proper inheritance in place.
21
37
  viewModel.backLink = this.getBackLink(request, context);
22
38
  viewModel.feedbackLink = this.feedbackLink;
23
39
  viewModel.phaseTag = this.phaseTag;
40
+ viewModel.components = components;
24
41
  return viewModel;
25
42
  }
26
43
 
@@ -67,7 +84,7 @@ export class SummaryPageController extends QuestionPageController {
67
84
  } = await getFormMetadata(params.slug);
68
85
  const {
69
86
  isPreview
70
- } = checkFormStatus(request.path);
87
+ } = checkFormStatus(request.params);
71
88
  const emailAddress = notificationEmail ?? this.model.def.outputEmail;
72
89
  checkEmailAddressForLiveFormSubmission(emailAddress, isPreview);
73
90
 
@@ -99,10 +116,7 @@ export class SummaryPageController extends QuestionPageController {
99
116
  }
100
117
  async function submitForm(request, summaryViewModel, model, state, emailAddress) {
101
118
  await extendFileRetention(model, state, emailAddress);
102
- const {
103
- path
104
- } = request;
105
- const formStatus = checkFormStatus(path);
119
+ const formStatus = checkFormStatus(request.params);
106
120
  const logTags = ['submit', 'submissionApi'];
107
121
  request.logger.info(logTags, 'Preparing email', formStatus);
108
122
 
@@ -1 +1 @@
1
- {"version":3,"file":"SummaryPageController.js","names":["Boom","FileUploadField","getAnswer","checkEmailAddressForLiveFormSubmission","checkFormStatus","getCacheService","SummaryViewModel","QuestionPageController","SummaryPageController","constructor","model","pageDef","viewName","getSummaryViewModel","request","context","viewModel","backLink","getBackLink","feedbackLink","phaseTag","makeGetRouteHandler","h","hasMissingNotificationEmail","view","makePostRouteHandler","params","state","cacheService","server","formsService","services","getFormMetadata","notificationEmail","slug","isPreview","path","emailAddress","def","outputEmail","submitForm","setConfirmationState","confirmed","clearState","proceed","getStatusPath","postRouteOptions","ext","onPreHandler","method","continue","summaryViewModel","extendFileRetention","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","yar","id","undefined","badRequest","outputService","submit","updatedRetrievalKey","formSubmissionService","persistFiles","files","pages","forEach","page","fileUploadComponents","collection","fields","filter","component","values","getFormValueFromState","length","push","map","status","fileId","form","file","initiatedRetrievalKey","metadata","retrievalKey","sessionId","payload","main","item","name","title","label","value","field","format","repeaters","subItems","detailItems","subItem","relevantPages","href","flatMap","flat"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import { type PageSummary, type SubmitPayload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'\n\nimport { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: PageSummary\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n constructor(model: FormModel, pageDef: PageSummary) {\n super(model, pageDef)\n this.viewName = 'summary'\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n // We already figure these out in the base page controller. Take them and apply them to our page-specific model.\n // This is a stop-gap until we can add proper inheritance in place.\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n\n return viewModel\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { viewName } = this\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { model } = this\n const { params } = request\n const { state } = context\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Get the form metadata using the `slug` param\n const { notificationEmail } = await getFormMetadata(params.slug)\n const { isPreview } = checkFormStatus(request.path)\n const emailAddress = notificationEmail ?? this.model.def.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Send submission email\n if (emailAddress) {\n const viewModel = this.getSummaryViewModel(request, context)\n await submitForm(request, viewModel, model, state, emailAddress)\n }\n\n await cacheService.setConfirmationState(request, { confirmed: true })\n\n // Clear all form data\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nasync function submitForm(\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n state: FormSubmissionState,\n emailAddress: string\n) {\n await extendFileRetention(model, state, emailAddress)\n\n const { path } = request\n const formStatus = checkFormStatus(path)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n // Get detail items\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n // Submit data\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n return model.services.outputService.submit(\n request,\n model,\n emailAddress,\n items,\n submitResponse\n )\n}\n\nasync function extendFileRetention(\n model: FormModel,\n state: FormSubmissionState,\n updatedRetrievalKey: string\n) {\n const { formSubmissionService } = model.services\n const { persistFiles } = formSubmissionService\n const files: { fileId: string; initiatedRetrievalKey: string }[] = []\n\n // For each file upload component with files in\n // state, add the files to the batch getting persisted\n model.pages.forEach((page) => {\n const fileUploadComponents = page.collection.fields.filter(\n (component) => component instanceof FileUploadField\n )\n\n fileUploadComponents.forEach((component) => {\n const values = component.getFormValueFromState(state)\n if (!values?.length) {\n return\n }\n\n files.push(\n ...values.map(({ status }) => ({\n fileId: status.form.file.fileId,\n initiatedRetrievalKey: status.metadata.retrievalKey\n }))\n )\n })\n })\n\n if (files.length) {\n return persistFiles(files, updatedRetrievalKey)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n\n // Main form answers\n main: items\n .filter((item) => 'field' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n value: getAnswer(item.field, item.state, { format: 'data' })\n })),\n\n // Repeater form answers\n repeaters: items\n .filter((item) => 'subItems' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n\n // Repeater item values\n value: item.subItems.map((detailItems) =>\n detailItems.map((subItem) => ({\n name: subItem.name,\n title: subItem.label,\n value: getAnswer(subItem.field, subItem.state, { format: 'data' })\n }))\n )\n }))\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n return context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","ComponentCollection","FileUploadField","getAnswer","checkEmailAddressForLiveFormSubmission","checkFormStatus","getCacheService","SummaryViewModel","QuestionPageController","SummaryPageController","constructor","model","pageDef","viewName","collection","components","page","getSummaryViewModel","request","context","viewModel","query","payload","errors","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","makeGetRouteHandler","h","hasMissingNotificationEmail","view","makePostRouteHandler","params","state","cacheService","server","formsService","services","getFormMetadata","notificationEmail","slug","isPreview","emailAddress","def","outputEmail","submitForm","setConfirmationState","confirmed","clearState","proceed","getStatusPath","postRouteOptions","ext","onPreHandler","method","continue","summaryViewModel","extendFileRetention","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","yar","id","undefined","badRequest","outputService","submit","updatedRetrievalKey","formSubmissionService","persistFiles","files","pages","forEach","fileUploadComponents","fields","filter","component","values","getFormValueFromState","length","push","map","status","fileId","form","file","initiatedRetrievalKey","metadata","retrievalKey","sessionId","main","item","name","title","label","value","field","format","repeaters","subItems","detailItems","subItem","relevantPages","href","flatMap","flat"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import {\n hasComponentsEvenIfNoNext,\n type Page,\n type SubmitPayload\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n this.viewName = 'summary'\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n const { query } = request\n const { payload, errors } = context\n const components = this.collection.getViewModel(payload, errors, query)\n\n // We already figure these out in the base page controller. Take them and apply them to our page-specific model.\n // This is a stop-gap until we can add proper inheritance in place.\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n\n return viewModel\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { viewName } = this\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { model } = this\n const { params } = request\n const { state } = context\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Get the form metadata using the `slug` param\n const { notificationEmail } = await getFormMetadata(params.slug)\n const { isPreview } = checkFormStatus(request.params)\n const emailAddress = notificationEmail ?? this.model.def.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Send submission email\n if (emailAddress) {\n const viewModel = this.getSummaryViewModel(request, context)\n await submitForm(request, viewModel, model, state, emailAddress)\n }\n\n await cacheService.setConfirmationState(request, { confirmed: true })\n\n // Clear all form data\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nasync function submitForm(\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n state: FormSubmissionState,\n emailAddress: string\n) {\n await extendFileRetention(model, state, emailAddress)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n // Get detail items\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n // Submit data\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n return model.services.outputService.submit(\n request,\n model,\n emailAddress,\n items,\n submitResponse\n )\n}\n\nasync function extendFileRetention(\n model: FormModel,\n state: FormSubmissionState,\n updatedRetrievalKey: string\n) {\n const { formSubmissionService } = model.services\n const { persistFiles } = formSubmissionService\n const files: { fileId: string; initiatedRetrievalKey: string }[] = []\n\n // For each file upload component with files in\n // state, add the files to the batch getting persisted\n model.pages.forEach((page) => {\n const fileUploadComponents = page.collection.fields.filter(\n (component) => component instanceof FileUploadField\n )\n\n fileUploadComponents.forEach((component) => {\n const values = component.getFormValueFromState(state)\n if (!values?.length) {\n return\n }\n\n files.push(\n ...values.map(({ status }) => ({\n fileId: status.form.file.fileId,\n initiatedRetrievalKey: status.metadata.retrievalKey\n }))\n )\n })\n })\n\n if (files.length) {\n return persistFiles(files, updatedRetrievalKey)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n\n // Main form answers\n main: items\n .filter((item) => 'field' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n value: getAnswer(item.field, item.state, { format: 'data' })\n })),\n\n // Repeater form answers\n repeaters: items\n .filter((item) => 'subItems' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n\n // Repeater item values\n value: item.subItems.map((detailItems) =>\n detailItems.map((subItem) => ({\n name: subItem.name,\n title: subItem.label,\n value: getAnswer(subItem.field, subItem.state, { format: 'data' })\n }))\n )\n }))\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n return context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n}\n"],"mappings":"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","ignoreList":[]}
@@ -13,17 +13,14 @@ const opts = {
13
13
  * see @link https://joi.dev/api/?v=17.4.2#template-syntax for template syntax
14
14
  */
15
15
  export const messageTemplate = {
16
- // @ts-expect-error - joi.expression options type issue
17
16
  required: joi.expression('Enter {{lowerFirst(#label)}}', opts),
18
- // @ts-expect-error - joi.expression options type issue
19
17
  selectRequired: joi.expression('Select {{lowerFirst(#label)}}', opts),
18
+ selectYesNoRequired: '{{#label}} - select yes or no',
20
19
  max: '{{#label}} must be {{#limit}} characters or less',
21
20
  min: '{{#label}} must be {{#limit}} characters or more',
22
- // @ts-expect-error - joi.expression options type issue
21
+ minMax: '{{#label}} must be between {{#min}} and {{#max}} characters',
23
22
  pattern: joi.expression('Enter a valid {{lowerFirst(#label)}}', opts),
24
- format: joi.expression('Enter {{lowerFirst(#label)}} in the correct format',
25
- // @ts-expect-error - joi.expression options type issue
26
- opts),
23
+ format: joi.expression('Enter {{lowerFirst(#label)}} in the correct format', opts),
27
24
  number: '{{#label}} must be a number',
28
25
  numberPrecision: '{{#label}} must have {{#limit}} or fewer decimal places',
29
26
  numberInteger: '{{#label}} must be a whole number',
@@ -32,11 +29,8 @@ export const messageTemplate = {
32
29
  maxWords: '{{#label}} must be {{#limit}} words or fewer',
33
30
  // Nested fields use component title
34
31
 
35
- // @ts-expect-error - joi.expression options type issue
36
32
  objectRequired: joi.expression('Enter {{#label}}', opts),
37
- objectMissing: joi.expression('{{#title}} must include a {{lowerFirst(#label)}}',
38
- // @ts-expect-error - joi.expression options type issue
39
- opts),
33
+ objectMissing: joi.expression('{{#title}} must include a {{lowerFirst(#label)}}', opts),
40
34
  dateFormat: '{{#title}} must be a real date',
41
35
  dateMin: '{{#title}} must be the same as or after {{#limit}}',
42
36
  dateMax: '{{#title}} must be the same as or before {{#limit}}'
@@ -65,9 +59,10 @@ export const messages = {
65
59
  'date.min': messageTemplate.dateMin,
66
60
  'date.max': messageTemplate.dateMax
67
61
  };
62
+ export const messagesPre = messages;
68
63
  export const validationOptions = {
69
64
  abortEarly: false,
70
- messages,
65
+ messages: messagesPre,
71
66
  errors: {
72
67
  wrap: {
73
68
  array: false,
@@ -1 +1 @@
1
- {"version":3,"file":"validationOptions.js","names":["joi","lowerFirst","opts","functions","messageTemplate","required","expression","selectRequired","max","min","pattern","format","number","numberPrecision","numberInteger","numberMin","numberMax","maxWords","objectRequired","objectMissing","dateFormat","dateMin","dateMax","messages","validationOptions","abortEarly","errors","wrap","array","label"],"sources":["../../../../../src/server/plugins/engine/pageControllers/validationOptions.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n// Declaration above is needed for: https://github.com/hapijs/joi/issues/3064\n\nimport joi, { type LanguageMessages, type ValidationOptions } from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\nconst opts = {\n functions: {\n lowerFirst\n }\n}\n\n/**\n * see @link https://joi.dev/api/?v=17.4.2#template-syntax for template syntax\n */\nexport const messageTemplate = {\n // @ts-expect-error - joi.expression options type issue\n required: joi.expression('Enter {{lowerFirst(#label)}}', opts),\n // @ts-expect-error - joi.expression options type issue\n selectRequired: joi.expression('Select {{lowerFirst(#label)}}', opts),\n max: '{{#label}} must be {{#limit}} characters or less',\n min: '{{#label}} must be {{#limit}} characters or more',\n // @ts-expect-error - joi.expression options type issue\n pattern: joi.expression('Enter a valid {{lowerFirst(#label)}}', opts),\n format: joi.expression(\n 'Enter {{lowerFirst(#label)}} in the correct format',\n // @ts-expect-error - joi.expression options type issue\n opts\n ),\n number: '{{#label}} must be a number',\n numberPrecision: '{{#label}} must have {{#limit}} or fewer decimal places',\n numberInteger: '{{#label}} must be a whole number',\n numberMin: '{{#label}} must be {{#limit}} or higher',\n numberMax: '{{#label}} must be {{#limit}} or lower',\n maxWords: '{{#label}} must be {{#limit}} words or fewer',\n\n // Nested fields use component title\n\n // @ts-expect-error - joi.expression options type issue\n objectRequired: joi.expression('Enter {{#label}}', opts),\n objectMissing: joi.expression(\n '{{#title}} must include a {{lowerFirst(#label)}}',\n // @ts-expect-error - joi.expression options type issue\n opts\n ),\n dateFormat: '{{#title}} must be a real date',\n dateMin: '{{#title}} must be the same as or after {{#limit}}',\n dateMax: '{{#title}} must be the same as or before {{#limit}}'\n}\n\nexport const messages: LanguageMessages = {\n 'string.base': messageTemplate.required,\n 'string.min': messageTemplate.min,\n 'string.empty': messageTemplate.required,\n 'string.max': messageTemplate.max,\n 'string.email': messageTemplate.format,\n 'string.pattern.base': messageTemplate.pattern,\n 'string.maxWords': messageTemplate.maxWords,\n\n 'number.base': messageTemplate.number,\n 'number.precision': messageTemplate.numberPrecision,\n 'number.integer': messageTemplate.numberInteger,\n 'number.unsafe': messageTemplate.format,\n 'number.min': messageTemplate.numberMin,\n 'number.max': messageTemplate.numberMax,\n\n 'object.required': messageTemplate.objectRequired,\n 'object.and': messageTemplate.objectMissing,\n\n 'any.only': messageTemplate.selectRequired,\n 'any.required': messageTemplate.selectRequired,\n 'any.empty': messageTemplate.required,\n\n 'date.base': messageTemplate.dateFormat,\n 'date.format': messageTemplate.dateFormat,\n 'date.min': messageTemplate.dateMin,\n 'date.max': messageTemplate.dateMax\n}\n\nexport const validationOptions: ValidationOptions = {\n abortEarly: false,\n messages,\n errors: {\n wrap: {\n array: false,\n label: false\n }\n }\n}\n"],"mappings":"AAAA;AACA;;AAEA,OAAOA,GAAG,MAAyD,KAAK;AACxE,OAAOC,UAAU,MAAM,sBAAsB;AAE7C,MAAMC,IAAI,GAAG;EACXC,SAAS,EAAE;IACTF;EACF;AACF,CAAC;;AAED;AACA;AACA;AACA,OAAO,MAAMG,eAAe,GAAG;EAC7B;EACAC,QAAQ,EAAEL,GAAG,CAACM,UAAU,CAAC,8BAA8B,EAAEJ,IAAI,CAAC;EAC9D;EACAK,cAAc,EAAEP,GAAG,CAACM,UAAU,CAAC,+BAA+B,EAAEJ,IAAI,CAAC;EACrEM,GAAG,EAAE,kDAAkD;EACvDC,GAAG,EAAE,kDAAkD;EACvD;EACAC,OAAO,EAAEV,GAAG,CAACM,UAAU,CAAC,sCAAsC,EAAEJ,IAAI,CAAC;EACrES,MAAM,EAAEX,GAAG,CAACM,UAAU,CACpB,oDAAoD;EACpD;EACAJ,IACF,CAAC;EACDU,MAAM,EAAE,6BAA6B;EACrCC,eAAe,EAAE,yDAAyD;EAC1EC,aAAa,EAAE,mCAAmC;EAClDC,SAAS,EAAE,yCAAyC;EACpDC,SAAS,EAAE,wCAAwC;EACnDC,QAAQ,EAAE,8CAA8C;EAExD;;EAEA;EACAC,cAAc,EAAElB,GAAG,CAACM,UAAU,CAAC,kBAAkB,EAAEJ,IAAI,CAAC;EACxDiB,aAAa,EAAEnB,GAAG,CAACM,UAAU,CAC3B,kDAAkD;EAClD;EACAJ,IACF,CAAC;EACDkB,UAAU,EAAE,gCAAgC;EAC5CC,OAAO,EAAE,oDAAoD;EAC7DC,OAAO,EAAE;AACX,CAAC;AAED,OAAO,MAAMC,QAA0B,GAAG;EACxC,aAAa,EAAEnB,eAAe,CAACC,QAAQ;EACvC,YAAY,EAAED,eAAe,CAACK,GAAG;EACjC,cAAc,EAAEL,eAAe,CAACC,QAAQ;EACxC,YAAY,EAAED,eAAe,CAACI,GAAG;EACjC,cAAc,EAAEJ,eAAe,CAACO,MAAM;EACtC,qBAAqB,EAAEP,eAAe,CAACM,OAAO;EAC9C,iBAAiB,EAAEN,eAAe,CAACa,QAAQ;EAE3C,aAAa,EAAEb,eAAe,CAACQ,MAAM;EACrC,kBAAkB,EAAER,eAAe,CAACS,eAAe;EACnD,gBAAgB,EAAET,eAAe,CAACU,aAAa;EAC/C,eAAe,EAAEV,eAAe,CAACO,MAAM;EACvC,YAAY,EAAEP,eAAe,CAACW,SAAS;EACvC,YAAY,EAAEX,eAAe,CAACY,SAAS;EAEvC,iBAAiB,EAAEZ,eAAe,CAACc,cAAc;EACjD,YAAY,EAAEd,eAAe,CAACe,aAAa;EAE3C,UAAU,EAAEf,eAAe,CAACG,cAAc;EAC1C,cAAc,EAAEH,eAAe,CAACG,cAAc;EAC9C,WAAW,EAAEH,eAAe,CAACC,QAAQ;EAErC,WAAW,EAAED,eAAe,CAACgB,UAAU;EACvC,aAAa,EAAEhB,eAAe,CAACgB,UAAU;EACzC,UAAU,EAAEhB,eAAe,CAACiB,OAAO;EACnC,UAAU,EAAEjB,eAAe,CAACkB;AAC9B,CAAC;AAED,OAAO,MAAME,iBAAoC,GAAG;EAClDC,UAAU,EAAE,KAAK;EACjBF,QAAQ;EACRG,MAAM,EAAE;IACNC,IAAI,EAAE;MACJC,KAAK,EAAE,KAAK;MACZC,KAAK,EAAE;IACT;EACF;AACF,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"validationOptions.js","names":["joi","lowerFirst","opts","functions","messageTemplate","required","expression","selectRequired","selectYesNoRequired","max","min","minMax","pattern","format","number","numberPrecision","numberInteger","numberMin","numberMax","maxWords","objectRequired","objectMissing","dateFormat","dateMin","dateMax","messages","messagesPre","validationOptions","abortEarly","errors","wrap","array","label"],"sources":["../../../../../src/server/plugins/engine/pageControllers/validationOptions.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n// Declaration above is needed for: https://github.com/hapijs/joi/issues/3064\n\nimport joi, {\n type JoiExpression,\n type LanguageMessages,\n type LanguageMessagesExt,\n type ReferenceOptions,\n type ValidationOptions\n} from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\nconst opts = {\n functions: {\n lowerFirst\n }\n} as ReferenceOptions\n\n/**\n * see @link https://joi.dev/api/?v=17.4.2#template-syntax for template syntax\n */\nexport const messageTemplate: Record<string, JoiExpression> = {\n required: joi.expression(\n 'Enter {{lowerFirst(#label)}}',\n opts\n ) as JoiExpression,\n selectRequired: joi.expression(\n 'Select {{lowerFirst(#label)}}',\n opts\n ) as JoiExpression,\n selectYesNoRequired: '{{#label}} - select yes or no',\n max: '{{#label}} must be {{#limit}} characters or less',\n min: '{{#label}} must be {{#limit}} characters or more',\n minMax: '{{#label}} must be between {{#min}} and {{#max}} characters',\n pattern: joi.expression(\n 'Enter a valid {{lowerFirst(#label)}}',\n opts\n ) as JoiExpression,\n format: joi.expression(\n 'Enter {{lowerFirst(#label)}} in the correct format',\n opts\n ) as JoiExpression,\n number: '{{#label}} must be a number',\n numberPrecision: '{{#label}} must have {{#limit}} or fewer decimal places',\n numberInteger: '{{#label}} must be a whole number',\n numberMin: '{{#label}} must be {{#limit}} or higher',\n numberMax: '{{#label}} must be {{#limit}} or lower',\n maxWords: '{{#label}} must be {{#limit}} words or fewer',\n\n // Nested fields use component title\n\n objectRequired: joi.expression('Enter {{#label}}', opts) as JoiExpression,\n objectMissing: joi.expression(\n '{{#title}} must include a {{lowerFirst(#label)}}',\n opts\n ) as JoiExpression,\n dateFormat: '{{#title}} must be a real date',\n dateMin: '{{#title}} must be the same as or after {{#limit}}',\n dateMax: '{{#title}} must be the same as or before {{#limit}}'\n}\n\nexport const messages: LanguageMessagesExt = {\n 'string.base': messageTemplate.required,\n 'string.min': messageTemplate.min,\n 'string.empty': messageTemplate.required,\n 'string.max': messageTemplate.max,\n 'string.email': messageTemplate.format,\n 'string.pattern.base': messageTemplate.pattern,\n 'string.maxWords': messageTemplate.maxWords,\n\n 'number.base': messageTemplate.number,\n 'number.precision': messageTemplate.numberPrecision,\n 'number.integer': messageTemplate.numberInteger,\n 'number.unsafe': messageTemplate.format,\n 'number.min': messageTemplate.numberMin,\n 'number.max': messageTemplate.numberMax,\n\n 'object.required': messageTemplate.objectRequired,\n 'object.and': messageTemplate.objectMissing,\n\n 'any.only': messageTemplate.selectRequired,\n 'any.required': messageTemplate.selectRequired,\n 'any.empty': messageTemplate.required,\n\n 'date.base': messageTemplate.dateFormat,\n 'date.format': messageTemplate.dateFormat,\n 'date.min': messageTemplate.dateMin,\n 'date.max': messageTemplate.dateMax\n}\n\nexport const messagesPre: LanguageMessages =\n messages as unknown as LanguageMessages\n\nexport const validationOptions: ValidationOptions = {\n abortEarly: false,\n messages: messagesPre,\n errors: {\n wrap: {\n array: false,\n label: false\n }\n }\n}\n"],"mappings":"AAAA;AACA;;AAEA,OAAOA,GAAG,MAMH,KAAK;AACZ,OAAOC,UAAU,MAAM,sBAAsB;AAE7C,MAAMC,IAAI,GAAG;EACXC,SAAS,EAAE;IACTF;EACF;AACF,CAAqB;;AAErB;AACA;AACA;AACA,OAAO,MAAMG,eAA8C,GAAG;EAC5DC,QAAQ,EAAEL,GAAG,CAACM,UAAU,CACtB,8BAA8B,EAC9BJ,IACF,CAAkB;EAClBK,cAAc,EAAEP,GAAG,CAACM,UAAU,CAC5B,+BAA+B,EAC/BJ,IACF,CAAkB;EAClBM,mBAAmB,EAAE,+BAA+B;EACpDC,GAAG,EAAE,kDAAkD;EACvDC,GAAG,EAAE,kDAAkD;EACvDC,MAAM,EAAE,6DAA6D;EACrEC,OAAO,EAAEZ,GAAG,CAACM,UAAU,CACrB,sCAAsC,EACtCJ,IACF,CAAkB;EAClBW,MAAM,EAAEb,GAAG,CAACM,UAAU,CACpB,oDAAoD,EACpDJ,IACF,CAAkB;EAClBY,MAAM,EAAE,6BAA6B;EACrCC,eAAe,EAAE,yDAAyD;EAC1EC,aAAa,EAAE,mCAAmC;EAClDC,SAAS,EAAE,yCAAyC;EACpDC,SAAS,EAAE,wCAAwC;EACnDC,QAAQ,EAAE,8CAA8C;EAExD;;EAEAC,cAAc,EAAEpB,GAAG,CAACM,UAAU,CAAC,kBAAkB,EAAEJ,IAAI,CAAkB;EACzEmB,aAAa,EAAErB,GAAG,CAACM,UAAU,CAC3B,kDAAkD,EAClDJ,IACF,CAAkB;EAClBoB,UAAU,EAAE,gCAAgC;EAC5CC,OAAO,EAAE,oDAAoD;EAC7DC,OAAO,EAAE;AACX,CAAC;AAED,OAAO,MAAMC,QAA6B,GAAG;EAC3C,aAAa,EAAErB,eAAe,CAACC,QAAQ;EACvC,YAAY,EAAED,eAAe,CAACM,GAAG;EACjC,cAAc,EAAEN,eAAe,CAACC,QAAQ;EACxC,YAAY,EAAED,eAAe,CAACK,GAAG;EACjC,cAAc,EAAEL,eAAe,CAACS,MAAM;EACtC,qBAAqB,EAAET,eAAe,CAACQ,OAAO;EAC9C,iBAAiB,EAAER,eAAe,CAACe,QAAQ;EAE3C,aAAa,EAAEf,eAAe,CAACU,MAAM;EACrC,kBAAkB,EAAEV,eAAe,CAACW,eAAe;EACnD,gBAAgB,EAAEX,eAAe,CAACY,aAAa;EAC/C,eAAe,EAAEZ,eAAe,CAACS,MAAM;EACvC,YAAY,EAAET,eAAe,CAACa,SAAS;EACvC,YAAY,EAAEb,eAAe,CAACc,SAAS;EAEvC,iBAAiB,EAAEd,eAAe,CAACgB,cAAc;EACjD,YAAY,EAAEhB,eAAe,CAACiB,aAAa;EAE3C,UAAU,EAAEjB,eAAe,CAACG,cAAc;EAC1C,cAAc,EAAEH,eAAe,CAACG,cAAc;EAC9C,WAAW,EAAEH,eAAe,CAACC,QAAQ;EAErC,WAAW,EAAED,eAAe,CAACkB,UAAU;EACvC,aAAa,EAAElB,eAAe,CAACkB,UAAU;EACzC,UAAU,EAAElB,eAAe,CAACmB,OAAO;EACnC,UAAU,EAAEnB,eAAe,CAACoB;AAC9B,CAAC;AAED,OAAO,MAAME,WAA6B,GACxCD,QAAuC;AAEzC,OAAO,MAAME,iBAAoC,GAAG;EAClDC,UAAU,EAAE,KAAK;EACjBH,QAAQ,EAAEC,WAAW;EACrBG,MAAM,EAAE;IACNC,IAAI,EAAE;MACJC,KAAK,EAAE,KAAK;MACZC,KAAK,EAAE;IACT;EACF;AACF,CAAC","ignoreList":[]}
@@ -1,12 +1,16 @@
1
+ import { existsSync } from 'fs';
2
+ import { dirname, join } from 'path';
3
+ import { fileURLToPath } from 'url';
1
4
  import { hasFormComponents, slugSchema } from '@defra/forms-model';
2
5
  import Boom from '@hapi/boom';
3
6
  import vision from '@hapi/vision';
4
7
  import { isEqual } from 'date-fns';
5
8
  import Joi from 'joi';
6
9
  import nunjucks from 'nunjucks';
10
+ import resolvePkg from 'resolve';
7
11
  import { PREVIEW_PATH_PREFIX } from "../../constants.js";
8
12
  import { checkEmailAddressForLiveFormSubmission, checkFormStatus, findPage, getCacheService, getPage, getStartPath, normalisePath, proceed, redirectPath } from "./helpers.js";
9
- import { PLUGIN_PATH, VIEW_PATH, context, prepareNunjucksEnvironment } from "./index.js";
13
+ import { VIEW_PATH, context, prepareNunjucksEnvironment } from "./index.js";
10
14
  import { FormModel, SummaryViewModel } from "./models/index.js";
11
15
  import { format } from "./outputFormatters/machine/v1.js";
12
16
  import { FileUploadPageController } from "./pageControllers/FileUploadPageController.js";
@@ -18,35 +22,42 @@ import { getUploadStatus } from "./services/uploadService.js";
18
22
  import { actionSchema, confirmSchema, crumbSchema, itemIdSchema, pathSchema, stateSchema } from "../../schemas/index.js";
19
23
  import * as httpService from "../../services/httpService.js";
20
24
  import { CacheService } from "../../services/index.js";
25
+ export function findPackageRoot() {
26
+ const currentFileName = fileURLToPath(import.meta.url);
27
+ const currentDirectoryName = dirname(currentFileName);
28
+ let dir = currentDirectoryName;
29
+ while (dir !== '/') {
30
+ if (existsSync(join(dir, 'package.json'))) {
31
+ return dir;
32
+ }
33
+ dir = dirname(dir);
34
+ }
35
+ throw new Error('package.json not found in parent directories');
36
+ }
21
37
  export const plugin = {
22
38
  name: '@defra/forms-engine-plugin',
23
39
  dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],
24
40
  multiple: true,
25
41
  async register(server, options) {
42
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong
43
+ const prefix = server.realm.modifiers.route.prefix ?? '';
26
44
  const {
27
45
  model,
28
46
  services = defaultServices,
29
47
  controllers,
30
48
  cacheName,
31
- viewPaths,
32
49
  filters,
33
- pluginPath = PLUGIN_PATH
50
+ nunjucks: nunjucksOptions,
51
+ viewContext
34
52
  } = options;
35
53
  const {
36
54
  formsService
37
55
  } = services;
38
56
  const cacheService = new CacheService(server, cacheName);
39
-
40
- // Paths array to tell `vision` and `nunjucks` where template files are stored.
41
- // We need to include `VIEW_PATH` in addition the runtime path (node_modules)
42
- // to keep the local tests working
43
- const path = [`${pluginPath}/${VIEW_PATH}`, VIEW_PATH];
44
-
45
- // Include any additional user provided view paths so our internal views engine
46
- // can find any files they provide from the consumer side if using custom `page.view`s
47
- if (Array.isArray(viewPaths) && viewPaths.length) {
48
- path.push(...viewPaths);
49
- }
57
+ const packageRoot = findPackageRoot();
58
+ const govukFrontendPath = dirname(resolvePkg.sync('govuk-frontend/package.json'));
59
+ const viewPathResolved = join(packageRoot, VIEW_PATH);
60
+ const paths = [...nunjucksOptions.paths, viewPathResolved, join(govukFrontendPath, 'dist')];
50
61
  await server.register({
51
62
  plugin: vision,
52
63
  options: {
@@ -61,7 +72,7 @@ export const plugin = {
61
72
  prepare: (options, next) => {
62
73
  // Nunjucks also needs an additional path configuration
63
74
  // to use the templates and macros from `govuk-frontend`
64
- const environment = nunjucks.configure([...path, 'node_modules/govuk-frontend/dist']);
75
+ const environment = nunjucks.configure(paths);
65
76
 
66
77
  // Applies custom filters and globals for nunjucks
67
78
  // that are required by the `forms-engine-plugin`
@@ -71,11 +82,13 @@ export const plugin = {
71
82
  }
72
83
  }
73
84
  },
74
- path,
85
+ path: paths,
75
86
  // Provides global context used with all templates
76
87
  context
77
88
  }
78
89
  });
90
+ server.expose('baseLayoutPath', nunjucksOptions.baseLayoutPath);
91
+ server.expose('viewContext', viewContext);
79
92
  server.expose('cacheService', cacheService);
80
93
  server.app.model = model;
81
94
 
@@ -89,8 +102,7 @@ export const plugin = {
89
102
  return h.continue;
90
103
  }
91
104
  const {
92
- params,
93
- path
105
+ params
94
106
  } = request;
95
107
  const {
96
108
  slug
@@ -98,7 +110,7 @@ export const plugin = {
98
110
  const {
99
111
  isPreview,
100
112
  state: formState
101
- } = checkFormStatus(path);
113
+ } = checkFormStatus(params);
102
114
 
103
115
  // Get the form metadata using the `slug` param
104
116
  const metadata = await formsService.getFormMetadata(slug);
@@ -135,7 +147,7 @@ export const plugin = {
135
147
  server.logger.info(`Building model for form definition ${id} (${slug}) ${formState}`);
136
148
 
137
149
  // Set up the basePath for the model
138
- const basePath = isPreview ? `${PREVIEW_PATH_PREFIX.substring(1)}/${formState}/${slug}` : slug;
150
+ const basePath = (isPreview ? `${prefix}${PREVIEW_PATH_PREFIX}/${formState}/${slug}` : `${prefix}/${slug}`).substring(1);
139
151
 
140
152
  // Construct the form model
141
153
  const model = new FormModel(definition, {