@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
@@ -1,10 +1,9 @@
1
- import { ControllerPath, Engine } from '@defra/forms-model';
1
+ import { ControllerPath, Engine, hasComponents, isFormType } from '@defra/forms-model';
2
2
  import Boom from '@hapi/boom';
3
3
  import { format, parseISO } from 'date-fns';
4
4
  import { StatusCodes } from 'http-status-codes';
5
5
  import { Liquid } from 'liquidjs';
6
6
  import { createLogger } from "../../common/helpers/logging/logger.js";
7
- import { PREVIEW_PATH_PREFIX } from "../../constants.js";
8
7
  import { getAnswer } from "./components/helpers.js";
9
8
  import { FormAction, FormStatus } from "../../routes/types.js";
10
9
  const logger = createLogger();
@@ -164,24 +163,15 @@ export function getStartPath(model) {
164
163
  const startPath = normalisePath(model?.def.startPage);
165
164
  return startPath ? `/${startPath}` : ControllerPath.Start;
166
165
  }
167
- export function checkFormStatus(path) {
168
- const isPreview = path.toLowerCase().startsWith(PREVIEW_PATH_PREFIX);
169
- let state;
170
- if (isPreview) {
171
- const previewState = path.split('/')[2];
172
- for (const formState of Object.values(FormStatus)) {
173
- if (previewState === formState.toString()) {
174
- state = formState;
175
- break;
176
- }
177
- }
178
- if (!state) {
179
- throw new Error(`Invalid form state: ${previewState}`);
180
- }
166
+ export function checkFormStatus(params) {
167
+ const isPreview = !!params?.state;
168
+ let state = FormStatus.Live;
169
+ if (isPreview && params.state === FormStatus.Draft) {
170
+ state = FormStatus.Draft;
181
171
  }
182
172
  return {
183
173
  isPreview,
184
- state: state ?? FormStatus.Live
174
+ state
185
175
  };
186
176
  }
187
177
  export function checkEmailAddressForLiveFormSubmission(emailAddress, isPreview) {
@@ -229,7 +219,7 @@ export function safeGenerateCrumb(request) {
229
219
  return undefined;
230
220
  }
231
221
 
232
- // crumb plugin or its generate method doesnt exist
222
+ // crumb plugin or its generate method doesn't exist
233
223
  if (!request.server.plugins.crumb.generate) {
234
224
  return undefined;
235
225
  }
@@ -268,4 +258,34 @@ export function evaluateTemplate(template, context) {
268
258
  export function getCacheService(server) {
269
259
  return server.plugins['forms-engine-plugin'].cacheService;
270
260
  }
261
+
262
+ /**
263
+ * Handles logging and issuing a permanent redirect for legacy routes.
264
+ * @param h - The Hapi response toolkit.
265
+ * @param targetUrl - The URL to redirect to.
266
+ * @returns The Hapi response object configured for permanent redirect.
267
+ */
268
+ export function handleLegacyRedirect(h, targetUrl) {
269
+ return h.redirect(targetUrl).permanent().takeover();
270
+ }
271
+
272
+ /**
273
+ * If the page doesn't have a title, set it from the title of the first form component
274
+ * @param def - the form definition
275
+ */
276
+ export function setPageTitles(def) {
277
+ def.pages.forEach(page => {
278
+ if (!page.title) {
279
+ if (hasComponents(page)) {
280
+ // Set the page title from the first form component
281
+ const firstFormComponent = page.components.find(component => isFormType(component.type));
282
+ page.title = firstFormComponent?.title ?? '';
283
+ }
284
+ if (!page.title) {
285
+ const formNameMsg = def.name ? ` in form '${def.name}'` : '';
286
+ logger.warn(`Page '${page.path}' has no title${formNameMsg}`);
287
+ }
288
+ }
289
+ });
290
+ }
271
291
  //# sourceMappingURL=helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.js","names":["ControllerPath","Engine","Boom","format","parseISO","StatusCodes","Liquid","createLogger","PREVIEW_PATH_PREFIX","getAnswer","FormAction","FormStatus","logger","engine","outputEscape","jsTruthy","ownPropertyOnly","registerFilter","template","globals","context","evaluated","evaluateTemplate","path","pageDef","pages","get","query","page","pageMap","undefined","getPageHref","name","componentDef","components","component","componentMap","isFormComponent","answer","relevantState","proceed","request","h","nextUrl","method","payload","returnUrl","isReturnAllowed","action","Continue","Validate","response","isPathRelative","redirect","redirectPath","code","SEE_OTHER","MOVED_TEMPORARILY","encodeUrl","link","URL","toString","err","error","pathOrQuery","queryOnly","Error","getHref","isRelative","params","Object","entries","filter","url","value","searchParams","set","pathname","search","href","startsWith","normalisePath","trim","replace","getPage","model","findPage","notFound","findPath","find","getStartPath","V2","startPath","def","at","Start","startPage","checkFormStatus","isPreview","toLowerCase","state","previewState","split","formState","values","Live","checkEmailAddressForLiveFormSubmission","emailAddress","internal","getErrors","details","length","map","getError","detail","message","key","text","safeGenerateCrumb","server","plugins","crumb","generate","route","settings","getExponentialBackoffDelay","depth","BASE_DELAY_MS","CAP_DELAY_MS","delay","Math","min","pageDefMap","componentDefMap","parseAndRenderSync","getCacheService","cacheService"],"sources":["../../../../src/server/plugins/engine/helpers.ts"],"sourcesContent":["import {\n ControllerPath,\n Engine,\n type ComponentDef,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type Server } from '@hapi/hapi'\nimport { format, parseISO } from 'date-fns'\nimport { StatusCodes } from 'http-status-codes'\nimport { type Schema, type ValidationErrorItem } from 'joi'\nimport { Liquid } from 'liquidjs'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n FormStatus,\n type FormQuery,\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nconst logger = createLogger()\n\nexport const engine = new Liquid({\n outputEscape: 'escape',\n jsTruthy: true,\n ownPropertyOnly: false\n})\n\nexport interface GlobalScope {\n context: FormContext\n pages: Map<string, Page>\n components: Map<string, ComponentDef>\n}\n\nengine.registerFilter('evaluate', function (template?: string) {\n if (typeof template !== 'string') {\n return template\n }\n\n const globals = this.context.globals as GlobalScope\n const evaluated = evaluateTemplate(template, globals.context)\n\n return evaluated\n})\n\nengine.registerFilter('page', function (path?: string) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const pageDef = globals.pages.get(path)\n\n return pageDef\n})\n\nengine.registerFilter('href', function (path: string, query?: FormQuery) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const page = globals.context.pageMap.get(path)\n\n if (page === undefined) {\n return\n }\n\n return getPageHref(page, query)\n})\n\nengine.registerFilter('field', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const componentDef = globals.components.get(name)\n\n return componentDef\n})\n\nengine.registerFilter('answer', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const component = globals.context.componentMap.get(name)\n\n if (!component?.isFormComponent) {\n return\n }\n\n const answer = getAnswer(component as Field, globals.context.relevantState)\n\n return answer\n})\n\nexport function proceed(\n request: Pick<FormContextRequest, 'method' | 'payload' | 'query'>,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n nextUrl: string\n) {\n const { method, payload, query } = request\n const { returnUrl } = query\n\n const isReturnAllowed =\n payload && 'action' in payload\n ? payload.action === FormAction.Continue ||\n payload.action === FormAction.Validate\n : false\n\n // Redirect to return location (optional)\n const response =\n isReturnAllowed && isPathRelative(returnUrl)\n ? h.redirect(returnUrl)\n : h.redirect(redirectPath(nextUrl))\n\n // Redirect POST to GET to avoid resubmission\n return method === 'post'\n ? response.code(StatusCodes.SEE_OTHER)\n : response.code(StatusCodes.MOVED_TEMPORARILY)\n}\n\n/**\n * Encodes a URL, returning undefined if the process fails.\n */\nexport function encodeUrl(link?: string) {\n if (link) {\n try {\n return new URL(link).toString() // escape the search params without breaking the ? and & reserved characters in rfc2368\n } catch (err) {\n logger.error(err, `Failed to encode ${link}`)\n throw err\n }\n }\n}\n\n/**\n * Get page href\n */\nexport function getPageHref(\n page: PageControllerClass,\n query?: FormQuery\n): string\n\n/**\n * Get page href by path\n */\nexport function getPageHref(\n page: PageControllerClass,\n path: string,\n query?: FormQuery\n): string\n\nexport function getPageHref(\n page: PageControllerClass,\n pathOrQuery?: string | FormQuery,\n queryOnly: FormQuery = {}\n) {\n const path = typeof pathOrQuery === 'string' ? pathOrQuery : page.path\n const query = typeof pathOrQuery === 'object' ? pathOrQuery : queryOnly\n\n if (!isPathRelative(path)) {\n throw Error(`Only relative URLs are allowed: ${path}`)\n }\n\n // Return path with page href as base\n return redirectPath(page.getHref(path), query)\n}\n\n/**\n * Get redirect path with optional query params\n */\nexport function redirectPath(nextUrl: string, query: FormQuery = {}) {\n const isRelative = isPathRelative(nextUrl)\n\n // Filter string query params only\n const params = Object.entries(query).filter(\n (query): query is [string, string] => typeof query[1] === 'string'\n )\n\n // Build URL with relative path support\n const url = isRelative\n ? new URL(nextUrl, 'http://example.com')\n : new URL(nextUrl)\n\n // Append query params\n for (const [name, value] of params) {\n url.searchParams.set(name, value)\n }\n\n if (isRelative) {\n return `${url.pathname}${url.search}`\n }\n\n return url.href\n}\n\nexport function isPathRelative(path?: string) {\n return (path ?? '').startsWith('/')\n}\n\nexport function normalisePath(path = '') {\n return path\n .trim() // Trim empty spaces\n .replace(/^\\//, '') // Remove leading slash\n .replace(/\\/$/, '') // Remove trailing slash\n}\n\nexport function getPage(\n model: FormModel | undefined,\n request: FormContextRequest\n) {\n const { params } = request\n\n const page = findPage(model, `/${params.path}`)\n\n if (!page) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page\n}\n\nexport function findPage(model: FormModel | undefined, path?: string) {\n const findPath = `/${normalisePath(path)}`\n return model?.pages.find(({ path }) => path === findPath)\n}\n\nexport function getStartPath(model?: FormModel) {\n if (model?.engine === Engine.V2) {\n const startPath = normalisePath(model.def.pages.at(0)?.path)\n return startPath ? `/${startPath}` : ControllerPath.Start\n }\n\n const startPath = normalisePath(model?.def.startPage)\n return startPath ? `/${startPath}` : ControllerPath.Start\n}\n\nexport function checkFormStatus(path: string) {\n const isPreview = path.toLowerCase().startsWith(PREVIEW_PATH_PREFIX)\n\n let state: FormStatus | undefined\n\n if (isPreview) {\n const previewState = path.split('/')[2]\n\n for (const formState of Object.values(FormStatus)) {\n if (previewState === formState.toString()) {\n state = formState\n break\n }\n }\n\n if (!state) {\n throw new Error(`Invalid form state: ${previewState}`)\n }\n }\n\n return {\n isPreview,\n state: state ?? FormStatus.Live\n }\n}\n\nexport function checkEmailAddressForLiveFormSubmission(\n emailAddress: string | undefined,\n isPreview: boolean\n) {\n if (!emailAddress && !isPreview) {\n throw Boom.internal(\n 'An email address is required to complete the form submission'\n )\n }\n}\n\n/**\n * Parses the errors from {@link Schema.validate} so they can be rendered by govuk-frontend templates\n * @param [details] - provided by {@link Schema.validate}\n */\nexport function getErrors(\n details?: ValidationErrorItem[]\n): FormSubmissionError[] | undefined {\n if (!details?.length) {\n return\n }\n\n return details.map(getError)\n}\n\nexport function getError(detail: ValidationErrorItem): FormSubmissionError {\n const { context, message, path } = detail\n\n const name = context?.key ?? ''\n const href = `#${name}`\n\n const text = message.replace(\n /\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z)/,\n (text) => format(parseISO(text), 'd MMMM yyyy')\n )\n\n return {\n path,\n href,\n name,\n text,\n context\n }\n}\n\n/**\n * A small helper to safely generate a crumb token.\n * Checks that the crumb plugin is available, that crumb\n * is not disabled on the current route, and that cookies/state are present.\n */\nexport function safeGenerateCrumb(\n request: FormRequest | FormRequestPayload | null\n): string | undefined {\n // no request or no .state\n if (!request?.state) {\n return undefined\n }\n\n // crumb plugin or its generate method doesn’t exist\n if (!request.server.plugins.crumb.generate) {\n return undefined\n }\n\n // crumb is explicitly disabled for this route\n if (request.route.settings.plugins?.crumb === false) {\n return undefined\n }\n\n return request.server.plugins.crumb.generate(request)\n}\n\n/**\n * Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,\n * using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).\n * @param depth - The current retry depth (1, 2, 3, …)\n * @returns The calculated delay in milliseconds.\n */\nexport function getExponentialBackoffDelay(depth: number): number {\n const BASE_DELAY_MS = 2000 // 2 seconds initial delay\n const CAP_DELAY_MS = 25000 // cap each delay to 25 seconds\n const delay = BASE_DELAY_MS * 2 ** (depth - 1)\n return Math.min(delay, CAP_DELAY_MS)\n}\n\nexport function evaluateTemplate(\n template: string,\n context: FormContext\n): string {\n const globals: GlobalScope = {\n context,\n pages: context.pageDefMap,\n components: context.componentDefMap\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return engine.parseAndRenderSync(template, context.relevantState, {\n globals\n })\n}\n\nexport function getCacheService(server: Server) {\n return server.plugins['forms-engine-plugin'].cacheService\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"helpers.js","names":["ControllerPath","Engine","hasComponents","isFormType","Boom","format","parseISO","StatusCodes","Liquid","createLogger","getAnswer","FormAction","FormStatus","logger","engine","outputEscape","jsTruthy","ownPropertyOnly","registerFilter","template","globals","context","evaluated","evaluateTemplate","path","pageDef","pages","get","query","page","pageMap","undefined","getPageHref","name","componentDef","components","component","componentMap","isFormComponent","answer","relevantState","proceed","request","h","nextUrl","method","payload","returnUrl","isReturnAllowed","action","Continue","Validate","response","isPathRelative","redirect","redirectPath","code","SEE_OTHER","MOVED_TEMPORARILY","encodeUrl","link","URL","toString","err","error","pathOrQuery","queryOnly","Error","getHref","isRelative","params","Object","entries","filter","url","value","searchParams","set","pathname","search","href","startsWith","normalisePath","trim","replace","getPage","model","findPage","notFound","findPath","find","getStartPath","V2","startPath","def","at","Start","startPage","checkFormStatus","isPreview","state","Live","Draft","checkEmailAddressForLiveFormSubmission","emailAddress","internal","getErrors","details","length","map","getError","detail","message","key","text","safeGenerateCrumb","server","plugins","crumb","generate","route","settings","getExponentialBackoffDelay","depth","BASE_DELAY_MS","CAP_DELAY_MS","delay","Math","min","pageDefMap","componentDefMap","parseAndRenderSync","getCacheService","cacheService","handleLegacyRedirect","targetUrl","permanent","takeover","setPageTitles","forEach","title","firstFormComponent","type","formNameMsg","warn"],"sources":["../../../../src/server/plugins/engine/helpers.ts"],"sourcesContent":["import {\n ControllerPath,\n Engine,\n hasComponents,\n isFormType,\n type ComponentDef,\n type FormDefinition,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type Server } from '@hapi/hapi'\nimport { format, parseISO } from 'date-fns'\nimport { StatusCodes } from 'http-status-codes'\nimport { type Schema, type ValidationErrorItem } from 'joi'\nimport { Liquid } from 'liquidjs'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n FormStatus,\n type FormParams,\n type FormQuery,\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nconst logger = createLogger()\n\nexport const engine = new Liquid({\n outputEscape: 'escape',\n jsTruthy: true,\n ownPropertyOnly: false\n})\n\nexport interface GlobalScope {\n context: FormContext\n pages: Map<string, Page>\n components: Map<string, ComponentDef>\n}\n\nengine.registerFilter('evaluate', function (template?: string) {\n if (typeof template !== 'string') {\n return template\n }\n\n const globals = this.context.globals as GlobalScope\n const evaluated = evaluateTemplate(template, globals.context)\n\n return evaluated\n})\n\nengine.registerFilter('page', function (path?: string) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const pageDef = globals.pages.get(path)\n\n return pageDef\n})\n\nengine.registerFilter('href', function (path: string, query?: FormQuery) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const page = globals.context.pageMap.get(path)\n\n if (page === undefined) {\n return\n }\n\n return getPageHref(page, query)\n})\n\nengine.registerFilter('field', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const componentDef = globals.components.get(name)\n\n return componentDef\n})\n\nengine.registerFilter('answer', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const component = globals.context.componentMap.get(name)\n\n if (!component?.isFormComponent) {\n return\n }\n\n const answer = getAnswer(component as Field, globals.context.relevantState)\n\n return answer\n})\n\nexport function proceed(\n request: Pick<FormContextRequest, 'method' | 'payload' | 'query'>,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n nextUrl: string\n) {\n const { method, payload, query } = request\n const { returnUrl } = query\n\n const isReturnAllowed =\n payload && 'action' in payload\n ? payload.action === FormAction.Continue ||\n payload.action === FormAction.Validate\n : false\n\n // Redirect to return location (optional)\n const response =\n isReturnAllowed && isPathRelative(returnUrl)\n ? h.redirect(returnUrl)\n : h.redirect(redirectPath(nextUrl))\n\n // Redirect POST to GET to avoid resubmission\n return method === 'post'\n ? response.code(StatusCodes.SEE_OTHER)\n : response.code(StatusCodes.MOVED_TEMPORARILY)\n}\n\n/**\n * Encodes a URL, returning undefined if the process fails.\n */\nexport function encodeUrl(link?: string) {\n if (link) {\n try {\n return new URL(link).toString() // escape the search params without breaking the ? and & reserved characters in rfc2368\n } catch (err) {\n logger.error(err, `Failed to encode ${link}`)\n throw err\n }\n }\n}\n\n/**\n * Get page href\n */\nexport function getPageHref(\n page: PageControllerClass,\n query?: FormQuery\n): string\n\n/**\n * Get page href by path\n */\nexport function getPageHref(\n page: PageControllerClass,\n path: string,\n query?: FormQuery\n): string\n\nexport function getPageHref(\n page: PageControllerClass,\n pathOrQuery?: string | FormQuery,\n queryOnly: FormQuery = {}\n) {\n const path = typeof pathOrQuery === 'string' ? pathOrQuery : page.path\n const query = typeof pathOrQuery === 'object' ? pathOrQuery : queryOnly\n\n if (!isPathRelative(path)) {\n throw Error(`Only relative URLs are allowed: ${path}`)\n }\n\n // Return path with page href as base\n return redirectPath(page.getHref(path), query)\n}\n\n/**\n * Get redirect path with optional query params\n */\nexport function redirectPath(nextUrl: string, query: FormQuery = {}) {\n const isRelative = isPathRelative(nextUrl)\n\n // Filter string query params only\n const params = Object.entries(query).filter(\n (query): query is [string, string] => typeof query[1] === 'string'\n )\n\n // Build URL with relative path support\n const url = isRelative\n ? new URL(nextUrl, 'http://example.com')\n : new URL(nextUrl)\n\n // Append query params\n for (const [name, value] of params) {\n url.searchParams.set(name, value)\n }\n\n if (isRelative) {\n return `${url.pathname}${url.search}`\n }\n\n return url.href\n}\n\nexport function isPathRelative(path?: string) {\n return (path ?? '').startsWith('/')\n}\n\nexport function normalisePath(path = '') {\n return path\n .trim() // Trim empty spaces\n .replace(/^\\//, '') // Remove leading slash\n .replace(/\\/$/, '') // Remove trailing slash\n}\n\nexport function getPage(\n model: FormModel | undefined,\n request: FormContextRequest\n) {\n const { params } = request\n\n const page = findPage(model, `/${params.path}`)\n\n if (!page) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page\n}\n\nexport function findPage(model: FormModel | undefined, path?: string) {\n const findPath = `/${normalisePath(path)}`\n return model?.pages.find(({ path }) => path === findPath)\n}\n\nexport function getStartPath(model?: FormModel) {\n if (model?.engine === Engine.V2) {\n const startPath = normalisePath(model.def.pages.at(0)?.path)\n return startPath ? `/${startPath}` : ControllerPath.Start\n }\n\n const startPath = normalisePath(model?.def.startPage)\n return startPath ? `/${startPath}` : ControllerPath.Start\n}\n\nexport function checkFormStatus(params?: FormParams) {\n const isPreview = !!params?.state\n\n let state = FormStatus.Live\n\n if (isPreview && params.state === FormStatus.Draft) {\n state = FormStatus.Draft\n }\n\n return {\n isPreview,\n state\n }\n}\n\nexport function checkEmailAddressForLiveFormSubmission(\n emailAddress: string | undefined,\n isPreview: boolean\n) {\n if (!emailAddress && !isPreview) {\n throw Boom.internal(\n 'An email address is required to complete the form submission'\n )\n }\n}\n\n/**\n * Parses the errors from {@link Schema.validate} so they can be rendered by govuk-frontend templates\n * @param [details] - provided by {@link Schema.validate}\n */\nexport function getErrors(\n details?: ValidationErrorItem[]\n): FormSubmissionError[] | undefined {\n if (!details?.length) {\n return\n }\n\n return details.map(getError)\n}\n\nexport function getError(detail: ValidationErrorItem): FormSubmissionError {\n const { context, message, path } = detail\n\n const name = context?.key ?? ''\n const href = `#${name}`\n\n const text = message.replace(\n /\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z)/,\n (text) => format(parseISO(text), 'd MMMM yyyy')\n )\n\n return {\n path,\n href,\n name,\n text,\n context\n }\n}\n\n/**\n * A small helper to safely generate a crumb token.\n * Checks that the crumb plugin is available, that crumb\n * is not disabled on the current route, and that cookies/state are present.\n */\nexport function safeGenerateCrumb(\n request: FormRequest | FormRequestPayload | null\n): string | undefined {\n // no request or no .state\n if (!request?.state) {\n return undefined\n }\n\n // crumb plugin or its generate method doesn't exist\n if (!request.server.plugins.crumb.generate) {\n return undefined\n }\n\n // crumb is explicitly disabled for this route\n if (request.route.settings.plugins?.crumb === false) {\n return undefined\n }\n\n return request.server.plugins.crumb.generate(request)\n}\n\n/**\n * Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,\n * using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).\n * @param depth - The current retry depth (1, 2, 3, …)\n * @returns The calculated delay in milliseconds.\n */\nexport function getExponentialBackoffDelay(depth: number): number {\n const BASE_DELAY_MS = 2000 // 2 seconds initial delay\n const CAP_DELAY_MS = 25000 // cap each delay to 25 seconds\n const delay = BASE_DELAY_MS * 2 ** (depth - 1)\n return Math.min(delay, CAP_DELAY_MS)\n}\n\nexport function evaluateTemplate(\n template: string,\n context: FormContext\n): string {\n const globals: GlobalScope = {\n context,\n pages: context.pageDefMap,\n components: context.componentDefMap\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return engine.parseAndRenderSync(template, context.relevantState, {\n globals\n })\n}\n\nexport function getCacheService(server: Server) {\n return server.plugins['forms-engine-plugin'].cacheService\n}\n\n/**\n * Handles logging and issuing a permanent redirect for legacy routes.\n * @param h - The Hapi response toolkit.\n * @param targetUrl - The URL to redirect to.\n * @returns The Hapi response object configured for permanent redirect.\n */\nexport function handleLegacyRedirect(h: ResponseToolkit, targetUrl: string) {\n return h.redirect(targetUrl).permanent().takeover()\n}\n\n/**\n * If the page doesn't have a title, set it from the title of the first form component\n * @param def - the form definition\n */\nexport function setPageTitles(def: FormDefinition) {\n def.pages.forEach((page) => {\n if (!page.title) {\n if (hasComponents(page)) {\n // Set the page title from the first form component\n const firstFormComponent = page.components.find((component) =>\n isFormType(component.type)\n )\n\n page.title = firstFormComponent?.title ?? ''\n }\n\n if (!page.title) {\n const formNameMsg = def.name ? ` in form '${def.name}'` : ''\n\n logger.warn(`Page '${page.path}' has no title${formNameMsg}`)\n }\n }\n })\n}\n"],"mappings":"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","ignoreList":[]}
@@ -2,7 +2,8 @@ import { ComponentType, ConditionsModel, ControllerPath, ControllerType, Engine,
2
2
  import { add } from 'date-fns';
3
3
  import { Parser } from 'expr-eval';
4
4
  import joi from 'joi';
5
- import { findPage, getError, getPage } from "../helpers.js";
5
+ import { hasListFormField } from "../components/helpers.js";
6
+ import { findPage, getError, getPage, setPageTitles } from "../helpers.js";
6
7
  import { createPage } from "../pageControllers/helpers.js";
7
8
  import { validationOptions as opts } from "../pageControllers/validationOptions.js";
8
9
  import * as defaultServices from "../services/index.js";
@@ -53,6 +54,9 @@ export class FormModel {
53
54
  value: false
54
55
  }]
55
56
  });
57
+
58
+ // Fix up page titles
59
+ setPageTitles(def);
56
60
  this.engine = def.engine;
57
61
  this.def = def;
58
62
  this.lists = def.lists;
@@ -215,7 +219,7 @@ export class FormModel {
215
219
  this.assignRelevantState(context, nextPage);
216
220
 
217
221
  // Stop at current page
218
- if (nextPage.path === currentPath) {
222
+ if (this.pageStateIsInvalid(context, nextPage) || nextPage.path === currentPath) {
219
223
  break;
220
224
  }
221
225
 
@@ -261,6 +265,60 @@ export class FormModel {
261
265
  }
262
266
  }
263
267
  }
268
+ pageStateIsInvalid(context, page) {
269
+ // Get any list-bound fields on the page
270
+ const listFields = page.collection.fields.filter(hasListFormField);
271
+
272
+ // For each list field that is bound to a list that contains any conditional items,
273
+ // we need to check any answers are still valid. Do this by evaluating the conditions
274
+ // and ensuring any current answers are all included in the set of valid answers
275
+ for (const field of listFields) {
276
+ const list = field.list;
277
+
278
+ // Filter out YesNo as they can't be conditional
279
+ if (list !== undefined && field.type !== ComponentType.YesNoField) {
280
+ const hasOptionalItems = list.items.filter(item => item.condition).length > 0;
281
+ if (hasOptionalItems) {
282
+ return this.fieldStateIsInvalid(context, field, list);
283
+ }
284
+ }
285
+ }
286
+ }
287
+ fieldStateIsInvalid(context, field, list) {
288
+ const {
289
+ evaluationState,
290
+ state
291
+ } = context;
292
+ const validValues = list.items.filter(item => item.condition ? this.conditions[item.condition]?.fn(evaluationState) : true).map(item => item.value);
293
+
294
+ // Get the field state
295
+ const fieldState = field.getFormValueFromState(state);
296
+ if (fieldState !== undefined) {
297
+ let isInvalid = false;
298
+ const isArray = Array.isArray(fieldState);
299
+
300
+ // Check if any saved state value(s) are still valid
301
+ // and return true if any are invalid
302
+ if (isArray) {
303
+ isInvalid = !fieldState.every(item => validValues.includes(item));
304
+ } else {
305
+ isInvalid = !validValues.includes(fieldState);
306
+ }
307
+ if (isInvalid) {
308
+ if (!context.errors) {
309
+ context.errors = [];
310
+ }
311
+ const text = 'Options are different because you changed a previous answer';
312
+ context.errors.push({
313
+ text,
314
+ name: field.name,
315
+ href: `#${field.name}`,
316
+ path: [`#${field.name}`]
317
+ });
318
+ }
319
+ return isInvalid;
320
+ }
321
+ }
264
322
  assignPaths(context) {
265
323
  for (const {
266
324
  keys,
@@ -1 +1 @@
1
- {"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","Engine","formDefinitionSchema","hasComponents","hasRepeater","add","Parser","joi","findPage","getError","getPage","createPage","validationOptions","opts","defaultServices","FormAction","merge","FormModel","engine","def","lists","sections","name","values","basePath","conditions","pages","services","controllers","pageDefMap","listDefMap","componentDefMap","pageMap","componentMap","constructor","options","result","validate","abortEarly","error","structuredClone","value","push","title","type","items","text","forEach","conditionDef","condition","makeCondition","map","pageDef","some","controller","Status","path","Map","page","list","filter","flatMap","components","component","collection","makeSchema","makeFilteredSchema","relevantPages","schema","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","Date","toISOString","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","key","defineProperty","get","from","parse","toExpression","getList","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","getNextPath","validateFormState","assignPaths","V2","keys","getContextValueFromState","includes","action","getFormParams","Validate","update","fields","field","CheckboxesField","undefined","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n Engine,\n formDefinitionSchema,\n hasComponents,\n hasRepeater,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionsModelData,\n type DateUnits,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n findPage,\n getError,\n getPage\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: { basePath: string },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n const result = formDefinitionSchema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n text: 'Yes',\n value: true\n },\n {\n text: 'No',\n value: false\n }\n ]\n })\n\n this.engine = def.engine\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(conditionDef)\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections\n */\n makeSchema() {\n return this.makeFilteredSchema(this.pages)\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n return add(new Date(), { [timeUnit]: timePeriod }).toISOString()\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const key in conditions) {\n Object.defineProperty(context, key, {\n get() {\n return conditions[key]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(name: string): List | undefined {\n return this.lists.find((list) => list.name === name)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state)\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (nextPage.path === currentPath) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // For the V2 engine, we need to initialise `evaluationState` to null\n // for all keys. This is because the current condition evaluation\n // library (eval-expr) will throw if an expression uses a key that is undefined.\n if (this.engine === Engine.V2) {\n for (const page of this.pages) {\n for (const key of page.keys) {\n context.evaluationState[key] = null\n }\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (!request.payload || action !== FormAction.Validate) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"AAAA,SACEA,aAAa,EACbC,eAAe,EACfC,cAAc,EACdC,cAAc,EACdC,MAAM,EACNC,oBAAoB,EACpBC,aAAa,EACbC,WAAW,QAQN,oBAAoB;AAC3B,SAASC,GAAG,QAAQ,UAAU;AAC9B,SAASC,MAAM,QAAoB,WAAW;AAC9C,OAAOC,GAAG,MAAM,KAAK;AAGrB,SACEC,QAAQ,EACRC,QAAQ,EACRC,OAAO;AAIT,SACEC,UAAU;AAGZ,SAASC,iBAAiB,IAAIC,IAAI;AAClC,OAAO,KAAKC,eAAe;AAQ3B,SAASC,UAAU;AACnB,SAASC,KAAK;AAGd,OAAO,MAAMC,SAAS,CAAC;EACrB;EACAC,MAAM;;EAEN;EACAC,GAAG;EAEHC,KAAK;EACLC,QAAQ,GAA+B,EAAE;EACzCC,IAAI;EACJC,MAAM;EACNC,QAAQ;EACRC,UAAU;EACVC,KAAK;EACLC,QAAQ;EAERC,WAAW;EACXC,UAAU;EACVC,UAAU;EACVC,eAAe;EACfC,OAAO;EACPC,YAAY;EAEZC,WAAWA,CACTf,GAAoB,EACpBgB,OAA6B,EAC7BR,QAAkB,GAAGb,eAAe,EACpCc,WAAmD,EACnD;IACA,MAAMQ,MAAM,GAAGlC,oBAAoB,CAACmC,QAAQ,CAAClB,GAAG,EAAE;MAAEmB,UAAU,EAAE;IAAM,CAAC,CAAC;IAExE,IAAIF,MAAM,CAACG,KAAK,EAAE;MAChB,MAAMH,MAAM,CAACG,KAAK;IACpB;;IAEA;IACA;IACApB,GAAG,GAAGqB,eAAe,CAACJ,MAAM,CAACK,KAAK,CAAC;;IAEnC;IACAtB,GAAG,CAACC,KAAK,CAACsB,IAAI,CAAC;MACbpB,IAAI,EAAE,SAAS;MACfqB,KAAK,EAAE,QAAQ;MACfC,IAAI,EAAE,SAAS;MACfC,KAAK,EAAE,CACL;QACEC,IAAI,EAAE,KAAK;QACXL,KAAK,EAAE;MACT,CAAC,EACD;QACEK,IAAI,EAAE,IAAI;QACVL,KAAK,EAAE;MACT,CAAC;IAEL,CAAC,CAAC;IAEF,IAAI,CAACvB,MAAM,GAAGC,GAAG,CAACD,MAAM;IACxB,IAAI,CAACC,GAAG,GAAGA,GAAG;IACd,IAAI,CAACC,KAAK,GAAGD,GAAG,CAACC,KAAK;IACtB,IAAI,CAACC,QAAQ,GAAGF,GAAG,CAACE,QAAQ;IAC5B,IAAI,CAACC,IAAI,GAAGH,GAAG,CAACG,IAAI,IAAI,EAAE;IAC1B,IAAI,CAACC,MAAM,GAAGa,MAAM,CAACK,KAAK;IAC1B,IAAI,CAACjB,QAAQ,GAAGW,OAAO,CAACX,QAAQ;IAChC,IAAI,CAACC,UAAU,GAAG,CAAC,CAAC;IACpB,IAAI,CAACE,QAAQ,GAAGA,QAAQ;IACxB,IAAI,CAACC,WAAW,GAAGA,WAAW;IAE9BT,GAAG,CAACM,UAAU,CAACsB,OAAO,CAAEC,YAAY,IAAK;MACvC,MAAMC,SAAS,GAAG,IAAI,CAACC,aAAa,CAACF,YAAY,CAAC;MAClD,IAAI,CAACvB,UAAU,CAACwB,SAAS,CAAC3B,IAAI,CAAC,GAAG2B,SAAS;IAC7C,CAAC,CAAC;IAEF,IAAI,CAACvB,KAAK,GAAGP,GAAG,CAACO,KAAK,CAACyB,GAAG,CAAEC,OAAO,IAAKzC,UAAU,CAAC,IAAI,EAAEyC,OAAO,CAAC,CAAC;IAElE,IACE,CAACjC,GAAG,CAACO,KAAK,CAAC2B,IAAI,CACb,CAAC;MAAEC;IAAW,CAAC;IACb;IACAA,UAAU,KAAKtD,cAAc,CAACuD,MAClC,CAAC,EACD;MACA,IAAI,CAAC7B,KAAK,CAACgB,IAAI,CACb/B,UAAU,CAAC,IAAI,EAAE;QACfgC,KAAK,EAAE,gBAAgB;QACvBa,IAAI,EAAEzD,cAAc,CAACwD,MAAM;QAC3BD,UAAU,EAAEtD,cAAc,CAACuD;MAC7B,CAAC,CACH,CAAC;IACH;IAEA,IAAI,CAAC1B,UAAU,GAAG,IAAI4B,GAAG,CAACtC,GAAG,CAACO,KAAK,CAACyB,GAAG,CAAEO,IAAI,IAAK,CAACA,IAAI,CAACF,IAAI,EAAEE,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC5B,UAAU,GAAG,IAAI2B,GAAG,CAACtC,GAAG,CAACC,KAAK,CAAC+B,GAAG,CAAEQ,IAAI,IAAK,CAACA,IAAI,CAACrC,IAAI,EAAEqC,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC5B,eAAe,GAAG,IAAI0B,GAAG,CAC5BtC,GAAG,CAACO,KAAK,CACNkC,MAAM,CAACzD,aAAa,CAAC,CACrB0D,OAAO,CAAEH,IAAI,IACZA,IAAI,CAACI,UAAU,CAACX,GAAG,CAAEY,SAAS,IAAK,CAACA,SAAS,CAACzC,IAAI,EAAEyC,SAAS,CAAC,CAChE,CACJ,CAAC;IAED,IAAI,CAAC/B,OAAO,GAAG,IAAIyB,GAAG,CAAC,IAAI,CAAC/B,KAAK,CAACyB,GAAG,CAAEO,IAAI,IAAK,CAACA,IAAI,CAACF,IAAI,EAAEE,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI,CAACzB,YAAY,GAAG,IAAIwB,GAAG,CACzB,IAAI,CAAC/B,KAAK,CAACmC,OAAO,CAAEH,IAAI,IACtBA,IAAI,CAACM,UAAU,CAACF,UAAU,CAACX,GAAG,CAAEY,SAAS,IAAK,CAC5CA,SAAS,CAACzC,IAAI,EACdyC,SAAS,CACV,CACH,CACF,CAAC;EACH;;EAEA;AACF;AACA;EACEE,UAAUA,CAAA,EAAG;IACX,OAAO,IAAI,CAACC,kBAAkB,CAAC,IAAI,CAACxC,KAAK,CAAC;EAC5C;;EAEA;AACF;AACA;AACA;EACEwC,kBAAkBA,CAACC,aAAoC,EAAE;IACvD;IACA;IACA,IAAIC,MAAM,GAAG7D,GAAG,CAAC8D,MAAM,CAAsB,CAAC,CAACC,QAAQ,CAAC,CAAC;IAEzDH,aAAa,CAACpB,OAAO,CAAEW,IAAI,IAAK;MAC9BU,MAAM,GAAGA,MAAM,CAACG,MAAM,CAACb,IAAI,CAACM,UAAU,CAACQ,WAAW,CAAC;IACrD,CAAC,CAAC;IAEF,OAAOJ,MAAM;EACf;;EAEA;AACF;AACA;AACA;EACElB,aAAaA,CAACD,SAA2B,EAAuB;IAC9D,MAAMwB,MAAM,GAAG,IAAInE,MAAM,CAAC;MACxBoE,SAAS,EAAE;QACTC,OAAO,EAAE;MACX;IACF,CAAC,CAAC;IAEFC,MAAM,CAACC,MAAM,CAACJ,MAAM,CAACK,SAAS,EAAE;MAC9BC,iBAAiBA,CAACC,UAAkB,EAAEC,QAAmB,EAAE;QACzD,OAAO5E,GAAG,CAAC,IAAI6E,IAAI,CAAC,CAAC,EAAE;UAAE,CAACD,QAAQ,GAAGD;QAAW,CAAC,CAAC,CAACG,WAAW,CAAC,CAAC;MAClE;IACF,CAAC,CAAC;IAEF,MAAM;MAAE7D,IAAI;MAAE8D,WAAW;MAAE3C;IAAM,CAAC,GAAGQ,SAAS;IAC9C,MAAMoC,IAAI,GAAG,IAAI,CAACC,qBAAqB,CAAC7C,KAAK,EAAEgC,MAAM,CAAC;IAEtD,MAAMc,EAAE,GAAIC,eAA0B,IAAK;MACzC,MAAMC,GAAG,GAAG,IAAI,CAACC,kBAAkB,CAACF,eAAe,EAAE,IAAI,CAAC/D,UAAU,CAAC;MACrE,IAAI;QACF,OAAO4D,IAAI,CAACM,QAAQ,CAACF,GAAG,CAAC;MAC3B,CAAC,CAAC,MAAM;QACN,OAAO,KAAK;MACd;IACF,CAAC;IAED,OAAO;MACLnE,IAAI;MACJ8D,WAAW;MACX3C,KAAK;MACL4C,IAAI;MACJE;IACF,CAAC;EACH;EAEAG,kBAAkBA,CAChBF,eAA0B,EAC1B/D,UAAwD,EACxD;IACA,MAAMmE,OAAO,GAAG;MAAE,GAAGJ;IAAgB,CAAC;IAEtC,KAAK,MAAMK,GAAG,IAAIpE,UAAU,EAAE;MAC5BmD,MAAM,CAACkB,cAAc,CAACF,OAAO,EAAEC,GAAG,EAAE;QAClCE,GAAGA,CAAA,EAAG;UACJ,OAAOtE,UAAU,CAACoE,GAAG,CAAC,EAAEN,EAAE,CAACC,eAAe,CAAC;QAC7C;MACF,CAAC,CAAC;IACJ;IAEA,OAAOI,OAAO;EAChB;EAEAN,qBAAqBA,CAAC7C,KAA0B,EAAEgC,MAAc,EAAE;IAChE,MAAMhD,UAAU,GAAG3B,eAAe,CAACkG,IAAI,CAACvD,KAAK,CAAC;IAC9C,OAAOgC,MAAM,CAACwB,KAAK,CAACxE,UAAU,CAACyE,YAAY,CAAC,CAAC,CAAC;EAChD;EAEAC,OAAOA,CAAC7E,IAAY,EAAoB;IACtC,OAAO,IAAI,CAACF,KAAK,CAACgF,IAAI,CAAEzC,IAAI,IAAKA,IAAI,CAACrC,IAAI,KAAKA,IAAI,CAAC;EACtD;;EAEA;AACF;AACA;EACE+E,cAAcA,CACZC,OAA2B,EAC3BC,KAAgB,EAChBC,MAA8B,EACjB;IACb,MAAM;MAAEC;IAAM,CAAC,GAAGH,OAAO;IAEzB,MAAM5C,IAAI,GAAGhD,OAAO,CAAC,IAAI,EAAE4F,OAAO,CAAC;;IAEnC;IACA,MAAMI,WAAW,GAAGhD,IAAI,CAACF,IAAI;IAC7B,MAAMmD,SAAS,GAAGjD,IAAI,CAACkD,YAAY,CAAC,CAAC;;IAErC;IACA,MAAMC,aAAa,GAAG,OAAO,IAAIJ,KAAK;IAEtC,IAAIb,OAAoB,GAAG;MACzBJ,eAAe,EAAE,CAAC,CAAC;MACnBsB,aAAa,EAAE,CAAC,CAAC;MACjB3C,aAAa,EAAE,EAAE;MACjB4C,OAAO,EAAErD,IAAI,CAACsD,oBAAoB,CAACV,OAAO,EAAEC,KAAK,CAAC;MAClDA,KAAK;MACLU,KAAK,EAAE,EAAE;MACTT,MAAM;MACNK,aAAa;MACbK,IAAI,EAAE,CAAC,CAAC;MACRrF,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BC,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BC,eAAe,EAAE,IAAI,CAACA,eAAe;MACrCC,OAAO,EAAE,IAAI,CAACA,OAAO;MACrBC,YAAY,EAAE,IAAI,CAACA,YAAY;MAC/BkF,eAAe,EAAEC,kBAAkB,CAACb,KAAK;IAC3C,CAAC;;IAED;IACAX,OAAO,GAAGyB,mBAAmB,CAACf,OAAO,EAAE5C,IAAI,EAAEkC,OAAO,CAAC;;IAErD;IACA,IAAI0B,QAAQ,GAAG9G,QAAQ,CAAC,IAAI,EAAEmG,SAAS,CAAC;IAExC,IAAI,CAACY,iBAAiB,CAAC3B,OAAO,CAAC;;IAE/B;IACA,OAAO0B,QAAQ,EAAE;MACf;MACA1B,OAAO,CAACzB,aAAa,CAACzB,IAAI,CAAC4E,QAAQ,CAAC;MAEpC,IAAI,CAACE,qBAAqB,CAAC5B,OAAO,EAAE0B,QAAQ,CAAC;MAE7C,IAAI,CAACG,mBAAmB,CAAC7B,OAAO,EAAE0B,QAAQ,CAAC;;MAE3C;MACA,IAAIA,QAAQ,CAAC9D,IAAI,KAAKkD,WAAW,EAAE;QACjC;MACF;;MAEA;MACAY,QAAQ,GAAG9G,QAAQ,CAAC,IAAI,EAAE8G,QAAQ,CAACI,WAAW,CAAC9B,OAAO,CAAC,CAAC;IAC1D;;IAEA;IACAA,OAAO,GAAG+B,iBAAiB,CAACrB,OAAO,EAAE5C,IAAI,EAAEkC,OAAO,CAAC;;IAEnD;IACA,IAAI,CAACgC,WAAW,CAAChC,OAAO,CAAC;IAEzB,OAAOA,OAAO;EAChB;EAEQ2B,iBAAiBA,CAAC3B,OAAoB,EAAE;IAC9C;IACA;IACA;IACA,IAAI,IAAI,CAAC1E,MAAM,KAAKjB,MAAM,CAAC4H,EAAE,EAAE;MAC7B,KAAK,MAAMnE,IAAI,IAAI,IAAI,CAAChC,KAAK,EAAE;QAC7B,KAAK,MAAMmE,GAAG,IAAInC,IAAI,CAACoE,IAAI,EAAE;UAC3BlC,OAAO,CAACJ,eAAe,CAACK,GAAG,CAAC,GAAG,IAAI;QACrC;MACF;IACF;EACF;EAEQ2B,qBAAqBA,CAC3B5B,OAAoB,EACpBlC,IAAyB,EACzB;IACA,MAAM;MAAEM,UAAU;MAAEZ;IAAQ,CAAC,GAAGM,IAAI;IACpC;;IAEA,IAAI,CAACtD,WAAW,CAACgD,OAAO,CAAC,EAAE;MACzBwB,MAAM,CAACC,MAAM,CACXe,OAAO,CAACJ,eAAe,EACvBxB,UAAU,CAAC+D,wBAAwB,CAACnC,OAAO,CAACW,KAAK,CACnD,CAAC;IACH;EACF;EAEQkB,mBAAmBA,CAAC7B,OAAoB,EAAElC,IAAyB,EAAE;IAC3E;IACA,KAAK,MAAMmC,GAAG,IAAInC,IAAI,CAACoE,IAAI,EAAE;MAC3B,IAAI,OAAOlC,OAAO,CAACW,KAAK,CAACV,GAAG,CAAC,KAAK,WAAW,EAAE;QAC7CD,OAAO,CAACkB,aAAa,CAACjB,GAAG,CAAC,GAAGD,OAAO,CAACW,KAAK,CAACV,GAAG,CAAC;MACjD;IACF;EACF;EAEQ+B,WAAWA,CAAChC,OAAoB,EAAE;IACxC,KAAK,MAAM;MAAEkC,IAAI;MAAEtE;IAAK,CAAC,IAAIoC,OAAO,CAACzB,aAAa,EAAE;MAClDyB,OAAO,CAACqB,KAAK,CAACvE,IAAI,CAACc,IAAI,CAAC;;MAExB;MACA,IACEoC,OAAO,CAACY,MAAM,EAAEnD,IAAI,CAAC,CAAC;QAAE/B,IAAI;QAAEkC;MAAK,CAAC,KAAK;QACvC,OAAOsE,IAAI,CAACE,QAAQ,CAAC1G,IAAI,CAAC,IAAIwG,IAAI,CAACzE,IAAI,CAAEwC,GAAG,IAAKrC,IAAI,CAACwE,QAAQ,CAACnC,GAAG,CAAC,CAAC;MACtE,CAAC,CAAC,EACF;QACA;MACF;IACF;EACF;AACF;;AAEA;AACA;AACA;AACA,SAASwB,mBAAmBA,CAC1Bf,OAA2B,EAC3B5C,IAAyB,EACzBkC,OAAoB,EACP;EACb,MAAM;IAAE5B;EAAW,CAAC,GAAGN,IAAI;EAC3B,MAAM;IAAEqD,OAAO;IAAER;EAAM,CAAC,GAAGX,OAAO;EAElC,MAAM;IAAEqC;EAAO,CAAC,GAAGvE,IAAI,CAACwE,aAAa,CAAC5B,OAAO,CAAC;;EAE9C;EACA,IAAI,CAACA,OAAO,CAACS,OAAO,IAAIkB,MAAM,KAAKlH,UAAU,CAACoH,QAAQ,EAAE;IACtD,OAAOvC,OAAO;EAChB;;EAEA;EACA;EACA;EACA,MAAMwC,MAAM,GAAG;IAAE,GAAG9B,OAAO,CAACS;EAAQ,CAAC;EACrC/C,UAAU,CAACqE,MAAM,CAACtF,OAAO,CAAEuF,KAAK,IAAK;IACnC,IACEA,KAAK,CAAC1F,IAAI,KAAK/C,aAAa,CAAC0I,eAAe,IAC5C,EAAED,KAAK,CAAChH,IAAI,IAAI8G,MAAM,CAAC,EACvB;MACAA,MAAM,CAACE,KAAK,CAAChH,IAAI,CAAC,GAAGkH,SAAS;IAChC;EACF,CAAC,CAAC;EAEF,MAAM;IAAE/F,KAAK;IAAE+D;EAAO,CAAC,GAAGxC,UAAU,CAAC3B,QAAQ,CAAC;IAC5C,GAAG0E,OAAO;IACV,GAAGqB;EACL,CAAC,CAAC;;EAEF;EACA,MAAMK,SAAS,GAAG/E,IAAI,CAACgF,qBAAqB,CAACpC,OAAO,EAAEC,KAAK,EAAE9D,KAAK,CAAC;EAEnE,OAAO;IACL,GAAGmD,OAAO;IACVmB,OAAO,EAAE/F,KAAK,CAAC+F,OAAO,EAAEtE,KAAK,CAAC;IAC9B8D,KAAK,EAAEvF,KAAK,CAACuF,KAAK,EAAEkC,SAAS,CAAC;IAC9BjC;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA,SAASmB,iBAAiBA,CACxBrB,OAA2B,EAC3B5C,IAAyB,EACzBkC,OAAoB,EACP;EACb,MAAM;IAAEY,MAAM,GAAG,EAAE;IAAErC,aAAa;IAAE2C;EAAc,CAAC,GAAGlB,OAAO;;EAE7D;EACA,MAAM+C,aAAa,GAAGxE,aAAa,CAACP,MAAM,CACvCgF,YAAY,IAAKA,YAAY,KAAKlF,IACrC,CAAC;;EAED;EACA,MAAM;IAAEnB;EAAM,CAAC,GAAGmB,IAAI,CAACmF,KAAK,CACzB3E,kBAAkB,CAACyE,aAAa,CAAC,CACjCtG,QAAQ,CAACyE,aAAa,EAAE;IAAE,GAAGjG,IAAI;IAAEiI,YAAY,EAAE;EAAK,CAAC,CAAC;;EAE3D;EACA,IAAIvG,KAAK,EAAE;IACT,MAAMwG,WAAW,GAAGxG,KAAK,CAACyG,OAAO,CAAC7F,GAAG,CAAC1C,QAAQ,CAAC;IAC/C,OAAO;MAAE,GAAGmF,OAAO;MAAEY,MAAM,EAAEA,MAAM,CAACjC,MAAM,CAACwE,WAAW;IAAE,CAAC;EAC3D;EAEA,OAAOnD,OAAO;AAChB;AAEA,SAASwB,kBAAkBA,CAACb,KAAgB,EAAU;EACpD,IACE,CAACA,KAAK,CAAC0C,mBAAmB,IAC1B,OAAO1C,KAAK,CAAC0C,mBAAmB,KAAK,QAAQ,EAC7C;IACA,MAAMC,KAAK,CAAC,0CAA0C,CAAC;EACzD;EAEA,OAAO3C,KAAK,CAAC0C,mBAAmB;AAClC","ignoreList":[]}
1
+ {"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","Engine","formDefinitionSchema","hasComponents","hasRepeater","add","Parser","joi","hasListFormField","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","FormModel","engine","def","lists","sections","name","values","basePath","conditions","pages","services","controllers","pageDefMap","listDefMap","componentDefMap","pageMap","componentMap","constructor","options","result","validate","abortEarly","error","structuredClone","value","push","title","type","items","text","forEach","conditionDef","condition","makeCondition","map","pageDef","some","controller","Status","path","Map","page","list","filter","flatMap","components","component","collection","makeSchema","makeFilteredSchema","relevantPages","schema","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","Date","toISOString","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","key","defineProperty","get","from","parse","toExpression","getList","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","V2","keys","getContextValueFromState","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","action","getFormParams","Validate","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n Engine,\n formDefinitionSchema,\n hasComponents,\n hasRepeater,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionsModelData,\n type DateUnits,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: { basePath: string },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n const result = formDefinitionSchema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n text: 'Yes',\n value: true\n },\n {\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(conditionDef)\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections\n */\n makeSchema() {\n return this.makeFilteredSchema(this.pages)\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n return add(new Date(), { [timeUnit]: timePeriod }).toISOString()\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const key in conditions) {\n Object.defineProperty(context, key, {\n get() {\n return conditions[key]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(name: string): List | undefined {\n return this.lists.find((list) => list.name === name)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state)\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // For the V2 engine, we need to initialise `evaluationState` to null\n // for all keys. This is because the current condition evaluation\n // library (eval-expr) will throw if an expression uses a key that is undefined.\n if (this.engine === Engine.V2) {\n for (const page of this.pages) {\n for (const key of page.keys) {\n context.evaluationState[key] = null\n }\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n if (!context.errors) {\n context.errors = []\n }\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (!request.payload || action !== FormAction.Validate) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
@@ -20,6 +20,7 @@ export class SummaryViewModel {
20
20
  errors;
21
21
  serviceUrl;
22
22
  hasMissingNotificationEmail;
23
+ components;
23
24
  constructor(request, page, context) {
24
25
  const {
25
26
  model
@@ -169,8 +170,8 @@ function ItemField(page, state, field, options) {
169
170
  return {
170
171
  name: field.name,
171
172
  label: field.title,
172
- title: field.title,
173
- error: field.getError(options.errors),
173
+ title: field.label,
174
+ error: field.getFirstError(options.errors),
174
175
  value: getAnswer(field, state),
175
176
  href: getPageHref(page, options.path, {
176
177
  returnUrl: getPageHref(page, page.getSummaryPath())
@@ -1 +1 @@
1
- {"version":3,"file":"SummaryViewModel.js","names":["getAnswer","evaluateTemplate","getError","getPageHref","RepeatPageController","validationOptions","opts","SummaryViewModel","page","pageTitle","declaration","details","checkAnswers","context","name","backLink","feedbackLink","phaseTag","errors","serviceUrl","hasMissingNotificationEmail","constructor","request","model","basePath","def","sections","isForceAccess","title","result","makeFilteredSchema","relevantPages","validate","relevantState","stripUnknown","error","map","summaryDetails","detail","rows","items","item","push","href","text","classes","visuallyHiddenText","label","key","value","html","actions","undefined","summaryList","state","forEach","section","sectionPages","filter","collection","path","ItemRepeat","getSummaryPath","field","fields","ItemField","length","options","repeat","values","getListFromState","unit","returnUrl","subItems","repeatState"],"sources":["../../../../../src/server/plugins/engine/models/SummaryViewModel.ts"],"sourcesContent":["import { type Section } from '@defra/forms-model'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n evaluateTemplate,\n getError,\n getPageHref\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type CheckAnswers,\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type SummaryListAction,\n type SummaryListRow\n} from '~/src/server/plugins/engine/types.js'\n\nexport class SummaryViewModel {\n /**\n * Responsible for parsing state values to the govuk-frontend summary list template\n */\n\n page: PageControllerClass\n pageTitle: string\n declaration?: string\n details: Detail[]\n checkAnswers: CheckAnswers[]\n context: FormContext\n name: string | undefined\n backLink?: BackLink\n feedbackLink?: string\n phaseTag?: string\n errors?: FormSubmissionError[]\n serviceUrl: string\n hasMissingNotificationEmail?: boolean\n\n constructor(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n ) {\n const { model } = page\n const { basePath, def, sections } = model\n const { isForceAccess } = context\n\n this.page = page\n this.pageTitle = page.title\n this.serviceUrl = `/${basePath}`\n this.name = def.name\n this.declaration = def.declaration\n this.context = context\n\n const result = model\n .makeFilteredSchema(this.context.relevantPages)\n .validate(this.context.relevantState, { ...opts, stripUnknown: true })\n\n // Format errors\n this.errors = result.error?.details.map(getError)\n this.details = this.summaryDetails(request, sections)\n\n // Format check answers\n this.checkAnswers = this.details.map((detail): CheckAnswers => {\n const { title } = detail\n\n const rows = detail.items.map((item): SummaryListRow => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: item.href,\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: item.label\n })\n }\n\n return {\n key: {\n text: evaluateTemplate(item.title, context)\n },\n value: {\n classes: 'app-prose-scope',\n html: item.value || 'Not supplied'\n },\n actions: {\n items\n }\n }\n })\n\n return {\n title: title ? { text: title } : undefined,\n summaryList: { rows }\n }\n })\n }\n\n private summaryDetails(request: FormContextRequest, sections: Section[]) {\n const { context, errors } = this\n const { relevantPages, state } = context\n\n const details: Detail[] = []\n\n ;[undefined, ...sections].forEach((section) => {\n const items: DetailItem[] = []\n\n const sectionPages = relevantPages.filter(\n (page) => page.section === section\n )\n\n sectionPages.forEach((page) => {\n const { collection, path } = page\n\n if (page instanceof RepeatPageController) {\n items.push(\n ItemRepeat(page, state, {\n path: page.getSummaryPath(request),\n errors\n })\n )\n } else {\n for (const field of collection.fields) {\n items.push(ItemField(page, state, field, { path, errors }))\n }\n }\n })\n\n if (items.length) {\n details.push({\n name: section?.name,\n title: section?.title,\n items\n })\n }\n })\n\n return details\n }\n}\n\n/**\n * Creates a repeater detail item\n * @see {@link DetailItemField}\n */\nfunction ItemRepeat(\n page: RepeatPageController,\n state: FormState,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemRepeat {\n const { collection, repeat } = page\n const { name, title } = repeat.options\n\n const values = page.getListFromState(state)\n const unit = values.length === 1 ? title : `${title}s`\n\n return {\n name,\n label: title,\n title: values.length ? `${unit} added` : unit,\n value: values.length ? `You added ${values.length} ${unit}` : '',\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n\n // Repeater field detail items\n subItems: values.map((repeatState) =>\n collection.fields.map((field) =>\n ItemField(page, repeatState, field, options)\n )\n )\n }\n}\n\n/**\n * Creates a form field detail item\n * @see {@link DetailItemField}\n */\nfunction ItemField(\n page: PageControllerClass,\n state: FormState,\n field: Field,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemField {\n return {\n name: field.name,\n label: field.title,\n title: field.title,\n error: field.getError(options.errors),\n value: getAnswer(field, state),\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n field\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"SummaryViewModel.js","names":["getAnswer","evaluateTemplate","getError","getPageHref","RepeatPageController","validationOptions","opts","SummaryViewModel","page","pageTitle","declaration","details","checkAnswers","context","name","backLink","feedbackLink","phaseTag","errors","serviceUrl","hasMissingNotificationEmail","components","constructor","request","model","basePath","def","sections","isForceAccess","title","result","makeFilteredSchema","relevantPages","validate","relevantState","stripUnknown","error","map","summaryDetails","detail","rows","items","item","push","href","text","classes","visuallyHiddenText","label","key","value","html","actions","undefined","summaryList","state","forEach","section","sectionPages","filter","collection","path","ItemRepeat","getSummaryPath","field","fields","ItemField","length","options","repeat","values","getListFromState","unit","returnUrl","subItems","repeatState","getFirstError"],"sources":["../../../../../src/server/plugins/engine/models/SummaryViewModel.ts"],"sourcesContent":["import { type Section } from '@defra/forms-model'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport {\n evaluateTemplate,\n getError,\n getPageHref\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type CheckAnswers,\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type SummaryListAction,\n type SummaryListRow\n} from '~/src/server/plugins/engine/types.js'\n\nexport class SummaryViewModel {\n /**\n * Responsible for parsing state values to the govuk-frontend summary list template\n */\n\n page: PageControllerClass\n pageTitle: string\n declaration?: string\n details: Detail[]\n checkAnswers: CheckAnswers[]\n context: FormContext\n name: string | undefined\n backLink?: BackLink\n feedbackLink?: string\n phaseTag?: string\n errors?: FormSubmissionError[]\n serviceUrl: string\n hasMissingNotificationEmail?: boolean\n components?: ComponentViewModel[]\n\n constructor(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n ) {\n const { model } = page\n const { basePath, def, sections } = model\n const { isForceAccess } = context\n\n this.page = page\n this.pageTitle = page.title\n this.serviceUrl = `/${basePath}`\n this.name = def.name\n this.declaration = def.declaration\n this.context = context\n\n const result = model\n .makeFilteredSchema(this.context.relevantPages)\n .validate(this.context.relevantState, { ...opts, stripUnknown: true })\n\n // Format errors\n this.errors = result.error?.details.map(getError)\n this.details = this.summaryDetails(request, sections)\n\n // Format check answers\n this.checkAnswers = this.details.map((detail): CheckAnswers => {\n const { title } = detail\n\n const rows = detail.items.map((item): SummaryListRow => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: item.href,\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: item.label\n })\n }\n\n return {\n key: {\n text: evaluateTemplate(item.title, context)\n },\n value: {\n classes: 'app-prose-scope',\n html: item.value || 'Not supplied'\n },\n actions: {\n items\n }\n }\n })\n\n return {\n title: title ? { text: title } : undefined,\n summaryList: { rows }\n }\n })\n }\n\n private summaryDetails(request: FormContextRequest, sections: Section[]) {\n const { context, errors } = this\n const { relevantPages, state } = context\n\n const details: Detail[] = []\n\n ;[undefined, ...sections].forEach((section) => {\n const items: DetailItem[] = []\n\n const sectionPages = relevantPages.filter(\n (page) => page.section === section\n )\n\n sectionPages.forEach((page) => {\n const { collection, path } = page\n\n if (page instanceof RepeatPageController) {\n items.push(\n ItemRepeat(page, state, {\n path: page.getSummaryPath(request),\n errors\n })\n )\n } else {\n for (const field of collection.fields) {\n items.push(ItemField(page, state, field, { path, errors }))\n }\n }\n })\n\n if (items.length) {\n details.push({\n name: section?.name,\n title: section?.title,\n items\n })\n }\n })\n\n return details\n }\n}\n\n/**\n * Creates a repeater detail item\n * @see {@link DetailItemField}\n */\nfunction ItemRepeat(\n page: RepeatPageController,\n state: FormState,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemRepeat {\n const { collection, repeat } = page\n const { name, title } = repeat.options\n\n const values = page.getListFromState(state)\n const unit = values.length === 1 ? title : `${title}s`\n\n return {\n name,\n label: title,\n title: values.length ? `${unit} added` : unit,\n value: values.length ? `You added ${values.length} ${unit}` : '',\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n\n // Repeater field detail items\n subItems: values.map((repeatState) =>\n collection.fields.map((field) =>\n ItemField(page, repeatState, field, options)\n )\n )\n }\n}\n\n/**\n * Creates a form field detail item\n * @see {@link DetailItemField}\n */\nfunction ItemField(\n page: PageControllerClass,\n state: FormState,\n field: Field,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemField {\n return {\n name: field.name,\n label: field.title,\n title: field.label,\n error: field.getFirstError(options.errors),\n value: getAnswer(field, state),\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n field\n }\n}\n"],"mappings":"AAEA,SACEA,SAAS;AAOX,SACEC,gBAAgB,EAChBC,QAAQ,EACRC,WAAW;AAQb,SAASC,oBAAoB;AAE7B,SAASC,iBAAiB,IAAIC,IAAI;AAWlC,OAAO,MAAMC,gBAAgB,CAAC;EAC5B;AACF;AACA;;EAEEC,IAAI;EACJC,SAAS;EACTC,WAAW;EACXC,OAAO;EACPC,YAAY;EACZC,OAAO;EACPC,IAAI;EACJC,QAAQ;EACRC,YAAY;EACZC,QAAQ;EACRC,MAAM;EACNC,UAAU;EACVC,2BAA2B;EAC3BC,UAAU;EAEVC,WAAWA,CACTC,OAA2B,EAC3Bf,IAAyB,EACzBK,OAAoB,EACpB;IACA,MAAM;MAAEW;IAAM,CAAC,GAAGhB,IAAI;IACtB,MAAM;MAAEiB,QAAQ;MAAEC,GAAG;MAAEC;IAAS,CAAC,GAAGH,KAAK;IACzC,MAAM;MAAEI;IAAc,CAAC,GAAGf,OAAO;IAEjC,IAAI,CAACL,IAAI,GAAGA,IAAI;IAChB,IAAI,CAACC,SAAS,GAAGD,IAAI,CAACqB,KAAK;IAC3B,IAAI,CAACV,UAAU,GAAG,IAAIM,QAAQ,EAAE;IAChC,IAAI,CAACX,IAAI,GAAGY,GAAG,CAACZ,IAAI;IACpB,IAAI,CAACJ,WAAW,GAAGgB,GAAG,CAAChB,WAAW;IAClC,IAAI,CAACG,OAAO,GAAGA,OAAO;IAEtB,MAAMiB,MAAM,GAAGN,KAAK,CACjBO,kBAAkB,CAAC,IAAI,CAAClB,OAAO,CAACmB,aAAa,CAAC,CAC9CC,QAAQ,CAAC,IAAI,CAACpB,OAAO,CAACqB,aAAa,EAAE;MAAE,GAAG5B,IAAI;MAAE6B,YAAY,EAAE;IAAK,CAAC,CAAC;;IAExE;IACA,IAAI,CAACjB,MAAM,GAAGY,MAAM,CAACM,KAAK,EAAEzB,OAAO,CAAC0B,GAAG,CAACnC,QAAQ,CAAC;IACjD,IAAI,CAACS,OAAO,GAAG,IAAI,CAAC2B,cAAc,CAACf,OAAO,EAAEI,QAAQ,CAAC;;IAErD;IACA,IAAI,CAACf,YAAY,GAAG,IAAI,CAACD,OAAO,CAAC0B,GAAG,CAAEE,MAAM,IAAmB;MAC7D,MAAM;QAAEV;MAAM,CAAC,GAAGU,MAAM;MAExB,MAAMC,IAAI,GAAGD,MAAM,CAACE,KAAK,CAACJ,GAAG,CAAEK,IAAI,IAAqB;QACtD,MAAMD,KAA0B,GAAG,EAAE;;QAErC;QACA,IAAI,CAACb,aAAa,EAAE;UAClBa,KAAK,CAACE,IAAI,CAAC;YACTC,IAAI,EAAEF,IAAI,CAACE,IAAI;YACfC,IAAI,EAAE,QAAQ;YACdC,OAAO,EAAE,8BAA8B;YACvCC,kBAAkB,EAAEL,IAAI,CAACM;UAC3B,CAAC,CAAC;QACJ;QAEA,OAAO;UACLC,GAAG,EAAE;YACHJ,IAAI,EAAE5C,gBAAgB,CAACyC,IAAI,CAACb,KAAK,EAAEhB,OAAO;UAC5C,CAAC;UACDqC,KAAK,EAAE;YACLJ,OAAO,EAAE,iBAAiB;YAC1BK,IAAI,EAAET,IAAI,CAACQ,KAAK,IAAI;UACtB,CAAC;UACDE,OAAO,EAAE;YACPX;UACF;QACF,CAAC;MACH,CAAC,CAAC;MAEF,OAAO;QACLZ,KAAK,EAAEA,KAAK,GAAG;UAAEgB,IAAI,EAAEhB;QAAM,CAAC,GAAGwB,SAAS;QAC1CC,WAAW,EAAE;UAAEd;QAAK;MACtB,CAAC;IACH,CAAC,CAAC;EACJ;EAEQF,cAAcA,CAACf,OAA2B,EAAEI,QAAmB,EAAE;IACvE,MAAM;MAAEd,OAAO;MAAEK;IAAO,CAAC,GAAG,IAAI;IAChC,MAAM;MAAEc,aAAa;MAAEuB;IAAM,CAAC,GAAG1C,OAAO;IAExC,MAAMF,OAAiB,GAAG,EAAE;IAE3B,CAAC0C,SAAS,EAAE,GAAG1B,QAAQ,CAAC,CAAC6B,OAAO,CAAEC,OAAO,IAAK;MAC7C,MAAMhB,KAAmB,GAAG,EAAE;MAE9B,MAAMiB,YAAY,GAAG1B,aAAa,CAAC2B,MAAM,CACtCnD,IAAI,IAAKA,IAAI,CAACiD,OAAO,KAAKA,OAC7B,CAAC;MAEDC,YAAY,CAACF,OAAO,CAAEhD,IAAI,IAAK;QAC7B,MAAM;UAAEoD,UAAU;UAAEC;QAAK,CAAC,GAAGrD,IAAI;QAEjC,IAAIA,IAAI,YAAYJ,oBAAoB,EAAE;UACxCqC,KAAK,CAACE,IAAI,CACRmB,UAAU,CAACtD,IAAI,EAAE+C,KAAK,EAAE;YACtBM,IAAI,EAAErD,IAAI,CAACuD,cAAc,CAACxC,OAAO,CAAC;YAClCL;UACF,CAAC,CACH,CAAC;QACH,CAAC,MAAM;UACL,KAAK,MAAM8C,KAAK,IAAIJ,UAAU,CAACK,MAAM,EAAE;YACrCxB,KAAK,CAACE,IAAI,CAACuB,SAAS,CAAC1D,IAAI,EAAE+C,KAAK,EAAES,KAAK,EAAE;cAAEH,IAAI;cAAE3C;YAAO,CAAC,CAAC,CAAC;UAC7D;QACF;MACF,CAAC,CAAC;MAEF,IAAIuB,KAAK,CAAC0B,MAAM,EAAE;QAChBxD,OAAO,CAACgC,IAAI,CAAC;UACX7B,IAAI,EAAE2C,OAAO,EAAE3C,IAAI;UACnBe,KAAK,EAAE4B,OAAO,EAAE5B,KAAK;UACrBY;QACF,CAAC,CAAC;MACJ;IACF,CAAC,CAAC;IAEF,OAAO9B,OAAO;EAChB;AACF;;AAEA;AACA;AACA;AACA;AACA,SAASmD,UAAUA,CACjBtD,IAA0B,EAC1B+C,KAAgB,EAChBa,OAGC,EACiB;EAClB,MAAM;IAAER,UAAU;IAAES;EAAO,CAAC,GAAG7D,IAAI;EACnC,MAAM;IAAEM,IAAI;IAAEe;EAAM,CAAC,GAAGwC,MAAM,CAACD,OAAO;EAEtC,MAAME,MAAM,GAAG9D,IAAI,CAAC+D,gBAAgB,CAAChB,KAAK,CAAC;EAC3C,MAAMiB,IAAI,GAAGF,MAAM,CAACH,MAAM,KAAK,CAAC,GAAGtC,KAAK,GAAG,GAAGA,KAAK,GAAG;EAEtD,OAAO;IACLf,IAAI;IACJkC,KAAK,EAAEnB,KAAK;IACZA,KAAK,EAAEyC,MAAM,CAACH,MAAM,GAAG,GAAGK,IAAI,QAAQ,GAAGA,IAAI;IAC7CtB,KAAK,EAAEoB,MAAM,CAACH,MAAM,GAAG,aAAaG,MAAM,CAACH,MAAM,IAAIK,IAAI,EAAE,GAAG,EAAE;IAChE5B,IAAI,EAAEzC,WAAW,CAACK,IAAI,EAAE4D,OAAO,CAACP,IAAI,EAAE;MACpCY,SAAS,EAAEtE,WAAW,CAACK,IAAI,EAAEA,IAAI,CAACuD,cAAc,CAAC,CAAC;IACpD,CAAC,CAAC;IACFR,KAAK;IACL/C,IAAI;IAEJ;IACAkE,QAAQ,EAAEJ,MAAM,CAACjC,GAAG,CAAEsC,WAAW,IAC/Bf,UAAU,CAACK,MAAM,CAAC5B,GAAG,CAAE2B,KAAK,IAC1BE,SAAS,CAAC1D,IAAI,EAAEmE,WAAW,EAAEX,KAAK,EAAEI,OAAO,CAC7C,CACF;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA,SAASF,SAASA,CAChB1D,IAAyB,EACzB+C,KAAgB,EAChBS,KAAY,EACZI,OAGC,EACgB;EACjB,OAAO;IACLtD,IAAI,EAAEkD,KAAK,CAAClD,IAAI;IAChBkC,KAAK,EAAEgB,KAAK,CAACnC,KAAK;IAClBA,KAAK,EAAEmC,KAAK,CAAChB,KAAK;IAClBZ,KAAK,EAAE4B,KAAK,CAACY,aAAa,CAACR,OAAO,CAAClD,MAAM,CAAC;IAC1CgC,KAAK,EAAElD,SAAS,CAACgE,KAAK,EAAET,KAAK,CAAC;IAC9BX,IAAI,EAAEzC,WAAW,CAACK,IAAI,EAAE4D,OAAO,CAACP,IAAI,EAAE;MACpCY,SAAS,EAAEtE,WAAW,CAACK,IAAI,EAAEA,IAAI,CAACuD,cAAc,CAAC,CAAC;IACpD,CAAC,CAAC;IACFR,KAAK;IACL/C,IAAI;IACJwD;EACF,CAAC;AACH","ignoreList":[]}
@@ -21,7 +21,7 @@ export function format(items, model, submitResponse, formStatus) {
21
21
  if (formStatus.isPreview) {
22
22
  lines.push(`This is a test of the ${formName} ${formStatus.state} form.\n`);
23
23
  }
24
- lines.push(`Form submitted at ${escapeMarkdown(formattedNow)}.\n`);
24
+ lines.push(`${formName} form received at ${escapeMarkdown(formattedNow)}.\n`);
25
25
  lines.push('---\n');
26
26
  items.forEach(item => {
27
27
  const label = escapeMarkdown(item.label);
@@ -1 +1 @@
1
- {"version":3,"file":"v1.js","names":["addDays","format","dateFormat","config","escapeMarkdown","getAnswer","designerUrl","get","items","model","submitResponse","formStatus","files","result","formName","name","now","Date","formattedNow","fileExpiryDate","formattedExpiryDate","lines","push","isPreview","state","forEach","item","label","filename","fileId","repeaters","field","main","join"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/human/v1.ts"],"sourcesContent":["import { type SubmitResponsePayload } from '@defra/forms-model'\nimport { addDays, format as dateFormat } from 'date-fns'\n\nimport { config } from '~/src/config/index.js'\nimport {\n escapeMarkdown,\n getAnswer\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n items: DetailItem[],\n model: FormModel,\n submitResponse: SubmitResponsePayload,\n formStatus: ReturnType<typeof checkFormStatus>\n) {\n const { files } = submitResponse.result\n\n const formName = escapeMarkdown(model.name)\n\n /**\n * @todo Refactor this below but the code to\n * generate the question and answers works for now\n */\n const now = new Date()\n const formattedNow = `${dateFormat(now, 'h:mmaaa')} on ${dateFormat(now, 'd MMMM yyyy')}`\n\n const fileExpiryDate = addDays(now, 90)\n const formattedExpiryDate = `${dateFormat(fileExpiryDate, 'h:mmaaa')} on ${dateFormat(fileExpiryDate, 'eeee d MMMM yyyy')}`\n\n const lines: string[] = []\n\n lines.push(\n `^ For security reasons, the links in this email expire at ${escapeMarkdown(formattedExpiryDate)}\\n`\n )\n\n if (formStatus.isPreview) {\n lines.push(`This is a test of the ${formName} ${formStatus.state} form.\\n`)\n }\n\n lines.push(`Form submitted at ${escapeMarkdown(formattedNow)}.\\n`)\n lines.push('---\\n')\n\n items.forEach((item) => {\n const label = escapeMarkdown(item.label)\n\n lines.push(`## ${label}\\n`)\n\n if ('subItems' in item) {\n const filename = escapeMarkdown(`Download ${label} (CSV)`)\n const fileId = files.repeaters[item.name]\n\n lines.push(`[${filename}](${designerUrl}/file-download/${fileId})\\n`)\n } else {\n lines.push(\n getAnswer(item.field, item.state, {\n format: 'email'\n })\n )\n }\n\n lines.push('---\\n')\n })\n\n const filename = escapeMarkdown('Download main form (CSV)')\n lines.push(`[${filename}](${designerUrl}/file-download/${files.main})\\n`)\n\n return lines.join('\\n')\n}\n"],"mappings":"AACA,SAASA,OAAO,EAAEC,MAAM,IAAIC,UAAU,QAAQ,UAAU;AAExD,SAASC,MAAM;AACf,SACEC,cAAc,EACdC,SAAS;AAMX,MAAMC,WAAW,GAAGH,MAAM,CAACI,GAAG,CAAC,aAAa,CAAC;AAE7C,OAAO,SAASN,MAAMA,CACpBO,KAAmB,EACnBC,KAAgB,EAChBC,cAAqC,EACrCC,UAA8C,EAC9C;EACA,MAAM;IAAEC;EAAM,CAAC,GAAGF,cAAc,CAACG,MAAM;EAEvC,MAAMC,QAAQ,GAAGV,cAAc,CAACK,KAAK,CAACM,IAAI,CAAC;;EAE3C;AACF;AACA;AACA;EACE,MAAMC,GAAG,GAAG,IAAIC,IAAI,CAAC,CAAC;EACtB,MAAMC,YAAY,GAAG,GAAGhB,UAAU,CAACc,GAAG,EAAE,SAAS,CAAC,OAAOd,UAAU,CAACc,GAAG,EAAE,aAAa,CAAC,EAAE;EAEzF,MAAMG,cAAc,GAAGnB,OAAO,CAACgB,GAAG,EAAE,EAAE,CAAC;EACvC,MAAMI,mBAAmB,GAAG,GAAGlB,UAAU,CAACiB,cAAc,EAAE,SAAS,CAAC,OAAOjB,UAAU,CAACiB,cAAc,EAAE,kBAAkB,CAAC,EAAE;EAE3H,MAAME,KAAe,GAAG,EAAE;EAE1BA,KAAK,CAACC,IAAI,CACR,6DAA6DlB,cAAc,CAACgB,mBAAmB,CAAC,IAClG,CAAC;EAED,IAAIT,UAAU,CAACY,SAAS,EAAE;IACxBF,KAAK,CAACC,IAAI,CAAC,yBAAyBR,QAAQ,IAAIH,UAAU,CAACa,KAAK,UAAU,CAAC;EAC7E;EAEAH,KAAK,CAACC,IAAI,CAAC,qBAAqBlB,cAAc,CAACc,YAAY,CAAC,KAAK,CAAC;EAClEG,KAAK,CAACC,IAAI,CAAC,OAAO,CAAC;EAEnBd,KAAK,CAACiB,OAAO,CAAEC,IAAI,IAAK;IACtB,MAAMC,KAAK,GAAGvB,cAAc,CAACsB,IAAI,CAACC,KAAK,CAAC;IAExCN,KAAK,CAACC,IAAI,CAAC,MAAMK,KAAK,IAAI,CAAC;IAE3B,IAAI,UAAU,IAAID,IAAI,EAAE;MACtB,MAAME,QAAQ,GAAGxB,cAAc,CAAC,YAAYuB,KAAK,QAAQ,CAAC;MAC1D,MAAME,MAAM,GAAGjB,KAAK,CAACkB,SAAS,CAACJ,IAAI,CAACX,IAAI,CAAC;MAEzCM,KAAK,CAACC,IAAI,CAAC,IAAIM,QAAQ,KAAKtB,WAAW,kBAAkBuB,MAAM,KAAK,CAAC;IACvE,CAAC,MAAM;MACLR,KAAK,CAACC,IAAI,CACRjB,SAAS,CAACqB,IAAI,CAACK,KAAK,EAAEL,IAAI,CAACF,KAAK,EAAE;QAChCvB,MAAM,EAAE;MACV,CAAC,CACH,CAAC;IACH;IAEAoB,KAAK,CAACC,IAAI,CAAC,OAAO,CAAC;EACrB,CAAC,CAAC;EAEF,MAAMM,QAAQ,GAAGxB,cAAc,CAAC,0BAA0B,CAAC;EAC3DiB,KAAK,CAACC,IAAI,CAAC,IAAIM,QAAQ,KAAKtB,WAAW,kBAAkBM,KAAK,CAACoB,IAAI,KAAK,CAAC;EAEzE,OAAOX,KAAK,CAACY,IAAI,CAAC,IAAI,CAAC;AACzB","ignoreList":[]}
1
+ {"version":3,"file":"v1.js","names":["addDays","format","dateFormat","config","escapeMarkdown","getAnswer","designerUrl","get","items","model","submitResponse","formStatus","files","result","formName","name","now","Date","formattedNow","fileExpiryDate","formattedExpiryDate","lines","push","isPreview","state","forEach","item","label","filename","fileId","repeaters","field","main","join"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/human/v1.ts"],"sourcesContent":["import { type SubmitResponsePayload } from '@defra/forms-model'\nimport { addDays, format as dateFormat } from 'date-fns'\n\nimport { config } from '~/src/config/index.js'\nimport {\n escapeMarkdown,\n getAnswer\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n items: DetailItem[],\n model: FormModel,\n submitResponse: SubmitResponsePayload,\n formStatus: ReturnType<typeof checkFormStatus>\n) {\n const { files } = submitResponse.result\n\n const formName = escapeMarkdown(model.name)\n\n /**\n * @todo Refactor this below but the code to\n * generate the question and answers works for now\n */\n const now = new Date()\n const formattedNow = `${dateFormat(now, 'h:mmaaa')} on ${dateFormat(now, 'd MMMM yyyy')}`\n\n const fileExpiryDate = addDays(now, 90)\n const formattedExpiryDate = `${dateFormat(fileExpiryDate, 'h:mmaaa')} on ${dateFormat(fileExpiryDate, 'eeee d MMMM yyyy')}`\n\n const lines: string[] = []\n\n lines.push(\n `^ For security reasons, the links in this email expire at ${escapeMarkdown(formattedExpiryDate)}\\n`\n )\n\n if (formStatus.isPreview) {\n lines.push(`This is a test of the ${formName} ${formStatus.state} form.\\n`)\n }\n\n lines.push(`${formName} form received at ${escapeMarkdown(formattedNow)}.\\n`)\n lines.push('---\\n')\n\n items.forEach((item) => {\n const label = escapeMarkdown(item.label)\n\n lines.push(`## ${label}\\n`)\n\n if ('subItems' in item) {\n const filename = escapeMarkdown(`Download ${label} (CSV)`)\n const fileId = files.repeaters[item.name]\n\n lines.push(`[${filename}](${designerUrl}/file-download/${fileId})\\n`)\n } else {\n lines.push(\n getAnswer(item.field, item.state, {\n format: 'email'\n })\n )\n }\n\n lines.push('---\\n')\n })\n\n const filename = escapeMarkdown('Download main form (CSV)')\n lines.push(`[${filename}](${designerUrl}/file-download/${files.main})\\n`)\n\n return lines.join('\\n')\n}\n"],"mappings":"AACA,SAASA,OAAO,EAAEC,MAAM,IAAIC,UAAU,QAAQ,UAAU;AAExD,SAASC,MAAM;AACf,SACEC,cAAc,EACdC,SAAS;AAMX,MAAMC,WAAW,GAAGH,MAAM,CAACI,GAAG,CAAC,aAAa,CAAC;AAE7C,OAAO,SAASN,MAAMA,CACpBO,KAAmB,EACnBC,KAAgB,EAChBC,cAAqC,EACrCC,UAA8C,EAC9C;EACA,MAAM;IAAEC;EAAM,CAAC,GAAGF,cAAc,CAACG,MAAM;EAEvC,MAAMC,QAAQ,GAAGV,cAAc,CAACK,KAAK,CAACM,IAAI,CAAC;;EAE3C;AACF;AACA;AACA;EACE,MAAMC,GAAG,GAAG,IAAIC,IAAI,CAAC,CAAC;EACtB,MAAMC,YAAY,GAAG,GAAGhB,UAAU,CAACc,GAAG,EAAE,SAAS,CAAC,OAAOd,UAAU,CAACc,GAAG,EAAE,aAAa,CAAC,EAAE;EAEzF,MAAMG,cAAc,GAAGnB,OAAO,CAACgB,GAAG,EAAE,EAAE,CAAC;EACvC,MAAMI,mBAAmB,GAAG,GAAGlB,UAAU,CAACiB,cAAc,EAAE,SAAS,CAAC,OAAOjB,UAAU,CAACiB,cAAc,EAAE,kBAAkB,CAAC,EAAE;EAE3H,MAAME,KAAe,GAAG,EAAE;EAE1BA,KAAK,CAACC,IAAI,CACR,6DAA6DlB,cAAc,CAACgB,mBAAmB,CAAC,IAClG,CAAC;EAED,IAAIT,UAAU,CAACY,SAAS,EAAE;IACxBF,KAAK,CAACC,IAAI,CAAC,yBAAyBR,QAAQ,IAAIH,UAAU,CAACa,KAAK,UAAU,CAAC;EAC7E;EAEAH,KAAK,CAACC,IAAI,CAAC,GAAGR,QAAQ,qBAAqBV,cAAc,CAACc,YAAY,CAAC,KAAK,CAAC;EAC7EG,KAAK,CAACC,IAAI,CAAC,OAAO,CAAC;EAEnBd,KAAK,CAACiB,OAAO,CAAEC,IAAI,IAAK;IACtB,MAAMC,KAAK,GAAGvB,cAAc,CAACsB,IAAI,CAACC,KAAK,CAAC;IAExCN,KAAK,CAACC,IAAI,CAAC,MAAMK,KAAK,IAAI,CAAC;IAE3B,IAAI,UAAU,IAAID,IAAI,EAAE;MACtB,MAAME,QAAQ,GAAGxB,cAAc,CAAC,YAAYuB,KAAK,QAAQ,CAAC;MAC1D,MAAME,MAAM,GAAGjB,KAAK,CAACkB,SAAS,CAACJ,IAAI,CAACX,IAAI,CAAC;MAEzCM,KAAK,CAACC,IAAI,CAAC,IAAIM,QAAQ,KAAKtB,WAAW,kBAAkBuB,MAAM,KAAK,CAAC;IACvE,CAAC,MAAM;MACLR,KAAK,CAACC,IAAI,CACRjB,SAAS,CAACqB,IAAI,CAACK,KAAK,EAAEL,IAAI,CAACF,KAAK,EAAE;QAChCvB,MAAM,EAAE;MACV,CAAC,CACH,CAAC;IACH;IAEAoB,KAAK,CAACC,IAAI,CAAC,OAAO,CAAC;EACrB,CAAC,CAAC;EAEF,MAAMM,QAAQ,GAAGxB,cAAc,CAAC,0BAA0B,CAAC;EAC3DiB,KAAK,CAACC,IAAI,CAAC,IAAIM,QAAQ,KAAKtB,WAAW,kBAAkBM,KAAK,CAACoB,IAAI,KAAK,CAAC;EAEzE,OAAOX,KAAK,CAACY,IAAI,CAAC,IAAI,CAAC;AACzB","ignoreList":[]}
@@ -102,11 +102,19 @@ export class PageController {
102
102
  return def.phaseBanner?.phase;
103
103
  }
104
104
  getHref(path) {
105
- const {
106
- model
107
- } = this;
108
- return path === '/' ? `/${model.basePath}` // Strip trailing slash
109
- : `/${model.basePath}${path}`;
105
+ const basePath = this.model.basePath;
106
+ if (path === '/') {
107
+ return `/${basePath}`;
108
+ }
109
+
110
+ // if ever the path is not prefixed with a slash, add it
111
+ const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path;
112
+ let finalPath = `/${basePath}`;
113
+ if (relativeTargetPath) {
114
+ finalPath += `/${relativeTargetPath}`;
115
+ }
116
+ finalPath = finalPath.replace(/\/{2,}/g, '/');
117
+ return finalPath;
110
118
  }
111
119
  getStartPath() {
112
120
  return getStartPath(this.model);
@@ -1 +1 @@
1
- {"version":3,"file":"PageController.js","names":["ControllerPath","Boom","encodeUrl","getStartPath","normalisePath","PageController","def","name","model","pageDef","title","section","condition","events","collection","viewName","constructor","sections","find","conditions","view","path","href","getHref","keys","getRouteOptions","postRouteOptions","viewModel","showTitle","pageTitle","sectionTitle","hideTitle","page","isStartPage","serviceUrl","feedbackLink","phaseTag","feedback","emailAddress","url","phaseBanner","phase","basePath","getSummaryPath","Summary","valueOf","getStatusPath","Status","makeGetRouteHandler","request","context","h","makePostRouteHandler","badRequest"],"sources":["../../../../../src/server/plugins/engine/pageControllers/PageController.ts"],"sourcesContent":["import {\n ControllerPath,\n type Events,\n type FormDefinition,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type Lifecycle,\n type ResponseToolkit,\n type RouteOptions\n} from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n encodeUrl,\n getStartPath,\n normalisePath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FormContext,\n type PageViewModelBase\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'\n\nexport class PageController {\n /**\n * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.\n */\n def: FormDefinition\n name?: string\n model: FormModel\n pageDef: Page\n title: string\n section?: Section\n condition?: ExecutableCondition\n events?: Events\n collection?: ComponentCollection\n viewName = 'index'\n\n constructor(model: FormModel, pageDef: Page) {\n const { def } = model\n\n this.def = def\n this.name = def.name\n this.model = model\n this.pageDef = pageDef\n this.title = pageDef.title\n this.events = pageDef.events\n\n // Resolve section\n this.section = model.sections.find(\n (section) => section.name === pageDef.section\n )\n\n // Resolve condition\n if (pageDef.condition) {\n this.condition = model.conditions[pageDef.condition]\n }\n\n // Override view name\n if (pageDef.view) {\n this.viewName = pageDef.view\n }\n }\n\n get path() {\n return this.pageDef.path\n }\n\n get href() {\n const { path } = this\n return this.getHref(`/${normalisePath(path)}`)\n }\n\n get keys() {\n return this.collection?.keys ?? []\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {}\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {}\n }\n\n get viewModel(): PageViewModelBase {\n const { name, section, title } = this\n\n const showTitle = true\n const pageTitle = title\n const sectionTitle = section?.hideTitle !== true ? section?.title : ''\n\n return {\n name,\n page: this,\n pageTitle,\n sectionTitle,\n showTitle,\n isStartPage: false,\n serviceUrl: this.getHref('/'),\n feedbackLink: this.feedbackLink,\n phaseTag: this.phaseTag\n }\n }\n\n get feedbackLink() {\n const { def } = this\n\n // setting the feedbackLink to undefined here for feedback forms prevents the feedback link from being shown\n const feedbackLink = def.feedback?.emailAddress\n ? `mailto:${def.feedback.emailAddress}`\n : def.feedback?.url\n\n return encodeUrl(feedbackLink)\n }\n\n get phaseTag() {\n const { def } = this\n return def.phaseBanner?.phase\n }\n\n getHref(path: string) {\n const { model } = this\n\n return path === '/'\n ? `/${model.basePath}` // Strip trailing slash\n : `/${model.basePath}${path}`\n }\n\n getStartPath() {\n return getStartPath(this.model)\n }\n\n getSummaryPath() {\n return ControllerPath.Summary.valueOf()\n }\n\n getStatusPath() {\n return ControllerPath.Status.valueOf()\n }\n\n makeGetRouteHandler(): (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestRefs>> {\n return (request, context, h) => {\n const { viewModel, viewName } = this\n return h.view(viewName, viewModel)\n }\n }\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {\n throw Boom.badRequest('Unsupported POST route handler for this page')\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"PageController.js","names":["ControllerPath","Boom","encodeUrl","getStartPath","normalisePath","PageController","def","name","model","pageDef","title","section","condition","events","collection","viewName","constructor","sections","find","conditions","view","path","href","getHref","keys","getRouteOptions","postRouteOptions","viewModel","showTitle","pageTitle","sectionTitle","hideTitle","page","isStartPage","serviceUrl","feedbackLink","phaseTag","feedback","emailAddress","url","phaseBanner","phase","basePath","relativeTargetPath","startsWith","substring","finalPath","replace","getSummaryPath","Summary","valueOf","getStatusPath","Status","makeGetRouteHandler","request","context","h","makePostRouteHandler","badRequest"],"sources":["../../../../../src/server/plugins/engine/pageControllers/PageController.ts"],"sourcesContent":["import {\n ControllerPath,\n type Events,\n type FormDefinition,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type Lifecycle,\n type ResponseToolkit,\n type RouteOptions\n} from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n encodeUrl,\n getStartPath,\n normalisePath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FormContext,\n type PageViewModelBase\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'\n\nexport class PageController {\n /**\n * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.\n */\n def: FormDefinition\n name?: string\n model: FormModel\n pageDef: Page\n title: string\n section?: Section\n condition?: ExecutableCondition\n events?: Events\n collection?: ComponentCollection\n viewName = 'index'\n\n constructor(model: FormModel, pageDef: Page) {\n const { def } = model\n\n this.def = def\n this.name = def.name\n this.model = model\n this.pageDef = pageDef\n this.title = pageDef.title\n this.events = pageDef.events\n\n // Resolve section\n this.section = model.sections.find(\n (section) => section.name === pageDef.section\n )\n\n // Resolve condition\n if (pageDef.condition) {\n this.condition = model.conditions[pageDef.condition]\n }\n\n // Override view name\n if (pageDef.view) {\n this.viewName = pageDef.view\n }\n }\n\n get path() {\n return this.pageDef.path\n }\n\n get href() {\n const { path } = this\n return this.getHref(`/${normalisePath(path)}`)\n }\n\n get keys() {\n return this.collection?.keys ?? []\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {}\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {}\n }\n\n get viewModel(): PageViewModelBase {\n const { name, section, title } = this\n\n const showTitle = true\n const pageTitle = title\n const sectionTitle = section?.hideTitle !== true ? section?.title : ''\n\n return {\n name,\n page: this,\n pageTitle,\n sectionTitle,\n showTitle,\n isStartPage: false,\n serviceUrl: this.getHref('/'),\n feedbackLink: this.feedbackLink,\n phaseTag: this.phaseTag\n }\n }\n\n get feedbackLink() {\n const { def } = this\n\n // setting the feedbackLink to undefined here for feedback forms prevents the feedback link from being shown\n const feedbackLink = def.feedback?.emailAddress\n ? `mailto:${def.feedback.emailAddress}`\n : def.feedback?.url\n\n return encodeUrl(feedbackLink)\n }\n\n get phaseTag() {\n const { def } = this\n return def.phaseBanner?.phase\n }\n\n getHref(path: string): string {\n const basePath = this.model.basePath\n\n if (path === '/') {\n return `/${basePath}`\n }\n\n // if ever the path is not prefixed with a slash, add it\n const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path\n let finalPath = `/${basePath}`\n if (relativeTargetPath) {\n finalPath += `/${relativeTargetPath}`\n }\n finalPath = finalPath.replace(/\\/{2,}/g, '/')\n\n return finalPath\n }\n\n getStartPath() {\n return getStartPath(this.model)\n }\n\n getSummaryPath() {\n return ControllerPath.Summary.valueOf()\n }\n\n getStatusPath() {\n return ControllerPath.Status.valueOf()\n }\n\n makeGetRouteHandler(): (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestRefs>> {\n return (request, context, h) => {\n const { viewModel, viewName } = this\n return h.view(viewName, viewModel)\n }\n }\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {\n throw Boom.badRequest('Unsupported POST route handler for this page')\n }\n}\n"],"mappings":"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","ignoreList":[]}