@defra/forms-engine-plugin 4.0.32 → 4.0.34
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.
- package/.server/server/constants.d.ts +1 -0
- package/.server/server/constants.js +1 -0
- package/.server/server/constants.js.map +1 -1
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +0 -1
- package/.server/server/forms/simple-form.yaml +64 -0
- package/.server/server/plugins/engine/beta/form-context.js +1 -2
- package/.server/server/plugins/engine/beta/form-context.js.map +1 -1
- package/.server/server/plugins/engine/components/CheckboxesField.d.ts +1 -1
- package/.server/server/plugins/engine/components/CheckboxesField.js +3 -0
- package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
- package/.server/server/plugins/engine/components/FileUploadField.d.ts +4 -3
- package/.server/server/plugins/engine/components/FileUploadField.js +38 -0
- package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
- package/.server/server/plugins/engine/components/FormComponent.d.ts +9 -7
- package/.server/server/plugins/engine/components/FormComponent.js +3 -0
- package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +5 -0
- package/.server/server/plugins/engine/helpers.js +7 -0
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +6 -2
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +4 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +4 -0
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +33 -35
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/__stubs__/request.d.ts +2 -2
- package/.server/server/plugins/engine/pageControllers/__stubs__/request.js +9 -0
- package/.server/server/plugins/engine/pageControllers/__stubs__/request.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/errors.d.ts +15 -0
- package/.server/server/plugins/engine/pageControllers/errors.js +25 -0
- package/.server/server/plugins/engine/pageControllers/errors.js.map +1 -0
- package/.server/server/plugins/engine/services/localFormsService.js +6 -0
- package/.server/server/plugins/engine/services/localFormsService.js.map +1 -1
- package/.server/server/plugins/engine/views/index.html +1 -1
- package/.server/server/plugins/nunjucks/context.test.js +9 -1
- package/.server/server/plugins/nunjucks/context.test.js.map +1 -1
- package/.server/server/plugins/nunjucks/types.d.ts +4 -0
- package/.server/server/plugins/nunjucks/types.js +1 -0
- package/.server/server/plugins/nunjucks/types.js.map +1 -1
- package/.server/server/services/cacheService.d.ts +1 -0
- package/.server/server/services/cacheService.js +10 -0
- package/.server/server/services/cacheService.js.map +1 -1
- package/.server/typings/hapi/index.d.js.map +1 -1
- package/package.json +1 -1
- package/src/server/constants.js +1 -0
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +0 -1
- package/src/server/forms/simple-form.yaml +64 -0
- package/src/server/plugins/engine/beta/form-context.test.ts +4 -3
- package/src/server/plugins/engine/beta/form-context.ts +4 -3
- package/src/server/plugins/engine/components/CheckboxesField.test.ts +38 -18
- package/src/server/plugins/engine/components/CheckboxesField.ts +7 -1
- package/src/server/plugins/engine/components/FileUploadField.test.ts +203 -2
- package/src/server/plugins/engine/components/FileUploadField.ts +61 -2
- package/src/server/plugins/engine/components/FormComponent.ts +17 -1
- package/src/server/plugins/engine/helpers.ts +8 -0
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +9 -0
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +11 -4
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +6 -0
- package/src/server/plugins/engine/pageControllers/SummaryPageController.test.ts +3 -0
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +55 -46
- package/src/server/plugins/engine/pageControllers/__stubs__/request.ts +14 -4
- package/src/server/plugins/engine/pageControllers/errors.test.ts +63 -0
- package/src/server/plugins/engine/pageControllers/errors.ts +30 -0
- package/src/server/plugins/engine/services/localFormsService.js +7 -0
- package/src/server/plugins/engine/views/index.html +1 -1
- package/src/server/plugins/nunjucks/context.test.js +10 -2
- package/src/server/plugins/nunjucks/types.js +1 -0
- package/src/server/services/cacheService.ts +16 -0
- package/src/typings/hapi/index.d.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","ComponentCollection","FileUploadField","getAnswer","checkEmailAddressForLiveFormSubmission","checkFormStatus","getCacheService","SummaryViewModel","QuestionPageController","FormAction","SummaryPageController","allowSaveAndExit","constructor","model","pageDef","viewName","collection","components","page","getSummaryViewModel","request","context","viewModel","query","payload","errors","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","shouldShowSaveAndExit","server","makeGetRouteHandler","h","hasMissingNotificationEmail","view","makePostRouteHandler","action","SaveAndExit","handleSaveAndExit","handleFormSubmit","params","cacheService","formsService","services","getFormMetadata","formMetadata","slug","notificationEmail","isPreview","emailAddress","def","outputEmail","submitForm","setConfirmationState","confirmed","formId","state","clearState","proceed","getStatusPath","postRouteOptions","ext","onPreHandler","method","continue","summaryViewModel","extendFileRetention","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","yar","id","undefined","badRequest","outputService","submit","updatedRetrievalKey","formSubmissionService","persistFiles","files","pages","forEach","fileUploadComponents","fields","filter","component","values","getFormValueFromState","length","push","map","status","fileId","form","file","initiatedRetrievalKey","metadata","retrievalKey","sessionId","main","item","name","title","label","value","field","format","repeaters","subItems","detailItems","subItem","relevantPages","href","flatMap","flat"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import {\n hasComponentsEvenIfNoNext,\n type FormMetadata,\n type Page,\n type SubmitPayload\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormConfirmationState,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n allowSaveAndExit = true\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n this.viewName = 'summary'\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n const { query } = request\n const { payload, errors } = context\n const components = this.collection.getViewModel(payload, errors, query)\n\n // We already figure these out in the base page controller. Take them and apply them to our page-specific model.\n // This is a stop-gap until we can add proper inheritance in place.\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndExit = this.shouldShowSaveAndExit(request.server)\n viewModel.errors = errors\n\n return viewModel\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewName } = this\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n // Check if this is a save-and-exit action\n const { action } = request.payload\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n return this.handleFormSubmit(request, context, h)\n }\n }\n\n async handleFormSubmit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const { model } = this\n const { params } = request\n\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Get the form metadata using the `slug` param\n const formMetadata = await getFormMetadata(params.slug)\n const { notificationEmail } = formMetadata\n const { isPreview } = checkFormStatus(request.params)\n const emailAddress = notificationEmail ?? this.model.def.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Send submission email\n if (emailAddress) {\n const viewModel = this.getSummaryViewModel(request, context)\n await submitForm(\n context,\n request,\n viewModel,\n model,\n emailAddress,\n formMetadata\n )\n }\n\n await cacheService.setConfirmationState(request, {\n confirmed: true,\n formId: context.state.formId\n } as FormConfirmationState)\n\n // Clear all form data\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nexport async function submitForm(\n context: FormContext,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string,\n formMetadata: FormMetadata\n) {\n await extendFileRetention(model, context.state, emailAddress)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n // Get detail items\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n // Submit data\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n return model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse,\n formMetadata\n )\n}\n\nasync function extendFileRetention(\n model: FormModel,\n state: FormSubmissionState,\n updatedRetrievalKey: string\n) {\n const { formSubmissionService } = model.services\n const { persistFiles } = formSubmissionService\n const files: { fileId: string; initiatedRetrievalKey: string }[] = []\n\n // For each file upload component with files in\n // state, add the files to the batch getting persisted\n model.pages.forEach((page) => {\n const fileUploadComponents = page.collection.fields.filter(\n (component) => component instanceof FileUploadField\n )\n\n fileUploadComponents.forEach((component) => {\n const values = component.getFormValueFromState(state)\n if (!values?.length) {\n return\n }\n\n files.push(\n ...values.map(({ status }) => ({\n fileId: status.form.file.fileId,\n initiatedRetrievalKey: status.metadata.retrievalKey\n }))\n )\n })\n })\n\n if (files.length) {\n return persistFiles(files, updatedRetrievalKey)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n\n // Main form answers\n main: items\n .filter((item) => 'field' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n value: getAnswer(item.field, item.state, { format: 'data' })\n })),\n\n // Repeater form answers\n repeaters: items\n .filter((item) => 'subItems' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n\n // Repeater item values\n value: item.subItems.map((detailItems) =>\n detailItems.map((subItem) => ({\n name: subItem.name,\n title: subItem.label,\n value: getAnswer(subItem.field, subItem.state, { format: 'data' })\n }))\n )\n }))\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n return context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","COMPONENT_STATE_ERROR","ComponentCollection","getAnswer","checkEmailAddressForLiveFormSubmission","checkFormStatus","createError","getCacheService","SummaryViewModel","QuestionPageController","InvalidComponentStateError","FormAction","SummaryPageController","allowSaveAndExit","constructor","model","pageDef","viewName","collection","components","page","getSummaryViewModel","request","context","viewModel","query","payload","errors","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","shouldShowSaveAndExit","server","makeGetRouteHandler","h","hasMissingNotificationEmail","view","makePostRouteHandler","action","SaveAndExit","handleSaveAndExit","handleFormSubmit","params","cacheService","formsService","services","getFormMetadata","formMetadata","slug","notificationEmail","isPreview","submitForm","error","govukError","component","name","userMessage","yar","flash","resetComponentStates","getStateKeys","proceed","path","setConfirmationState","confirmed","formId","state","clearState","getStatusPath","postRouteOptions","ext","onPreHandler","method","continue","metadata","summaryViewModel","emailAddress","finaliseComponents","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","id","undefined","badRequest","outputService","submit","relevantFields","relevantPages","flatMap","fields","onSubmit","retrievalKey","sessionId","formSubmissionService","main","filter","item","map","title","label","value","field","format","repeaters","subItems","detailItems","subItem","href","flat"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import {\n hasComponentsEvenIfNoNext,\n type FormMetadata,\n type Page,\n type SubmitPayload\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\n\nimport { COMPONENT_STATE_ERROR } from '~/src/server/constants.js'\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n createError,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { InvalidComponentStateError } from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n type FormConfirmationState,\n type FormContext,\n type FormContextRequest\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n allowSaveAndExit = true\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n this.viewName = 'summary'\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n const { query } = request\n const { payload, errors } = context\n const components = this.collection.getViewModel(payload, errors, query)\n\n // We already figure these out in the base page controller. Take them and apply them to our page-specific model.\n // This is a stop-gap until we can add proper inheritance in place.\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndExit = this.shouldShowSaveAndExit(request.server)\n viewModel.errors = errors\n\n return viewModel\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewName } = this\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n // Check if this is a save-and-exit action\n const { action } = request.payload\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n return this.handleFormSubmit(request, context, h)\n }\n }\n\n async handleFormSubmit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const { model } = this\n const { params } = request\n\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Get the form metadata using the `slug` param\n const formMetadata = await getFormMetadata(params.slug)\n const { notificationEmail } = formMetadata\n const { isPreview } = checkFormStatus(request.params)\n\n checkEmailAddressForLiveFormSubmission(notificationEmail, isPreview)\n\n // Send submission email\n if (notificationEmail) {\n const viewModel = this.getSummaryViewModel(request, context)\n\n try {\n await submitForm(\n context,\n formMetadata,\n request,\n viewModel,\n model,\n notificationEmail,\n formMetadata\n )\n } catch (error) {\n if (error instanceof InvalidComponentStateError) {\n const govukError = createError(\n error.component.name,\n error.userMessage\n )\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n return this.proceed(request, h, error.component.page?.path)\n }\n\n throw error\n }\n }\n\n await cacheService.setConfirmationState(request, {\n confirmed: true,\n formId: context.state.formId\n } as FormConfirmationState)\n\n // Clear all form data\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nexport async function submitForm(\n context: FormContext,\n metadata: FormMetadata,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string,\n formMetadata: FormMetadata\n) {\n await finaliseComponents(request, metadata, context)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n // Get detail items\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n // Submit data\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n return model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse,\n formMetadata\n )\n}\n\n/**\n * Finalises any components that need post-processing before form submission. Candidates usually involve\n * those that have external state.\n * Examples include:\n * - file uploads which are 'persisted' before submission\n * - payments which are 'captured' before submission\n */\nasync function finaliseComponents(\n request: FormRequestPayload,\n metadata: FormMetadata,\n context: FormContext\n) {\n const relevantFields = context.relevantPages.flatMap(\n (page) => page.collection.fields\n )\n\n for (const component of relevantFields) {\n /*\n Each component will throw InvalidComponent if its state is invalid, which is handled\n by handleFormSubmit\n */\n await component.onSubmit(request, metadata, context)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n\n // Main form answers\n main: items\n .filter((item) => 'field' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n value: getAnswer(item.field, item.state, { format: 'data' })\n })),\n\n // Repeater form answers\n repeaters: items\n .filter((item) => 'subItems' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n\n // Repeater item values\n value: item.subItems.map((detailItems) =>\n detailItems.map((subItem) => ({\n name: subItem.name,\n title: subItem.label,\n value: getAnswer(subItem.field, subItem.state, { format: 'data' })\n }))\n )\n }))\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n return context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n}\n"],"mappings":"AAAA,SACEA,yBAAyB,QAIpB,oBAAoB;AAC3B,OAAOC,IAAI,MAAM,YAAY;AAG7B,SAASC,qBAAqB;AAC9B,SAASC,mBAAmB;AAC5B,SAASC,SAAS;AAClB,SACEC,sCAAsC,EACtCC,eAAe,EACfC,WAAW,EACXC,eAAe;AAEjB,SACEC,gBAAgB;AAOlB,SAASC,sBAAsB;AAC/B,SAASC,0BAA0B;AAMnC,SACEC,UAAU;AAOZ,OAAO,MAAMC,qBAAqB,SAASH,sBAAsB,CAAC;EAEhEI,gBAAgB,GAAG,IAAI;;EAEvB;AACF;AACA;;EAEEC,WAAWA,CAACC,KAAgB,EAAEC,OAAa,EAAE;IAC3C,KAAK,CAACD,KAAK,EAAEC,OAAO,CAAC;IACrB,IAAI,CAACC,QAAQ,GAAG,SAAS;;IAEzB;IACA,IAAI,CAACC,UAAU,GAAG,IAAIhB,mBAAmB,CACvCH,yBAAyB,CAACiB,OAAO,CAAC,GAAGA,OAAO,CAACG,UAAU,GAAG,EAAE,EAC5D;MAAEJ,KAAK;MAAEK,IAAI,EAAE;IAAK,CACtB,CAAC;EACH;EAEAC,mBAAmBA,CACjBC,OAA2B,EAC3BC,OAAoB,EACF;IAClB,MAAMC,SAAS,GAAG,IAAIhB,gBAAgB,CAACc,OAAO,EAAE,IAAI,EAAEC,OAAO,CAAC;IAE9D,MAAM;MAAEE;IAAM,CAAC,GAAGH,OAAO;IACzB,MAAM;MAAEI,OAAO;MAAEC;IAAO,CAAC,GAAGJ,OAAO;IACnC,MAAMJ,UAAU,GAAG,IAAI,CAACD,UAAU,CAACU,YAAY,CAACF,OAAO,EAAEC,MAAM,EAAEF,KAAK,CAAC;;IAEvE;IACA;IACAD,SAAS,CAACK,QAAQ,GAAG,IAAI,CAACC,WAAW,CAACR,OAAO,EAAEC,OAAO,CAAC;IACvDC,SAAS,CAACO,YAAY,GAAG,IAAI,CAACA,YAAY;IAC1CP,SAAS,CAACQ,QAAQ,GAAG,IAAI,CAACA,QAAQ;IAClCR,SAAS,CAACL,UAAU,GAAGA,UAAU;IACjCK,SAAS,CAACX,gBAAgB,GAAG,IAAI,CAACoB,qBAAqB,CAACX,OAAO,CAACY,MAAM,CAAC;IACvEV,SAAS,CAACG,MAAM,GAAGA,MAAM;IAEzB,OAAOH,SAAS;EAClB;;EAEA;AACF;AACA;EACEW,mBAAmBA,CAAA,EAAG;IACpB,OAAO,OACLb,OAAoB,EACpBC,OAAoB,EACpBa,CAAsB,KACnB;MACH,MAAM;QAAEnB;MAAS,CAAC,GAAG,IAAI;MAEzB,MAAMO,SAAS,GAAG,IAAI,CAACH,mBAAmB,CAACC,OAAO,EAAEC,OAAO,CAAC;MAE5DC,SAAS,CAACa,2BAA2B,GACnC,MAAM,IAAI,CAACA,2BAA2B,CAACf,OAAO,EAAEC,OAAO,CAAC;MAE1D,OAAOa,CAAC,CAACE,IAAI,CAACrB,QAAQ,EAAEO,SAAS,CAAC;IACpC,CAAC;EACH;;EAEA;AACF;AACA;AACA;EACEe,oBAAoBA,CAAA,EAAG;IACrB,OAAO,OACLjB,OAA2B,EAC3BC,OAAoB,EACpBa,CAAsB,KACnB;MACH;MACA,MAAM;QAAEI;MAAO,CAAC,GAAGlB,OAAO,CAACI,OAAO;MAClC,IAAIc,MAAM,KAAK7B,UAAU,CAAC8B,WAAW,EAAE;QACrC,OAAO,IAAI,CAACC,iBAAiB,CAACpB,OAAO,EAAEC,OAAO,EAAEa,CAAC,CAAC;MACpD;MAEA,OAAO,IAAI,CAACO,gBAAgB,CAACrB,OAAO,EAAEC,OAAO,EAAEa,CAAC,CAAC;IACnD,CAAC;EACH;EAEA,MAAMO,gBAAgBA,CACpBrB,OAA2B,EAC3BC,OAAoB,EACpBa,CAAsB,EACtB;IACA,MAAM;MAAErB;IAAM,CAAC,GAAG,IAAI;IACtB,MAAM;MAAE6B;IAAO,CAAC,GAAGtB,OAAO;IAE1B,MAAMuB,YAAY,GAAGtC,eAAe,CAACe,OAAO,CAACY,MAAM,CAAC;IAEpD,MAAM;MAAEY;IAAa,CAAC,GAAG,IAAI,CAAC/B,KAAK,CAACgC,QAAQ;IAC5C,MAAM;MAAEC;IAAgB,CAAC,GAAGF,YAAY;;IAExC;IACA,MAAMG,YAAY,GAAG,MAAMD,eAAe,CAACJ,MAAM,CAACM,IAAI,CAAC;IACvD,MAAM;MAAEC;IAAkB,CAAC,GAAGF,YAAY;IAC1C,MAAM;MAAEG;IAAU,CAAC,GAAG/C,eAAe,CAACiB,OAAO,CAACsB,MAAM,CAAC;IAErDxC,sCAAsC,CAAC+C,iBAAiB,EAAEC,SAAS,CAAC;;IAEpE;IACA,IAAID,iBAAiB,EAAE;MACrB,MAAM3B,SAAS,GAAG,IAAI,CAACH,mBAAmB,CAACC,OAAO,EAAEC,OAAO,CAAC;MAE5D,IAAI;QACF,MAAM8B,UAAU,CACd9B,OAAO,EACP0B,YAAY,EACZ3B,OAAO,EACPE,SAAS,EACTT,KAAK,EACLoC,iBAAiB,EACjBF,YACF,CAAC;MACH,CAAC,CAAC,OAAOK,KAAK,EAAE;QACd,IAAIA,KAAK,YAAY5C,0BAA0B,EAAE;UAC/C,MAAM6C,UAAU,GAAGjD,WAAW,CAC5BgD,KAAK,CAACE,SAAS,CAACC,IAAI,EACpBH,KAAK,CAACI,WACR,CAAC;UAEDpC,OAAO,CAACqC,GAAG,CAACC,KAAK,CAAC3D,qBAAqB,EAAEsD,UAAU,EAAE,IAAI,CAAC;UAE1D,MAAMV,YAAY,CAACgB,oBAAoB,CAACvC,OAAO,EAAEgC,KAAK,CAACQ,YAAY,CAAC,CAAC,CAAC;UAEtE,OAAO,IAAI,CAACC,OAAO,CAACzC,OAAO,EAAEc,CAAC,EAAEkB,KAAK,CAACE,SAAS,CAACpC,IAAI,EAAE4C,IAAI,CAAC;QAC7D;QAEA,MAAMV,KAAK;MACb;IACF;IAEA,MAAMT,YAAY,CAACoB,oBAAoB,CAAC3C,OAAO,EAAE;MAC/C4C,SAAS,EAAE,IAAI;MACfC,MAAM,EAAE5C,OAAO,CAAC6C,KAAK,CAACD;IACxB,CAA0B,CAAC;;IAE3B;IACA,MAAMtB,YAAY,CAACwB,UAAU,CAAC/C,OAAO,CAAC;IAEtC,OAAO,IAAI,CAACyC,OAAO,CAACzC,OAAO,EAAEc,CAAC,EAAE,IAAI,CAACkC,aAAa,CAAC,CAAC,CAAC;EACvD;EAEA,IAAIC,gBAAgBA,CAAA,EAAyC;IAC3D,OAAO;MACLC,GAAG,EAAE;QACHC,YAAY,EAAE;UACZC,MAAMA,CAACpD,OAAO,EAAEc,CAAC,EAAE;YACjB,OAAOA,CAAC,CAACuC,QAAQ;UACnB;QACF;MACF;IACF,CAAC;EACH;AACF;AAEA,OAAO,eAAetB,UAAUA,CAC9B9B,OAAoB,EACpBqD,QAAsB,EACtBtD,OAA2B,EAC3BuD,gBAAkC,EAClC9D,KAAgB,EAChB+D,YAAoB,EACpB7B,YAA0B,EAC1B;EACA,MAAM8B,kBAAkB,CAACzD,OAAO,EAAEsD,QAAQ,EAAErD,OAAO,CAAC;EAEpD,MAAMyD,UAAU,GAAG3E,eAAe,CAACiB,OAAO,CAACsB,MAAM,CAAC;EAClD,MAAMqC,OAAO,GAAG,CAAC,QAAQ,EAAE,eAAe,CAAC;EAE3C3D,OAAO,CAAC4D,MAAM,CAACC,IAAI,CAACF,OAAO,EAAE,iBAAiB,EAAED,UAAU,CAAC;;EAE3D;EACA,MAAMI,KAAK,GAAGC,qBAAqB,CACjCR,gBAAgB,CAACtD,OAAO,EACxBsD,gBAAgB,CAACS,OACnB,CAAC;;EAED;EACAhE,OAAO,CAAC4D,MAAM,CAACC,IAAI,CAACF,OAAO,EAAE,iBAAiB,CAAC;EAC/C,MAAMM,cAAc,GAAG,MAAMC,UAAU,CACrCzE,KAAK,EACLqE,KAAK,EACLN,YAAY,EACZxD,OAAO,CAACqC,GAAG,CAAC8B,EACd,CAAC;EAED,IAAIF,cAAc,KAAKG,SAAS,EAAE;IAChC,MAAM1F,IAAI,CAAC2F,UAAU,CAAC,2CAA2C,CAAC;EACpE;EAEA,OAAO5E,KAAK,CAACgC,QAAQ,CAAC6C,aAAa,CAACC,MAAM,CACxCtE,OAAO,EACPD,OAAO,EACPP,KAAK,EACL+D,YAAY,EACZM,KAAK,EACLG,cAAc,EACdtC,YACF,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe8B,kBAAkBA,CAC/BzD,OAA2B,EAC3BsD,QAAsB,EACtBrD,OAAoB,EACpB;EACA,MAAMuE,cAAc,GAAGvE,OAAO,CAACwE,aAAa,CAACC,OAAO,CACjD5E,IAAI,IAAKA,IAAI,CAACF,UAAU,CAAC+E,MAC5B,CAAC;EAED,KAAK,MAAMzC,SAAS,IAAIsC,cAAc,EAAE;IACtC;AACJ;AACA;AACA;IACI,MAAMtC,SAAS,CAAC0C,QAAQ,CAAC5E,OAAO,EAAEsD,QAAQ,EAAErD,OAAO,CAAC;EACtD;AACF;AAEA,SAASiE,UAAUA,CACjBzE,KAAgB,EAChBqE,KAAmB,EACnBe,YAAoB,EACpBC,SAAiB,EACjB;EACA,MAAM;IAAEC;EAAsB,CAAC,GAAGtF,KAAK,CAACgC,QAAQ;EAChD,MAAM;IAAE8C;EAAO,CAAC,GAAGQ,qBAAqB;EAExC,MAAM3E,OAAsB,GAAG;IAC7B0E,SAAS;IACTD,YAAY;IAEZ;IACAG,IAAI,EAAElB,KAAK,CACRmB,MAAM,CAAEC,IAAI,IAAK,OAAO,IAAIA,IAAI,CAAC,CACjCC,GAAG,CAAED,IAAI,KAAM;MACd/C,IAAI,EAAE+C,IAAI,CAAC/C,IAAI;MACfiD,KAAK,EAAEF,IAAI,CAACG,KAAK;MACjBC,KAAK,EAAEzG,SAAS,CAACqG,IAAI,CAACK,KAAK,EAAEL,IAAI,CAACpC,KAAK,EAAE;QAAE0C,MAAM,EAAE;MAAO,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEL;IACAC,SAAS,EAAE3B,KAAK,CACbmB,MAAM,CAAEC,IAAI,IAAK,UAAU,IAAIA,IAAI,CAAC,CACpCC,GAAG,CAAED,IAAI,KAAM;MACd/C,IAAI,EAAE+C,IAAI,CAAC/C,IAAI;MACfiD,KAAK,EAAEF,IAAI,CAACG,KAAK;MAEjB;MACAC,KAAK,EAAEJ,IAAI,CAACQ,QAAQ,CAACP,GAAG,CAAEQ,WAAW,IACnCA,WAAW,CAACR,GAAG,CAAES,OAAO,KAAM;QAC5BzD,IAAI,EAAEyD,OAAO,CAACzD,IAAI;QAClBiD,KAAK,EAAEQ,OAAO,CAACP,KAAK;QACpBC,KAAK,EAAEzG,SAAS,CAAC+G,OAAO,CAACL,KAAK,EAAEK,OAAO,CAAC9C,KAAK,EAAE;UAAE0C,MAAM,EAAE;QAAO,CAAC;MACnE,CAAC,CAAC,CACJ;IACF,CAAC,CAAC;EACN,CAAC;EAED,OAAOjB,MAAM,CAACnE,OAAO,CAAC;AACxB;AAEA,OAAO,SAAS2D,qBAAqBA,CAAC9D,OAAoB,EAAE+D,OAAiB,EAAE;EAC7E,OAAO/D,OAAO,CAACwE,aAAa,CACzBU,GAAG,CAAC,CAAC;IAAEU;EAAK,CAAC,KACZ7B,OAAO,CAACU,OAAO,CAAC,CAAC;IAAEZ;EAAM,CAAC,KACxBA,KAAK,CAACmB,MAAM,CAAC,CAAC;IAAEnF;EAAK,CAAC,KAAKA,IAAI,CAAC+F,IAAI,KAAKA,IAAI,CAC/C,CACF,CAAC,CACAC,IAAI,CAAC,CAAC;AACX","ignoreList":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type FormContextRequest } from '~/src/server/plugins/engine/types.js';
|
|
2
2
|
import { type FormRequest } from '~/src/server/routes/types.js';
|
|
3
|
-
export declare function buildFormRequest(request: Omit<FormRequest, 'server'>): FormRequest;
|
|
4
|
-
export declare function buildFormContextRequest(request: Omit<FormContextRequest, 'server'>): FormContextRequest;
|
|
3
|
+
export declare function buildFormRequest(request: Omit<FormRequest, 'server' | 'yar'>): FormRequest;
|
|
4
|
+
export declare function buildFormContextRequest(request: Omit<FormContextRequest, 'server' | 'yar'>): FormContextRequest;
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { server } from "./server.js";
|
|
2
|
+
const mockYar = {
|
|
3
|
+
flash: jest.fn().mockReturnValue([]),
|
|
4
|
+
clear: jest.fn(),
|
|
5
|
+
get: jest.fn(),
|
|
6
|
+
set: jest.fn(),
|
|
7
|
+
reset: jest.fn()
|
|
8
|
+
};
|
|
2
9
|
export function buildFormRequest(request) {
|
|
3
10
|
return {
|
|
4
11
|
...request,
|
|
12
|
+
yar: mockYar,
|
|
5
13
|
server
|
|
6
14
|
};
|
|
7
15
|
}
|
|
8
16
|
export function buildFormContextRequest(request) {
|
|
9
17
|
return {
|
|
10
18
|
...request,
|
|
19
|
+
yar: mockYar,
|
|
11
20
|
server
|
|
12
21
|
};
|
|
13
22
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.js","names":["server","buildFormRequest","request","buildFormContextRequest"],"sources":["../../../../../../src/server/plugins/engine/pageControllers/__stubs__/request.ts"],"sourcesContent":["import { server } from '~/src/server/plugins/engine/pageControllers/__stubs__/server.js'\nimport { type FormContextRequest } from '~/src/server/plugins/engine/types.js'\nimport { type FormRequest } from '~/src/server/routes/types.js'\n\nexport function buildFormRequest(\n request: Omit<FormRequest, 'server'>\n): FormRequest {\n return {\n ...request,\n server\n } as FormRequest\n}\n\nexport function buildFormContextRequest(\n request: Omit<FormContextRequest, 'server'>\n): FormContextRequest {\n return {\n ...request,\n server\n } as FormContextRequest\n}\n"],"mappings":"AAAA,SAASA,MAAM;AAIf,OAAO,
|
|
1
|
+
{"version":3,"file":"request.js","names":["server","mockYar","flash","jest","fn","mockReturnValue","clear","get","set","reset","buildFormRequest","request","yar","buildFormContextRequest"],"sources":["../../../../../../src/server/plugins/engine/pageControllers/__stubs__/request.ts"],"sourcesContent":["import { server } from '~/src/server/plugins/engine/pageControllers/__stubs__/server.js'\nimport { type FormContextRequest } from '~/src/server/plugins/engine/types.js'\nimport { type FormRequest } from '~/src/server/routes/types.js'\n\nconst mockYar = {\n flash: jest.fn().mockReturnValue([]),\n clear: jest.fn(),\n get: jest.fn(),\n set: jest.fn(),\n reset: jest.fn()\n}\n\nexport function buildFormRequest(\n request: Omit<FormRequest, 'server' | 'yar'>\n): FormRequest {\n return {\n ...request,\n yar: mockYar,\n server\n } as unknown as FormRequest\n}\n\nexport function buildFormContextRequest(\n request: Omit<FormContextRequest, 'server' | 'yar'>\n): FormContextRequest {\n return {\n ...request,\n yar: mockYar,\n server\n } as unknown as FormContextRequest\n}\n"],"mappings":"AAAA,SAASA,MAAM;AAIf,MAAMC,OAAO,GAAG;EACdC,KAAK,EAAEC,IAAI,CAACC,EAAE,CAAC,CAAC,CAACC,eAAe,CAAC,EAAE,CAAC;EACpCC,KAAK,EAAEH,IAAI,CAACC,EAAE,CAAC,CAAC;EAChBG,GAAG,EAAEJ,IAAI,CAACC,EAAE,CAAC,CAAC;EACdI,GAAG,EAAEL,IAAI,CAACC,EAAE,CAAC,CAAC;EACdK,KAAK,EAAEN,IAAI,CAACC,EAAE,CAAC;AACjB,CAAC;AAED,OAAO,SAASM,gBAAgBA,CAC9BC,OAA4C,EAC/B;EACb,OAAO;IACL,GAAGA,OAAO;IACVC,GAAG,EAAEX,OAAO;IACZD;EACF,CAAC;AACH;AAEA,OAAO,SAASa,uBAAuBA,CACrCF,OAAmD,EAC/B;EACpB,OAAO;IACL,GAAGA,OAAO;IACVC,GAAG,EAAEX,OAAO;IACZD;EACF,CAAC;AACH","ignoreList":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a component has an invalid state. This is typically only required where state needs
|
|
4
|
+
* to be checked against an external source upon submission of a form. For example: file upload
|
|
5
|
+
* has internal state (file upload IDs) but also external state (files in S3). The internal state
|
|
6
|
+
* is always validated by the engine, but the external state needs validating too.
|
|
7
|
+
*
|
|
8
|
+
* This should be used within a formComponent.onSubmit(...).
|
|
9
|
+
*/
|
|
10
|
+
export declare class InvalidComponentStateError extends Error {
|
|
11
|
+
readonly component: FormComponent;
|
|
12
|
+
readonly userMessage: string;
|
|
13
|
+
constructor(component: FormComponent, userMessage: string);
|
|
14
|
+
getStateKeys(): string[];
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { FileUploadField } from "../components/FileUploadField.js";
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a component has an invalid state. This is typically only required where state needs
|
|
4
|
+
* to be checked against an external source upon submission of a form. For example: file upload
|
|
5
|
+
* has internal state (file upload IDs) but also external state (files in S3). The internal state
|
|
6
|
+
* is always validated by the engine, but the external state needs validating too.
|
|
7
|
+
*
|
|
8
|
+
* This should be used within a formComponent.onSubmit(...).
|
|
9
|
+
*/
|
|
10
|
+
export class InvalidComponentStateError extends Error {
|
|
11
|
+
component;
|
|
12
|
+
userMessage;
|
|
13
|
+
constructor(component, userMessage) {
|
|
14
|
+
const message = `Invalid component state for: ${component.name}`;
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'InvalidComponentStateError';
|
|
17
|
+
this.component = component;
|
|
18
|
+
this.userMessage = userMessage;
|
|
19
|
+
}
|
|
20
|
+
getStateKeys() {
|
|
21
|
+
const extraStateKeys = this.component instanceof FileUploadField ? ['upload'] : [];
|
|
22
|
+
return [this.component.name].concat(extraStateKeys);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","names":["FileUploadField","InvalidComponentStateError","Error","component","userMessage","constructor","message","name","getStateKeys","extraStateKeys","concat"],"sources":["../../../../../src/server/plugins/engine/pageControllers/errors.ts"],"sourcesContent":["import { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\n\n/**\n * Thrown when a component has an invalid state. This is typically only required where state needs\n * to be checked against an external source upon submission of a form. For example: file upload\n * has internal state (file upload IDs) but also external state (files in S3). The internal state\n * is always validated by the engine, but the external state needs validating too.\n *\n * This should be used within a formComponent.onSubmit(...).\n */\nexport class InvalidComponentStateError extends Error {\n public readonly component: FormComponent\n public readonly userMessage: string\n\n constructor(component: FormComponent, userMessage: string) {\n const message = `Invalid component state for: ${component.name}`\n super(message)\n this.name = 'InvalidComponentStateError'\n this.component = component\n this.userMessage = userMessage\n }\n\n getStateKeys() {\n const extraStateKeys =\n this.component instanceof FileUploadField ? ['upload'] : []\n\n return [this.component.name].concat(extraStateKeys)\n }\n}\n"],"mappings":"AAAA,SAASA,eAAe;AAGxB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,0BAA0B,SAASC,KAAK,CAAC;EACpCC,SAAS;EACTC,WAAW;EAE3BC,WAAWA,CAACF,SAAwB,EAAEC,WAAmB,EAAE;IACzD,MAAME,OAAO,GAAG,gCAAgCH,SAAS,CAACI,IAAI,EAAE;IAChE,KAAK,CAACD,OAAO,CAAC;IACd,IAAI,CAACC,IAAI,GAAG,4BAA4B;IACxC,IAAI,CAACJ,SAAS,GAAGA,SAAS;IAC1B,IAAI,CAACC,WAAW,GAAGA,WAAW;EAChC;EAEAI,YAAYA,CAAA,EAAG;IACb,MAAMC,cAAc,GAClB,IAAI,CAACN,SAAS,YAAYH,eAAe,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE;IAE7D,OAAO,CAAC,IAAI,CAACG,SAAS,CAACI,IAAI,CAAC,CAACG,MAAM,CAACD,cAAc,CAAC;EACrD;AACF","ignoreList":[]}
|
|
@@ -51,6 +51,12 @@ export const formsService = async () => {
|
|
|
51
51
|
title: 'Components',
|
|
52
52
|
slug: 'components'
|
|
53
53
|
});
|
|
54
|
+
await loader.addForm('src/server/forms/simple-form.yaml', {
|
|
55
|
+
...metadata,
|
|
56
|
+
id: 'a1b2c3d4-e5f6-7890-abcd-ef0123456789',
|
|
57
|
+
title: 'Simple Form',
|
|
58
|
+
slug: 'simple-form'
|
|
59
|
+
});
|
|
54
60
|
return loader.toFormsService();
|
|
55
61
|
};
|
|
56
62
|
//# sourceMappingURL=localFormsService.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localFormsService.js","names":["config","FileFormService","now","Date","user","id","displayName","author","createdAt","createdBy","updatedAt","updatedBy","metadata","organisation","teamName","teamEmail","submissionGuidance","notificationEmail","get","live","formsService","loader","addForm","title","slug","toFormsService"],"sources":["../../../../../src/server/plugins/engine/services/localFormsService.js"],"sourcesContent":["import { config } from '~/src/config/index.js'\nimport { FileFormService } from '~/src/server/utils/file-form-service.js'\n\n// Create shared form metadata\nconst now = new Date()\nconst user = { id: 'user', displayName: 'Username' }\nconst author = {\n createdAt: now,\n createdBy: user,\n updatedAt: now,\n updatedBy: user\n}\nconst metadata = {\n organisation: 'Defra',\n teamName: 'Team name',\n teamEmail: 'team@defra.gov.uk',\n submissionGuidance: \"Thanks for your submission, we'll be in touch\",\n notificationEmail: config.get('submissionEmailAddress'),\n ...author,\n live: author\n}\n\n/**\n * Return an function rather than the service directly. This is to prevent consumer applications\n * blowing up as they won't have these files on disk. We can defer the execution until when it's\n * needed, i.e. the createServer function of the devtool.\n */\nexport const formsService = async () => {\n // Instantiate the file loader form service\n const loader = new FileFormService()\n\n // Add a Yaml form\n await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.yaml', {\n ...metadata,\n id: '641aeafd-13dd-40fa-9186-001703800efb',\n title: 'Register as a unicorn breeder',\n slug: 'register-as-a-unicorn-breeder'\n })\n\n await loader.addForm('src/server/forms/page-events.yaml', {\n ...metadata,\n id: '511db05e-ebbd-42e8-8270-5fe93f5c9762',\n title: 'Page events demo',\n slug: 'page-events-demo'\n })\n\n await loader.addForm('src/server/forms/components.json', {\n ...metadata,\n id: '6a872d3b-13f9e-804ce3e-4830-5c45fb32',\n title: 'Components',\n slug: 'components'\n })\n\n return loader.toFormsService()\n}\n"],"mappings":"AAAA,SAASA,MAAM;AACf,SAASC,eAAe;;AAExB;AACA,MAAMC,GAAG,GAAG,IAAIC,IAAI,CAAC,CAAC;AACtB,MAAMC,IAAI,GAAG;EAAEC,EAAE,EAAE,MAAM;EAAEC,WAAW,EAAE;AAAW,CAAC;AACpD,MAAMC,MAAM,GAAG;EACbC,SAAS,EAAEN,GAAG;EACdO,SAAS,EAAEL,IAAI;EACfM,SAAS,EAAER,GAAG;EACdS,SAAS,EAAEP;AACb,CAAC;AACD,MAAMQ,QAAQ,GAAG;EACfC,YAAY,EAAE,OAAO;EACrBC,QAAQ,EAAE,WAAW;EACrBC,SAAS,EAAE,mBAAmB;EAC9BC,kBAAkB,EAAE,+CAA+C;EACnEC,iBAAiB,EAAEjB,MAAM,CAACkB,GAAG,CAAC,wBAAwB,CAAC;EACvD,GAAGX,MAAM;EACTY,IAAI,EAAEZ;AACR,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMa,YAAY,GAAG,MAAAA,CAAA,KAAY;EACtC;EACA,MAAMC,MAAM,GAAG,IAAIpB,eAAe,CAAC,CAAC;;EAEpC;EACA,MAAMoB,MAAM,CAACC,OAAO,CAAC,qDAAqD,EAAE;IAC1E,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,+BAA+B;IACtCC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,mCAAmC,EAAE;IACxD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,kBAAkB;IACzBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,kCAAkC,EAAE;IACvD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,YAAY;IACnBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,OAAOH,MAAM,CAACI,cAAc,CAAC,CAAC;AAChC,CAAC","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"localFormsService.js","names":["config","FileFormService","now","Date","user","id","displayName","author","createdAt","createdBy","updatedAt","updatedBy","metadata","organisation","teamName","teamEmail","submissionGuidance","notificationEmail","get","live","formsService","loader","addForm","title","slug","toFormsService"],"sources":["../../../../../src/server/plugins/engine/services/localFormsService.js"],"sourcesContent":["import { config } from '~/src/config/index.js'\nimport { FileFormService } from '~/src/server/utils/file-form-service.js'\n\n// Create shared form metadata\nconst now = new Date()\nconst user = { id: 'user', displayName: 'Username' }\nconst author = {\n createdAt: now,\n createdBy: user,\n updatedAt: now,\n updatedBy: user\n}\nconst metadata = {\n organisation: 'Defra',\n teamName: 'Team name',\n teamEmail: 'team@defra.gov.uk',\n submissionGuidance: \"Thanks for your submission, we'll be in touch\",\n notificationEmail: config.get('submissionEmailAddress'),\n ...author,\n live: author\n}\n\n/**\n * Return an function rather than the service directly. This is to prevent consumer applications\n * blowing up as they won't have these files on disk. We can defer the execution until when it's\n * needed, i.e. the createServer function of the devtool.\n */\nexport const formsService = async () => {\n // Instantiate the file loader form service\n const loader = new FileFormService()\n\n // Add a Yaml form\n await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.yaml', {\n ...metadata,\n id: '641aeafd-13dd-40fa-9186-001703800efb',\n title: 'Register as a unicorn breeder',\n slug: 'register-as-a-unicorn-breeder'\n })\n\n await loader.addForm('src/server/forms/page-events.yaml', {\n ...metadata,\n id: '511db05e-ebbd-42e8-8270-5fe93f5c9762',\n title: 'Page events demo',\n slug: 'page-events-demo'\n })\n\n await loader.addForm('src/server/forms/components.json', {\n ...metadata,\n id: '6a872d3b-13f9e-804ce3e-4830-5c45fb32',\n title: 'Components',\n slug: 'components'\n })\n\n await loader.addForm('src/server/forms/simple-form.yaml', {\n ...metadata,\n id: 'a1b2c3d4-e5f6-7890-abcd-ef0123456789',\n title: 'Simple Form',\n slug: 'simple-form'\n })\n\n return loader.toFormsService()\n}\n"],"mappings":"AAAA,SAASA,MAAM;AACf,SAASC,eAAe;;AAExB;AACA,MAAMC,GAAG,GAAG,IAAIC,IAAI,CAAC,CAAC;AACtB,MAAMC,IAAI,GAAG;EAAEC,EAAE,EAAE,MAAM;EAAEC,WAAW,EAAE;AAAW,CAAC;AACpD,MAAMC,MAAM,GAAG;EACbC,SAAS,EAAEN,GAAG;EACdO,SAAS,EAAEL,IAAI;EACfM,SAAS,EAAER,GAAG;EACdS,SAAS,EAAEP;AACb,CAAC;AACD,MAAMQ,QAAQ,GAAG;EACfC,YAAY,EAAE,OAAO;EACrBC,QAAQ,EAAE,WAAW;EACrBC,SAAS,EAAE,mBAAmB;EAC9BC,kBAAkB,EAAE,+CAA+C;EACnEC,iBAAiB,EAAEjB,MAAM,CAACkB,GAAG,CAAC,wBAAwB,CAAC;EACvD,GAAGX,MAAM;EACTY,IAAI,EAAEZ;AACR,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMa,YAAY,GAAG,MAAAA,CAAA,KAAY;EACtC;EACA,MAAMC,MAAM,GAAG,IAAIpB,eAAe,CAAC,CAAC;;EAEpC;EACA,MAAMoB,MAAM,CAACC,OAAO,CAAC,qDAAqD,EAAE;IAC1E,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,+BAA+B;IACtCC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,mCAAmC,EAAE;IACxD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,kBAAkB;IACzBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,kCAAkC,EAAE;IACvD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,YAAY;IACnBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,mCAAmC,EAAE;IACxD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,aAAa;IACpBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,OAAOH,MAAM,CAACI,cAAc,CAAC,CAAC;AAChC,CAAC","ignoreList":[]}
|
|
@@ -79,6 +79,10 @@ describe('Nunjucks context', () => {
|
|
|
79
79
|
path: '/test',
|
|
80
80
|
url: {
|
|
81
81
|
search: ''
|
|
82
|
+
},
|
|
83
|
+
yar: {
|
|
84
|
+
flash: jest.fn().mockReturnValue([]),
|
|
85
|
+
commit: jest.fn()
|
|
82
86
|
}
|
|
83
87
|
// state intentionally omitted to test real malformed requests
|
|
84
88
|
};
|
|
@@ -112,7 +116,11 @@ describe('Nunjucks context', () => {
|
|
|
112
116
|
url: {
|
|
113
117
|
search: ''
|
|
114
118
|
},
|
|
115
|
-
state: {}
|
|
119
|
+
state: {},
|
|
120
|
+
yar: {
|
|
121
|
+
flash: jest.fn().mockReturnValue([]),
|
|
122
|
+
commit: jest.fn()
|
|
123
|
+
}
|
|
116
124
|
};
|
|
117
125
|
const {
|
|
118
126
|
crumb
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.test.js","names":["tmpdir","context","devtoolContext","describe","beforeEach","jest","resetModules","it","assetPath","expect","toBe","getDxtAssetPath","isolateModulesAsync","config","set","rejects","toThrow","malformedRequest","server","plugins","crumb","generate","fn","baseLayoutPath","route","settings","path","url","search","toBeUndefined","not","toHaveBeenCalled","mockCrumb","validRequest","
|
|
1
|
+
{"version":3,"file":"context.test.js","names":["tmpdir","context","devtoolContext","describe","beforeEach","jest","resetModules","it","assetPath","expect","toBe","getDxtAssetPath","isolateModulesAsync","config","set","rejects","toThrow","malformedRequest","server","plugins","crumb","generate","fn","baseLayoutPath","route","settings","path","url","search","yar","flash","mockReturnValue","commit","toBeUndefined","not","toHaveBeenCalled","mockCrumb","validRequest","state","toHaveBeenCalledWith"],"sources":["../../../../src/server/plugins/nunjucks/context.test.js"],"sourcesContent":["import { tmpdir } from 'node:os'\n\nimport {\n context,\n devtoolContext\n} from '~/src/server/plugins/nunjucks/context.js'\n\ndescribe('Nunjucks context', () => {\n beforeEach(() => jest.resetModules())\n\n describe('Asset path', () => {\n it(\"should include 'assetPath' for GOV.UK Frontend icons\", () => {\n const { assetPath } = devtoolContext(null)\n expect(assetPath).toBe('/assets')\n })\n })\n\n describe('Asset helper', () => {\n it(\"should locate 'assets-manifest.json' assets\", () => {\n const { getDxtAssetPath } = devtoolContext(null)\n\n expect(getDxtAssetPath('example.scss')).toBe(\n '/stylesheets/example.xxxxxxx.min.css'\n )\n\n expect(getDxtAssetPath('example.mjs')).toBe(\n '/javascripts/example.xxxxxxx.min.js'\n )\n })\n\n it(\"should return path when 'assets-manifest.json' is missing\", async () => {\n await jest.isolateModulesAsync(async () => {\n const { config } = await import('~/src/config/index.js')\n\n // Import when isolated to avoid cache\n const { devtoolContext } = await import(\n '~/src/server/plugins/nunjucks/context.js'\n )\n\n // Update config for missing manifest\n config.set('publicDir', tmpdir())\n const { getDxtAssetPath } = devtoolContext(null)\n\n // Uses original paths when missing\n expect(getDxtAssetPath('example.scss')).toBe('/example.scss')\n expect(getDxtAssetPath('example.mjs')).toBe('/example.mjs')\n })\n })\n\n it('should return path to unknown assets', () => {\n const { getDxtAssetPath } = devtoolContext(null)\n\n expect(getDxtAssetPath('')).toBe('/')\n expect(getDxtAssetPath('example.jpg')).toBe('/example.jpg')\n expect(getDxtAssetPath('example.gif')).toBe('/example.gif')\n })\n })\n\n describe('Config', () => {\n it('should include environment, phase tag and service info', async () => {\n await expect(context(null)).rejects.toThrow(\n 'context called before plugin registered'\n )\n })\n })\n\n describe('Crumb', () => {\n it('should handle malformed requests with missing state', async () => {\n // While state should always exist in a valid Hapi request (it holds cookies),\n // we've seen malformed requests in production where it's missing\n const malformedRequest = /** @type {FormRequest} */ (\n /** @type {unknown} */ ({\n server: {\n plugins: {\n crumb: {\n generate: jest.fn()\n },\n 'forms-engine-plugin': {\n baseLayoutPath: 'randomValue'\n }\n }\n },\n plugins: {},\n route: {\n settings: {\n plugins: {}\n }\n },\n path: '/test',\n url: { search: '' },\n yar: {\n flash: jest.fn().mockReturnValue([]),\n commit: jest.fn()\n }\n // state intentionally omitted to test real malformed requests\n })\n )\n\n const { crumb } = await context(malformedRequest)\n expect(crumb).toBeUndefined()\n expect(\n malformedRequest.server.plugins.crumb.generate\n ).not.toHaveBeenCalled()\n })\n\n it('should generate crumb when state exists', async () => {\n const mockCrumb = 'generated-crumb-value'\n const validRequest = /** @type {FormRequest} */ (\n /** @type {unknown} */ ({\n server: {\n plugins: {\n crumb: {\n generate: jest.fn().mockReturnValue(mockCrumb)\n },\n 'forms-engine-plugin': {\n baseLayoutPath: 'randomValue'\n }\n }\n },\n plugins: {},\n route: {\n settings: {\n plugins: {}\n }\n },\n path: '/test',\n url: { search: '' },\n state: {},\n yar: {\n flash: jest.fn().mockReturnValue([]),\n commit: jest.fn()\n }\n })\n )\n\n const { crumb } = await context(validRequest)\n expect(crumb).toBe(mockCrumb)\n expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith(\n validRequest\n )\n })\n })\n})\n\n/**\n * @import { FormRequest } from '~/src/server/routes/types.js'\n */\n"],"mappings":"AAAA,SAASA,MAAM,QAAQ,SAAS;AAEhC,SACEC,OAAO,EACPC,cAAc;AAGhBC,QAAQ,CAAC,kBAAkB,EAAE,MAAM;EACjCC,UAAU,CAAC,MAAMC,IAAI,CAACC,YAAY,CAAC,CAAC,CAAC;EAErCH,QAAQ,CAAC,YAAY,EAAE,MAAM;IAC3BI,EAAE,CAAC,sDAAsD,EAAE,MAAM;MAC/D,MAAM;QAAEC;MAAU,CAAC,GAAGN,cAAc,CAAC,IAAI,CAAC;MAC1CO,MAAM,CAACD,SAAS,CAAC,CAACE,IAAI,CAAC,SAAS,CAAC;IACnC,CAAC,CAAC;EACJ,CAAC,CAAC;EAEFP,QAAQ,CAAC,cAAc,EAAE,MAAM;IAC7BI,EAAE,CAAC,6CAA6C,EAAE,MAAM;MACtD,MAAM;QAAEI;MAAgB,CAAC,GAAGT,cAAc,CAAC,IAAI,CAAC;MAEhDO,MAAM,CAACE,eAAe,CAAC,cAAc,CAAC,CAAC,CAACD,IAAI,CAC1C,sCACF,CAAC;MAEDD,MAAM,CAACE,eAAe,CAAC,aAAa,CAAC,CAAC,CAACD,IAAI,CACzC,qCACF,CAAC;IACH,CAAC,CAAC;IAEFH,EAAE,CAAC,2DAA2D,EAAE,YAAY;MAC1E,MAAMF,IAAI,CAACO,mBAAmB,CAAC,YAAY;QACzC,MAAM;UAAEC;QAAO,CAAC,GAAG,MAAM,MAAM,2BAAwB,CAAC;;QAExD;QACA,MAAM;UAAEX;QAAe,CAAC,GAAG,MAAM,MAAM,eAEvC,CAAC;;QAED;QACAW,MAAM,CAACC,GAAG,CAAC,WAAW,EAAEd,MAAM,CAAC,CAAC,CAAC;QACjC,MAAM;UAAEW;QAAgB,CAAC,GAAGT,cAAc,CAAC,IAAI,CAAC;;QAEhD;QACAO,MAAM,CAACE,eAAe,CAAC,cAAc,CAAC,CAAC,CAACD,IAAI,CAAC,eAAe,CAAC;QAC7DD,MAAM,CAACE,eAAe,CAAC,aAAa,CAAC,CAAC,CAACD,IAAI,CAAC,cAAc,CAAC;MAC7D,CAAC,CAAC;IACJ,CAAC,CAAC;IAEFH,EAAE,CAAC,sCAAsC,EAAE,MAAM;MAC/C,MAAM;QAAEI;MAAgB,CAAC,GAAGT,cAAc,CAAC,IAAI,CAAC;MAEhDO,MAAM,CAACE,eAAe,CAAC,EAAE,CAAC,CAAC,CAACD,IAAI,CAAC,GAAG,CAAC;MACrCD,MAAM,CAACE,eAAe,CAAC,aAAa,CAAC,CAAC,CAACD,IAAI,CAAC,cAAc,CAAC;MAC3DD,MAAM,CAACE,eAAe,CAAC,aAAa,CAAC,CAAC,CAACD,IAAI,CAAC,cAAc,CAAC;IAC7D,CAAC,CAAC;EACJ,CAAC,CAAC;EAEFP,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACvBI,EAAE,CAAC,wDAAwD,EAAE,YAAY;MACvE,MAAME,MAAM,CAACR,OAAO,CAAC,IAAI,CAAC,CAAC,CAACc,OAAO,CAACC,OAAO,CACzC,yCACF,CAAC;IACH,CAAC,CAAC;EACJ,CAAC,CAAC;EAEFb,QAAQ,CAAC,OAAO,EAAE,MAAM;IACtBI,EAAE,CAAC,qDAAqD,EAAE,YAAY;MACpE;MACA;MACA,MAAMU,gBAAgB,GAAG;MACvB,sBAAwB;QACtBC,MAAM,EAAE;UACNC,OAAO,EAAE;YACPC,KAAK,EAAE;cACLC,QAAQ,EAAEhB,IAAI,CAACiB,EAAE,CAAC;YACpB,CAAC;YACD,qBAAqB,EAAE;cACrBC,cAAc,EAAE;YAClB;UACF;QACF,CAAC;QACDJ,OAAO,EAAE,CAAC,CAAC;QACXK,KAAK,EAAE;UACLC,QAAQ,EAAE;YACRN,OAAO,EAAE,CAAC;UACZ;QACF,CAAC;QACDO,IAAI,EAAE,OAAO;QACbC,GAAG,EAAE;UAAEC,MAAM,EAAE;QAAG,CAAC;QACnBC,GAAG,EAAE;UACHC,KAAK,EAAEzB,IAAI,CAACiB,EAAE,CAAC,CAAC,CAACS,eAAe,CAAC,EAAE,CAAC;UACpCC,MAAM,EAAE3B,IAAI,CAACiB,EAAE,CAAC;QAClB;QACA;MACF,CACD;MAED,MAAM;QAAEF;MAAM,CAAC,GAAG,MAAMnB,OAAO,CAACgB,gBAAgB,CAAC;MACjDR,MAAM,CAACW,KAAK,CAAC,CAACa,aAAa,CAAC,CAAC;MAC7BxB,MAAM,CACJQ,gBAAgB,CAACC,MAAM,CAACC,OAAO,CAACC,KAAK,CAACC,QACxC,CAAC,CAACa,GAAG,CAACC,gBAAgB,CAAC,CAAC;IAC1B,CAAC,CAAC;IAEF5B,EAAE,CAAC,yCAAyC,EAAE,YAAY;MACxD,MAAM6B,SAAS,GAAG,uBAAuB;MACzC,MAAMC,YAAY,GAAG;MACnB,sBAAwB;QACtBnB,MAAM,EAAE;UACNC,OAAO,EAAE;YACPC,KAAK,EAAE;cACLC,QAAQ,EAAEhB,IAAI,CAACiB,EAAE,CAAC,CAAC,CAACS,eAAe,CAACK,SAAS;YAC/C,CAAC;YACD,qBAAqB,EAAE;cACrBb,cAAc,EAAE;YAClB;UACF;QACF,CAAC;QACDJ,OAAO,EAAE,CAAC,CAAC;QACXK,KAAK,EAAE;UACLC,QAAQ,EAAE;YACRN,OAAO,EAAE,CAAC;UACZ;QACF,CAAC;QACDO,IAAI,EAAE,OAAO;QACbC,GAAG,EAAE;UAAEC,MAAM,EAAE;QAAG,CAAC;QACnBU,KAAK,EAAE,CAAC,CAAC;QACTT,GAAG,EAAE;UACHC,KAAK,EAAEzB,IAAI,CAACiB,EAAE,CAAC,CAAC,CAACS,eAAe,CAAC,EAAE,CAAC;UACpCC,MAAM,EAAE3B,IAAI,CAACiB,EAAE,CAAC;QAClB;MACF,CACD;MAED,MAAM;QAAEF;MAAM,CAAC,GAAG,MAAMnB,OAAO,CAACoC,YAAY,CAAC;MAC7C5B,MAAM,CAACW,KAAK,CAAC,CAACV,IAAI,CAAC0B,SAAS,CAAC;MAC7B3B,MAAM,CAAC4B,YAAY,CAACnB,MAAM,CAACC,OAAO,CAACC,KAAK,CAACC,QAAQ,CAAC,CAACkB,oBAAoB,CACrEF,YACF,CAAC;IACH,CAAC,CAAC;EACJ,CAAC,CAAC;AACJ,CAAC,CAAC;;AAEF;AACA;AACA","ignoreList":[]}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* @property {string} [currentPath] - Current path
|
|
18
18
|
* @property {string} [previewMode] - Preview mode
|
|
19
19
|
* @property {string} [slug] - Form slug
|
|
20
|
+
* @property {string} [error] - Error message for temporary error messages (not related to form state)
|
|
20
21
|
* @property {FormContext} [context] - the current form context
|
|
21
22
|
*/
|
|
22
23
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/server/plugins/nunjucks/types.js"],"sourcesContent":["/**\n * @typedef {object} MacroOptions\n * @property {string} [callBlock] - Nunjucks call block content\n * @property {object} [params] - Nunjucks macro params\n */\n\n/**\n * @typedef {object} RenderOptions\n * @property {object} [context] - Nunjucks render context\n */\n\n/**\n * @typedef {object} ViewContext - Nunjucks view context\n * @property {string} [baseLayoutPath] - Base layout path\n * @property {string} [crumb] - Cross-Site Request Forgery (CSRF) token\n * @property {string} [cspNonce] - Content Security Policy (CSP) nonce\n * @property {string} [currentPath] - Current path\n * @property {string} [previewMode] - Preview mode\n * @property {string} [slug] - Form slug\n * @property {FormContext} [context] - the current form context\n */\n\n/**\n * @typedef NunjucksContext\n * @property {ViewContext} ctx - the current nunjucks view context\n */\n\n/**\n * @import { FormContext } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/server/plugins/nunjucks/types.js"],"sourcesContent":["/**\n * @typedef {object} MacroOptions\n * @property {string} [callBlock] - Nunjucks call block content\n * @property {object} [params] - Nunjucks macro params\n */\n\n/**\n * @typedef {object} RenderOptions\n * @property {object} [context] - Nunjucks render context\n */\n\n/**\n * @typedef {object} ViewContext - Nunjucks view context\n * @property {string} [baseLayoutPath] - Base layout path\n * @property {string} [crumb] - Cross-Site Request Forgery (CSRF) token\n * @property {string} [cspNonce] - Content Security Policy (CSP) nonce\n * @property {string} [currentPath] - Current path\n * @property {string} [previewMode] - Preview mode\n * @property {string} [slug] - Form slug\n * @property {string} [error] - Error message for temporary error messages (not related to form state)\n * @property {FormContext} [context] - the current form context\n */\n\n/**\n * @typedef NunjucksContext\n * @property {ViewContext} ctx - the current nunjucks view context\n */\n\n/**\n * @import { FormContext } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA","ignoreList":[]}
|
|
@@ -27,6 +27,7 @@ export declare class CacheService {
|
|
|
27
27
|
setFlash(request: AnyFormRequest, message: {
|
|
28
28
|
errors: FormSubmissionError[];
|
|
29
29
|
}): void;
|
|
30
|
+
resetComponentStates(request: AnyFormRequest, componentNames: string[]): Promise<FormSubmissionState>;
|
|
30
31
|
/**
|
|
31
32
|
* The key used to store user session data against.
|
|
32
33
|
* If there are multiple forms on the same runner instance, for example `form-a` and `form-a-feedback` this will prevent CacheService from clearing data from `form-a` if a user gave feedback before they finished `form-a`
|
|
@@ -61,6 +61,16 @@ export class CacheService {
|
|
|
61
61
|
const key = this.Key(request);
|
|
62
62
|
request.yar.flash(key.id, message);
|
|
63
63
|
}
|
|
64
|
+
async resetComponentStates(request, componentNames) {
|
|
65
|
+
const state = await this.getState(request);
|
|
66
|
+
for (const componentName of componentNames) {
|
|
67
|
+
if (componentName in state) {
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
69
|
+
delete state[componentName];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return this.setState(request, state);
|
|
73
|
+
}
|
|
64
74
|
|
|
65
75
|
/**
|
|
66
76
|
* The key used to store user session data against.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cacheService.js","names":["Hoek","config","partition","ADDITIONAL_IDENTIFIER","CacheService","cache","logger","constructor","server","cacheName","log","segment","getState","request","key","Key","cached","get","setState","state","ttl","set","getConfirmationState","Confirmation","value","setConfirmationState","confirmationState","clearState","yar","id","drop","getFlash","messages","flash","Array","isArray","length","at","setFlash","message","additionalIdentifier","Error","params","slug","merge","update","mergeArrays"],"sources":["../../../src/server/services/cacheService.ts"],"sourcesContent":["import { type Server } from '@hapi/hapi'\nimport * as Hoek from '@hapi/hoek'\n\nimport { config } from '~/src/config/index.js'\nimport { type createServer } from '~/src/server/index.js'\nimport {\n type AnyFormRequest,\n type AnyRequest,\n type FormConfirmationState,\n type FormPayload,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nconst partition = 'cache'\n\nexport enum ADDITIONAL_IDENTIFIER {\n Confirmation = ':confirmation'\n}\n\nexport class CacheService {\n /**\n * This service is responsible for getting, storing or deleting a user's session data in the cache. This service has been registered by {@link createServer}\n */\n cache\n logger: Server['logger']\n\n constructor({ server, cacheName }: { server: Server; cacheName?: string }) {\n if (!cacheName) {\n server.log(\n 'warn',\n 'You are using the default hapi cache. Please provide a cache name in plugin registration options.'\n )\n }\n\n this.cache = server.cache({ cache: cacheName, segment: 'formSubmission' })\n this.logger = server.logger\n }\n\n async getState(request: AnyRequest): Promise<FormSubmissionState> {\n const key = this.Key(request)\n const cached = await this.cache.get(key)\n\n return cached ?? {}\n }\n\n async setState(request: AnyFormRequest, state: FormSubmissionState) {\n const key = this.Key(request)\n const ttl = config.get('sessionTimeout')\n\n await this.cache.set(key, state, ttl)\n\n return this.getState(request)\n }\n\n async getConfirmationState(\n request: AnyFormRequest\n ): Promise<FormConfirmationState> {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const value = await this.cache.get(key)\n\n return value ?? {}\n }\n\n async setConfirmationState(\n request: AnyFormRequest,\n confirmationState: FormConfirmationState\n ) {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const ttl = config.get('confirmationSessionTimeout')\n\n return this.cache.set(key, confirmationState, ttl)\n }\n\n async clearState(request: AnyFormRequest) {\n if (request.yar.id) {\n await this.cache.drop(this.Key(request))\n }\n }\n\n getFlash(\n request: AnyFormRequest\n ): { errors: FormSubmissionError[] } | undefined {\n const key = this.Key(request)\n const messages = request.yar.flash(key.id)\n\n if (Array.isArray(messages) && messages.length) {\n return messages.at(0)\n }\n }\n\n setFlash(\n request: AnyFormRequest,\n message: { errors: FormSubmissionError[] }\n ) {\n const key = this.Key(request)\n\n request.yar.flash(key.id, message)\n }\n\n /**\n * The key used to store user session data against.\n * If there are multiple forms on the same runner instance, for example `form-a` and `form-a-feedback` this will prevent CacheService from clearing data from `form-a` if a user gave feedback before they finished `form-a`\n * @param request - hapi request object\n * @param additionalIdentifier - appended to the id\n */\n Key(request: AnyRequest, additionalIdentifier?: ADDITIONAL_IDENTIFIER) {\n if (!request.yar.id) {\n throw new Error('No session ID found')\n }\n\n const state = (request.params.state as string) || ''\n const slug = (request.params.slug as string) || ''\n const key = `${request.yar.id}:${state}:${slug}:`\n\n return {\n segment: partition,\n id: `${key}${additionalIdentifier ?? ''}`\n }\n }\n}\n\n/**\n * State merge helper\n * 1. Merges objects (form fields)\n * 2. Overwrites arrays\n */\nexport function merge<StateType extends FormState | FormPayload>(\n state: StateType,\n update: object\n): StateType {\n return Hoek.merge(state, update, {\n mergeArrays: false\n })\n}\n"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"cacheService.js","names":["Hoek","config","partition","ADDITIONAL_IDENTIFIER","CacheService","cache","logger","constructor","server","cacheName","log","segment","getState","request","key","Key","cached","get","setState","state","ttl","set","getConfirmationState","Confirmation","value","setConfirmationState","confirmationState","clearState","yar","id","drop","getFlash","messages","flash","Array","isArray","length","at","setFlash","message","resetComponentStates","componentNames","componentName","additionalIdentifier","Error","params","slug","merge","update","mergeArrays"],"sources":["../../../src/server/services/cacheService.ts"],"sourcesContent":["import { type Server } from '@hapi/hapi'\nimport * as Hoek from '@hapi/hoek'\n\nimport { config } from '~/src/config/index.js'\nimport { type createServer } from '~/src/server/index.js'\nimport {\n type AnyFormRequest,\n type AnyRequest,\n type FormConfirmationState,\n type FormPayload,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nconst partition = 'cache'\n\nexport enum ADDITIONAL_IDENTIFIER {\n Confirmation = ':confirmation'\n}\n\nexport class CacheService {\n /**\n * This service is responsible for getting, storing or deleting a user's session data in the cache. This service has been registered by {@link createServer}\n */\n cache\n logger: Server['logger']\n\n constructor({ server, cacheName }: { server: Server; cacheName?: string }) {\n if (!cacheName) {\n server.log(\n 'warn',\n 'You are using the default hapi cache. Please provide a cache name in plugin registration options.'\n )\n }\n\n this.cache = server.cache({ cache: cacheName, segment: 'formSubmission' })\n this.logger = server.logger\n }\n\n async getState(request: AnyRequest): Promise<FormSubmissionState> {\n const key = this.Key(request)\n const cached = await this.cache.get(key)\n\n return cached ?? {}\n }\n\n async setState(request: AnyFormRequest, state: FormSubmissionState) {\n const key = this.Key(request)\n const ttl = config.get('sessionTimeout')\n\n await this.cache.set(key, state, ttl)\n\n return this.getState(request)\n }\n\n async getConfirmationState(\n request: AnyFormRequest\n ): Promise<FormConfirmationState> {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const value = await this.cache.get(key)\n\n return value ?? {}\n }\n\n async setConfirmationState(\n request: AnyFormRequest,\n confirmationState: FormConfirmationState\n ) {\n const key = this.Key(request, ADDITIONAL_IDENTIFIER.Confirmation)\n const ttl = config.get('confirmationSessionTimeout')\n\n return this.cache.set(key, confirmationState, ttl)\n }\n\n async clearState(request: AnyFormRequest) {\n if (request.yar.id) {\n await this.cache.drop(this.Key(request))\n }\n }\n\n getFlash(\n request: AnyFormRequest\n ): { errors: FormSubmissionError[] } | undefined {\n const key = this.Key(request)\n const messages = request.yar.flash(key.id)\n\n if (Array.isArray(messages) && messages.length) {\n return messages.at(0)\n }\n }\n\n setFlash(\n request: AnyFormRequest,\n message: { errors: FormSubmissionError[] }\n ) {\n const key = this.Key(request)\n\n request.yar.flash(key.id, message)\n }\n\n async resetComponentStates(\n request: AnyFormRequest,\n componentNames: string[]\n ) {\n const state = await this.getState(request)\n\n for (const componentName of componentNames) {\n if (componentName in state) {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete state[componentName]\n }\n }\n\n return this.setState(request, state)\n }\n\n /**\n * The key used to store user session data against.\n * If there are multiple forms on the same runner instance, for example `form-a` and `form-a-feedback` this will prevent CacheService from clearing data from `form-a` if a user gave feedback before they finished `form-a`\n * @param request - hapi request object\n * @param additionalIdentifier - appended to the id\n */\n Key(request: AnyRequest, additionalIdentifier?: ADDITIONAL_IDENTIFIER) {\n if (!request.yar.id) {\n throw new Error('No session ID found')\n }\n\n const state = (request.params.state as string) || ''\n const slug = (request.params.slug as string) || ''\n const key = `${request.yar.id}:${state}:${slug}:`\n\n return {\n segment: partition,\n id: `${key}${additionalIdentifier ?? ''}`\n }\n }\n}\n\n/**\n * State merge helper\n * 1. Merges objects (form fields)\n * 2. Overwrites arrays\n */\nexport function merge<StateType extends FormState | FormPayload>(\n state: StateType,\n update: object\n): StateType {\n return Hoek.merge(state, update, {\n mergeArrays: false\n })\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport {\n type EXTERNAL_STATE_APPENDAGE,\n type EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type AnyFormRequest,\n type FormSubmissionError,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.ts'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/yar' {\n interface YarFlashes {\n [EXTERNAL_STATE_APPENDAGE]: object\n [EXTERNAL_STATE_PAYLOAD]: object\n [key: string]: { errors: FormSubmissionError[] }\n }\n}\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: AnyRequest) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: AnyFormRequest | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n saveAndExit?: PluginOptions['saveAndExit']\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.d.js","names":[],"sources":["../../../src/typings/hapi/index.d.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unified-signatures */\n\nimport { type Plugin } from '@hapi/hapi'\nimport { type ServerYar, type Yar } from '@hapi/yar'\nimport { type Logger } from 'pino'\n\nimport {\n type COMPONENT_STATE_ERROR,\n type EXTERNAL_STATE_APPENDAGE,\n type EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type AnyFormRequest,\n type FormSubmissionError,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.ts'\nimport { type CacheService } from '~/src/server/services/index.js'\n\ndeclare module '@hapi/yar' {\n interface YarFlashes {\n [EXTERNAL_STATE_APPENDAGE]: object\n [EXTERNAL_STATE_PAYLOAD]: object\n [COMPONENT_STATE_ERROR]: string\n [key: string]: { errors: FormSubmissionError[] }\n }\n}\n\ndeclare module '@hapi/hapi' {\n // Here we are decorating Hapi interface types with\n // props from plugins which doesn't export @types\n interface PluginProperties {\n crumb: {\n generate?: (request: AnyRequest) => string\n }\n 'forms-engine-plugin': {\n baseLayoutPath: string\n cacheService: CacheService\n viewContext?: (\n request: AnyFormRequest | null\n ) => Record<string, unknown> | Promise<Record<string, unknown>>\n saveAndExit?: PluginOptions['saveAndExit']\n }\n }\n\n interface Request {\n logger: Logger\n yar: Yar\n }\n\n interface RequestApplicationState {\n model?: FormModel\n }\n\n interface Server {\n logger: Logger\n yar: ServerYar\n }\n\n interface ServerApplicationState {\n model?: FormModel\n models: Map<string, { model: FormModel; updatedAt: Date }>\n }\n}\n\ndeclare module 'blankie' {\n declare const blankie: {\n plugin: Plugin<Record<string, boolean | string | string[]>>\n }\n\n export = blankie\n}\n\ndeclare module 'blipp' {\n declare const blipp: {\n plugin: Plugin\n }\n\n export = blipp\n}\n\ndeclare module 'hapi-pulse' {\n declare const hapiPulse: {\n plugin: Plugin<{\n timeout: number\n }>\n }\n\n export = hapiPulse\n}\n"],"mappings":"","ignoreList":[]}
|
package/package.json
CHANGED
package/src/server/constants.js
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Page events
|
|
3
|
+
engine: V2
|
|
4
|
+
schema: 2
|
|
5
|
+
startPage: '/summary'
|
|
6
|
+
pages:
|
|
7
|
+
- title: Your name
|
|
8
|
+
path: '/your-name'
|
|
9
|
+
components:
|
|
10
|
+
- type: TextField
|
|
11
|
+
title: What is your first name?
|
|
12
|
+
name: applicantFirstName
|
|
13
|
+
shortDescription: Your first name
|
|
14
|
+
hint: ''
|
|
15
|
+
options:
|
|
16
|
+
required: true
|
|
17
|
+
schema: {}
|
|
18
|
+
id: 1fb8e182-c709-4792-8f83-e01d8b1fee1a
|
|
19
|
+
- type: TextField
|
|
20
|
+
title: What is your last name?
|
|
21
|
+
name: applicantLastName
|
|
22
|
+
shortDescription: Your last name
|
|
23
|
+
hint: ''
|
|
24
|
+
options:
|
|
25
|
+
required: true
|
|
26
|
+
schema: {}
|
|
27
|
+
id: b68df7f1-d4f4-4c17-83c8-402f584906c9
|
|
28
|
+
next: []
|
|
29
|
+
id: 622a35ec-3795-418a-81f3-a45746959045
|
|
30
|
+
- title: Upload a copy of your passport
|
|
31
|
+
controller: FileUploadPageController
|
|
32
|
+
path: '/upload-passport'
|
|
33
|
+
components:
|
|
34
|
+
- type: FileUploadField
|
|
35
|
+
title: Please upload a copy of your passport
|
|
36
|
+
name: passportUpload
|
|
37
|
+
shortDescription: Upload passport
|
|
38
|
+
hint: ''
|
|
39
|
+
options:
|
|
40
|
+
required: true
|
|
41
|
+
schema: {}
|
|
42
|
+
id: 987c1234-56d7-89e0-1234-56789abcdef0
|
|
43
|
+
id: 23456789-0abc-def1-2345-67890abcdef1
|
|
44
|
+
- title: ''
|
|
45
|
+
path: '/date-of-birth'
|
|
46
|
+
components:
|
|
47
|
+
- type: DatePartsField
|
|
48
|
+
title: When is {{ applicantFirstName }} {{ applicantLastName }}'s birthday?
|
|
49
|
+
name: dateOfBirth
|
|
50
|
+
shortDescription: Your birthday
|
|
51
|
+
hint: ''
|
|
52
|
+
options:
|
|
53
|
+
required: true
|
|
54
|
+
schema: {}
|
|
55
|
+
id: '00738799-3489-4ab2-a57b-542eecb31bfa'
|
|
56
|
+
next: []
|
|
57
|
+
id: da0fbdb4-a2de-4650-be16-9ba552af135f
|
|
58
|
+
- id: 449a45f6-4541-4a46-91bd-8b8931b07b50
|
|
59
|
+
title: ''
|
|
60
|
+
path: '/summary'
|
|
61
|
+
controller: SummaryPageController
|
|
62
|
+
conditions: []
|
|
63
|
+
sections: []
|
|
64
|
+
lists: []
|
|
@@ -209,11 +209,12 @@ describe('getFormModel helper', () => {
|
|
|
209
209
|
|
|
210
210
|
describe('resolveFormModel helper', () => {
|
|
211
211
|
const slug = 'tb-origin'
|
|
212
|
-
const definition = { pages: []
|
|
212
|
+
const definition = { pages: [] }
|
|
213
213
|
const metadata = {
|
|
214
214
|
id: 'metadata-123',
|
|
215
215
|
live: { updatedAt: new Date('2024-10-15T10:00:00Z') },
|
|
216
|
-
versions: [{ versionNumber: 9 }]
|
|
216
|
+
versions: [{ versionNumber: 9 }],
|
|
217
|
+
notificationEmail: 'enrique.chase@defra.gov.uk'
|
|
217
218
|
}
|
|
218
219
|
let server: Request['server']
|
|
219
220
|
let formModelInstance: { id: string }
|
|
@@ -265,7 +266,7 @@ describe('resolveFormModel helper', () => {
|
|
|
265
266
|
expect(FormModel).toHaveBeenCalledTimes(2)
|
|
266
267
|
expect(mockFormsService.getFormDefinition).toHaveBeenCalledTimes(2)
|
|
267
268
|
expect(mockCheckEmailAddressForLiveFormSubmission).toHaveBeenCalledWith(
|
|
268
|
-
|
|
269
|
+
undefined,
|
|
269
270
|
true
|
|
270
271
|
)
|
|
271
272
|
expect(FormModel).toHaveBeenCalledWith(
|
|
@@ -174,9 +174,10 @@ export async function resolveFormModel(
|
|
|
174
174
|
)
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
checkEmailAddressForLiveFormSubmission(
|
|
178
|
+
metadata.notificationEmail,
|
|
179
|
+
isPreview
|
|
180
|
+
)
|
|
180
181
|
|
|
181
182
|
const routePrefix =
|
|
182
183
|
options.routePrefix ?? server.realm.modifiers.route.prefix
|
|
@@ -41,24 +41,6 @@ describe.each([
|
|
|
41
41
|
deny: ['5', '6', '7', '8']
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
|
-
{
|
|
45
|
-
component: {
|
|
46
|
-
title: 'String list title',
|
|
47
|
-
shortDescription: 'String list',
|
|
48
|
-
name: 'myComponent',
|
|
49
|
-
type: ComponentType.CheckboxesField,
|
|
50
|
-
list: 'listString',
|
|
51
|
-
options: {}
|
|
52
|
-
} satisfies CheckboxesFieldComponent,
|
|
53
|
-
|
|
54
|
-
options: {
|
|
55
|
-
label: 'string list',
|
|
56
|
-
list: listString,
|
|
57
|
-
examples: listStringExamples,
|
|
58
|
-
allow: ['1', '2', '3', '4'],
|
|
59
|
-
deny: ['5', '6', '7', '8']
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
44
|
{
|
|
63
45
|
component: {
|
|
64
46
|
title: 'Number list title',
|
|
@@ -407,5 +389,43 @@ describe.each([
|
|
|
407
389
|
expect(errors.advancedSettingsErrors).toBeEmpty()
|
|
408
390
|
})
|
|
409
391
|
})
|
|
392
|
+
|
|
393
|
+
describe('getDisplayStringFromFormValue', () => {
|
|
394
|
+
it('returns empty string when value is undefined', () => {
|
|
395
|
+
const checkboxField = field as CheckboxesField
|
|
396
|
+
const result = checkboxField.getDisplayStringFromFormValue(undefined)
|
|
397
|
+
expect(result).toBe('')
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
it('returns empty string when value is empty array', () => {
|
|
401
|
+
const checkboxField = field as CheckboxesField
|
|
402
|
+
const result = checkboxField.getDisplayStringFromFormValue([])
|
|
403
|
+
expect(result).toBe('')
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
it.each([...options.examples])(
|
|
407
|
+
'returns text for single selected value',
|
|
408
|
+
(item) => {
|
|
409
|
+
const checkboxField = field as CheckboxesField
|
|
410
|
+
const result = checkboxField.getDisplayStringFromFormValue([
|
|
411
|
+
item.value
|
|
412
|
+
])
|
|
413
|
+
expect(result).toBe(item.text)
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
it('returns comma-separated text for multiple selected values', () => {
|
|
418
|
+
const checkboxField = field as CheckboxesField
|
|
419
|
+
const item1 = options.examples[0]
|
|
420
|
+
const item2 = options.examples[2]
|
|
421
|
+
|
|
422
|
+
const result = checkboxField.getDisplayStringFromFormValue([
|
|
423
|
+
item1.value,
|
|
424
|
+
item2.value
|
|
425
|
+
])
|
|
426
|
+
|
|
427
|
+
expect(result).toBe(`${item1.text}, ${item2.text}`)
|
|
428
|
+
})
|
|
429
|
+
})
|
|
410
430
|
})
|
|
411
431
|
})
|