@defra/forms-engine-plugin 2.1.4 → 2.1.5
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/plugins/engine/outputFormatters/adapter/v1.d.ts +6 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +23 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -0
- package/.server/server/plugins/engine/outputFormatters/human/v1.d.ts +2 -2
- package/.server/server/plugins/engine/outputFormatters/human/v1.js +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/index.d.ts +4 -3
- package/.server/server/plugins/engine/outputFormatters/index.js +4 -0
- package/.server/server/plugins/engine/outputFormatters/index.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v1.d.ts +2 -2
- package/.server/server/plugins/engine/outputFormatters/machine/v1.js +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +4 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +5 -4
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/services/localFormsService.d.ts +1 -1
- package/.server/server/plugins/engine/services/notifyService.d.ts +7 -2
- package/.server/server/plugins/engine/services/notifyService.js +11 -2
- package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
- package/.server/server/plugins/engine/types/index.d.ts +10 -0
- package/.server/server/plugins/engine/types/index.js +4 -0
- package/.server/server/plugins/engine/types/index.js.map +1 -0
- package/.server/server/plugins/engine/types/schema.d.ts +5 -0
- package/.server/server/plugins/engine/types/schema.js +24 -0
- package/.server/server/plugins/engine/types/schema.js.map +1 -0
- package/.server/server/plugins/engine/types.d.ts +37 -1
- package/.server/server/plugins/engine/types.js +4 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/.server/server/plugins/nunjucks/filters/page.d.ts +1 -1
- package/.server/server/types.d.ts +1 -1
- package/.server/server/types.js.map +1 -1
- package/package.json +6 -1
- package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +506 -0
- package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +53 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +6 -2
- package/src/server/plugins/engine/outputFormatters/index.ts +11 -3
- package/src/server/plugins/engine/outputFormatters/machine/v1.ts +6 -2
- package/src/server/plugins/engine/outputFormatters/machine/v2.ts +1 -1
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -4
- package/src/server/plugins/engine/services/notifyService.test.ts +156 -1
- package/src/server/plugins/engine/services/notifyService.ts +24 -3
- package/src/server/plugins/engine/types/index.ts +96 -0
- package/src/server/plugins/engine/types/schema.test.ts +152 -0
- package/src/server/plugins/engine/types/schema.ts +45 -0
- package/src/server/plugins/engine/types.ts +51 -1
- package/src/server/types.ts +2 -1
|
@@ -50,4 +50,8 @@ export let FileStatus = /*#__PURE__*/function (FileStatus) {
|
|
|
50
50
|
FileStatus["pending"] = "pending";
|
|
51
51
|
return FileStatus;
|
|
52
52
|
}({});
|
|
53
|
+
export let FormAdapterSubmissionSchemaVersion = /*#__PURE__*/function (FormAdapterSubmissionSchemaVersion) {
|
|
54
|
+
FormAdapterSubmissionSchemaVersion[FormAdapterSubmissionSchemaVersion["V1"] = 1] = "V1";
|
|
55
|
+
return FormAdapterSubmissionSchemaVersion;
|
|
56
|
+
}({});
|
|
53
57
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":["UploadStatus","FileStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { type PluginProperties, type Request } from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\ntype RequestType = Request | FormRequest | FormRequestPayload\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\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 referenceNumber: string\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport enum UploadStatus {\n initiated = 'initiated',\n pending = 'pending',\n ready = 'ready'\n}\n\nexport enum FileStatus {\n complete = 'complete',\n rejected = 'rejected',\n pending = 'pending'\n}\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndReturn: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: FormRequest | FormRequestPayload,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndReturn?: {\n keyGenerator: (request: RequestType) => string\n sessionHydrator: (request: RequestType) => Promise<FormSubmissionState>\n sessionPersister: (\n state: FormSubmissionState,\n request: RequestType\n ) => Promise<void>\n }\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n"],"mappings":"AAkCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAqGA,WAAYA,YAAY,0BAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA;AAMxB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":["UploadStatus","FileStatus","FormAdapterSubmissionSchemaVersion"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { type PluginProperties, type Request } from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type RichFormValue } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\ntype RequestType = Request | FormRequest | FormRequestPayload\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\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 referenceNumber: string\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport enum UploadStatus {\n initiated = 'initiated',\n pending = 'pending',\n ready = 'ready'\n}\n\nexport enum FileStatus {\n complete = 'complete',\n rejected = 'rejected',\n pending = 'pending'\n}\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndReturn: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: FormRequest | FormRequestPayload,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndReturn?: {\n keyGenerator: (request: RequestType) => string\n sessionHydrator: (request: RequestType) => Promise<FormSubmissionState>\n sessionPersister: (\n state: FormSubmissionState,\n request: RequestType\n ) => Promise<void>\n }\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: string\n status: string\n timestamp: string\n}\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, Record<string, string>[]>\n}\n\nexport enum FormAdapterSubmissionSchemaVersion {\n V1 = 1\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAoCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAqGA,WAAYA,YAAY,0BAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA;AAMxB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA;AA0NtB,WAAYC,kCAAkC,0BAAlCA,kCAAkC;EAAlCA,kCAAkC,CAAlCA,kCAAkC;EAAA,OAAlCA,kCAAkC;AAAA","ignoreList":[]}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* @this {NunjucksContext}
|
|
4
4
|
* @param {string} name - The name of the component
|
|
5
5
|
*/
|
|
6
|
-
export function field(this: NunjucksContext, name: string): import("../../engine/components/SelectField.js").SelectField | import("../../engine/components/RadiosField.js").RadiosField | import("../../engine/components/YesNoField.js").YesNoField | import("../../engine/components/CheckboxesField.js").CheckboxesField | import("../../engine/components/DatePartsField.js").DatePartsField | import("../../engine/components/EmailAddressField.js").EmailAddressField | import("../../engine/components/MonthYearField.js").MonthYearField | import("../../engine/components/MultilineTextField.js").MultilineTextField | import("../../engine/components/NumberField.js").NumberField | import("../../engine/components/TelephoneNumberField.js").TelephoneNumberField | import("../../engine/components/
|
|
6
|
+
export function field(this: NunjucksContext, name: string): import("../../engine/components/TextField.js").TextField | import("../../engine/components/SelectField.js").SelectField | import("../../engine/components/RadiosField.js").RadiosField | import("../../engine/components/YesNoField.js").YesNoField | import("../../engine/components/CheckboxesField.js").CheckboxesField | import("../../engine/components/DatePartsField.js").DatePartsField | import("../../engine/components/EmailAddressField.js").EmailAddressField | import("../../engine/components/MonthYearField.js").MonthYearField | import("../../engine/components/MultilineTextField.js").MultilineTextField | import("../../engine/components/NumberField.js").NumberField | import("../../engine/components/TelephoneNumberField.js").TelephoneNumberField | import("../../engine/components/UkAddressField.js").UkAddressField | import("../../engine/components/FileUploadField.js").FileUploadField | import("../../engine/components/Details.js").Details | import("../../engine/components/Html.js").Html | import("../../engine/components/InsetText.js").InsetText | import("../../engine/components/List.js").List | import("../../engine/components/Markdown.js").Markdown | undefined;
|
|
7
7
|
import type { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js';
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* @this {NunjucksContext}
|
|
4
4
|
* @param {string} path - The path of the page
|
|
5
5
|
*/
|
|
6
|
-
export function page(this: NunjucksContext, path: string): import("../../engine/pageControllers/
|
|
6
|
+
export function page(this: NunjucksContext, path: string): import("../../engine/pageControllers/StartPageController.js").StartPageController | import("../../engine/pageControllers/QuestionPageController.js").QuestionPageController | import("../../engine/pageControllers/RepeatPageController.js").RepeatPageController | undefined;
|
|
7
7
|
import type { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js';
|
|
@@ -31,5 +31,5 @@ export interface RouteConfig {
|
|
|
31
31
|
saveAndReturn?: PluginOptions['saveAndReturn'];
|
|
32
32
|
}
|
|
33
33
|
export interface OutputService {
|
|
34
|
-
submit: (context: FormContext, request: FormRequestPayload, model: FormModel, emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload) => Promise<void>;
|
|
34
|
+
submit: (context: FormContext, request: FormRequestPayload, model: FormModel, emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload, formMetadata?: FormMetadata) => Promise<void>;
|
|
35
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type OnRequestCallback,\n type PluginOptions,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n saveAndReturn?: PluginOptions['saveAndReturn']\n}\n\nexport interface OutputService {\n submit: (\n context: FormContext,\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type OnRequestCallback,\n type PluginOptions,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n saveAndReturn?: PluginOptions['saveAndReturn']\n}\n\nexport interface OutputService {\n submit: (\n context: FormContext,\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload,\n formMetadata?: FormMetadata\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.5",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
"import": "./.server/server/plugins/engine/index.js",
|
|
15
15
|
"default": "./.server/server/plugins/engine/index.js"
|
|
16
16
|
},
|
|
17
|
+
"./types": {
|
|
18
|
+
"types": "./.server/server/plugins/engine/types/index.d.ts",
|
|
19
|
+
"import": "./.server/server/plugins/engine/types/index.js",
|
|
20
|
+
"default": "./.server/server/plugins/engine/types/index.js"
|
|
21
|
+
},
|
|
17
22
|
"./shared.js": "./.server/client/javascripts/shared.js",
|
|
18
23
|
"./shared.min.js": "./.public/javascripts/shared.min.js",
|
|
19
24
|
"./shared.min.js.map": "./.public/javascripts/shared.min.js.map",
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import { type FormMetadata } from '@defra/forms-model'
|
|
2
|
+
|
|
3
|
+
import { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'
|
|
4
|
+
import { type Field } from '~/src/server/plugins/engine/components/helpers.js'
|
|
5
|
+
import { FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
6
|
+
import {
|
|
7
|
+
type DetailItem,
|
|
8
|
+
type DetailItemField,
|
|
9
|
+
type DetailItemRepeat
|
|
10
|
+
} from '~/src/server/plugins/engine/models/types.js'
|
|
11
|
+
import { format } from '~/src/server/plugins/engine/outputFormatters/adapter/v1.js'
|
|
12
|
+
import { buildFormContextRequest } from '~/src/server/plugins/engine/pageControllers/__stubs__/request.js'
|
|
13
|
+
import {
|
|
14
|
+
FileStatus,
|
|
15
|
+
FormAdapterSubmissionSchemaVersion,
|
|
16
|
+
UploadStatus,
|
|
17
|
+
type FileState,
|
|
18
|
+
type FormAdapterSubmissionMessagePayload
|
|
19
|
+
} from '~/src/server/plugins/engine/types.js'
|
|
20
|
+
import { FormStatus } from '~/src/server/routes/types.js'
|
|
21
|
+
import definition from '~/test/form/definitions/repeat-mixed.js'
|
|
22
|
+
|
|
23
|
+
const submitResponse = {
|
|
24
|
+
message: 'Submit completed',
|
|
25
|
+
result: {
|
|
26
|
+
files: {
|
|
27
|
+
main: '00000000-0000-0000-0000-000000000000',
|
|
28
|
+
repeaters: {
|
|
29
|
+
pizza: '11111111-1111-1111-1111-111111111111'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const model = new FormModel(definition, {
|
|
36
|
+
basePath: 'test'
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const dummyField: Field = {
|
|
40
|
+
getFormValueFromState: (_) => 'hello world'
|
|
41
|
+
} as Field
|
|
42
|
+
|
|
43
|
+
const itemId1 = 'abc-123'
|
|
44
|
+
const itemId2 = 'xyz-987'
|
|
45
|
+
|
|
46
|
+
const state = {
|
|
47
|
+
$$__referenceNumber: 'foobar',
|
|
48
|
+
orderType: 'delivery',
|
|
49
|
+
pizza: [
|
|
50
|
+
{
|
|
51
|
+
toppings: 'Ham',
|
|
52
|
+
quantity: 2,
|
|
53
|
+
itemId: itemId1
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
toppings: 'Pepperoni',
|
|
57
|
+
quantity: 1,
|
|
58
|
+
itemId: itemId2
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const pageUrl = new URL('http://example.com/repeat/pizza-order/summary')
|
|
64
|
+
|
|
65
|
+
const request = buildFormContextRequest({
|
|
66
|
+
method: 'get',
|
|
67
|
+
url: pageUrl,
|
|
68
|
+
path: pageUrl.pathname,
|
|
69
|
+
params: {
|
|
70
|
+
path: 'pizza-order',
|
|
71
|
+
slug: 'repeat'
|
|
72
|
+
},
|
|
73
|
+
query: {},
|
|
74
|
+
app: { model }
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const context = model.getFormContext(request, state)
|
|
78
|
+
|
|
79
|
+
const testDetailItemField: DetailItemField = {
|
|
80
|
+
name: 'exampleField',
|
|
81
|
+
label: 'Example Field',
|
|
82
|
+
href: '/example-field',
|
|
83
|
+
title: 'Example Field Title',
|
|
84
|
+
field: dummyField,
|
|
85
|
+
value: 'Example Value'
|
|
86
|
+
} as DetailItemField
|
|
87
|
+
|
|
88
|
+
const testDetailItemField2: DetailItemField = {
|
|
89
|
+
name: 'exampleField2',
|
|
90
|
+
label: 'Example Field 2',
|
|
91
|
+
href: '/example-field-2',
|
|
92
|
+
title: 'Example Field 2 Title',
|
|
93
|
+
field: dummyField,
|
|
94
|
+
value: 'Example Value 2'
|
|
95
|
+
} as DetailItemField
|
|
96
|
+
|
|
97
|
+
const testDetailItemRepeat: DetailItemRepeat = {
|
|
98
|
+
name: 'exampleRepeat',
|
|
99
|
+
label: 'Example Repeat',
|
|
100
|
+
href: '/example-repeat',
|
|
101
|
+
title: 'Example Repeat Title',
|
|
102
|
+
value: 'Example Repeat Value',
|
|
103
|
+
subItems: [
|
|
104
|
+
[
|
|
105
|
+
{
|
|
106
|
+
name: 'subItem1_1',
|
|
107
|
+
label: 'Sub Item 1 1',
|
|
108
|
+
field: dummyField,
|
|
109
|
+
href: '/sub-item-1-1',
|
|
110
|
+
title: 'Sub Item 1 1 Title',
|
|
111
|
+
value: 'Sub Item 1 1 Value'
|
|
112
|
+
} as DetailItemField,
|
|
113
|
+
{
|
|
114
|
+
name: 'subItem1_2',
|
|
115
|
+
label: 'Sub Item 1 2',
|
|
116
|
+
field: dummyField,
|
|
117
|
+
href: '/sub-item-1-2',
|
|
118
|
+
title: 'Sub Item 1 2 Title',
|
|
119
|
+
value: 'Sub Item 1 2 Value'
|
|
120
|
+
} as DetailItemField
|
|
121
|
+
],
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
name: 'subItem2_1',
|
|
125
|
+
label: 'Sub Item 2 1',
|
|
126
|
+
field: dummyField,
|
|
127
|
+
href: '/sub-item-2-1',
|
|
128
|
+
title: 'Sub Item 2 1 Title',
|
|
129
|
+
value: 'Sub Item 2 1 Value'
|
|
130
|
+
} as DetailItemField
|
|
131
|
+
]
|
|
132
|
+
]
|
|
133
|
+
} as DetailItemRepeat
|
|
134
|
+
|
|
135
|
+
const fileState: FileState = {
|
|
136
|
+
uploadId: '123',
|
|
137
|
+
status: {
|
|
138
|
+
form: {
|
|
139
|
+
file: {
|
|
140
|
+
fileId: '123-456-789',
|
|
141
|
+
contentLength: 1,
|
|
142
|
+
filename: 'foobar.txt',
|
|
143
|
+
fileStatus: FileStatus.complete
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
uploadStatus: UploadStatus.ready,
|
|
147
|
+
numberOfRejectedFiles: 0,
|
|
148
|
+
metadata: {
|
|
149
|
+
retrievalKey: '123'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const fileState2: FileState = {
|
|
155
|
+
uploadId: '456',
|
|
156
|
+
status: {
|
|
157
|
+
form: {
|
|
158
|
+
file: {
|
|
159
|
+
fileId: '456-789-123',
|
|
160
|
+
contentLength: 1,
|
|
161
|
+
filename: 'bazbuzz.txt',
|
|
162
|
+
fileStatus: FileStatus.complete
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
uploadStatus: UploadStatus.ready,
|
|
166
|
+
numberOfRejectedFiles: 0,
|
|
167
|
+
metadata: {
|
|
168
|
+
retrievalKey: '456'
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const testDetailItemFile1: DetailItemField = Object.create(
|
|
174
|
+
FileUploadField.prototype
|
|
175
|
+
)
|
|
176
|
+
Object.assign(testDetailItemFile1, {
|
|
177
|
+
name: 'exampleFile1',
|
|
178
|
+
label: 'Example File Field',
|
|
179
|
+
href: '/example-file',
|
|
180
|
+
title: 'Example File Field Title',
|
|
181
|
+
field: testDetailItemFile1,
|
|
182
|
+
value: 'Example File Value',
|
|
183
|
+
state: {
|
|
184
|
+
exampleFile1: [fileState, fileState2]
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const items: DetailItem[] = [
|
|
189
|
+
testDetailItemField,
|
|
190
|
+
testDetailItemField2,
|
|
191
|
+
testDetailItemRepeat,
|
|
192
|
+
testDetailItemFile1
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
describe('Adapter v1 formatter', () => {
|
|
196
|
+
beforeEach(() => {
|
|
197
|
+
jest.clearAllMocks()
|
|
198
|
+
jest.useFakeTimers()
|
|
199
|
+
jest.setSystemTime(new Date('2024-01-15T10:30:00.000Z'))
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
afterEach(() => {
|
|
203
|
+
jest.useRealTimers()
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('should return the adapter v1 output with complete formMetadata', () => {
|
|
207
|
+
const formMetadata: FormMetadata = {
|
|
208
|
+
id: 'form-123',
|
|
209
|
+
slug: 'test-form',
|
|
210
|
+
title: 'Test Form',
|
|
211
|
+
notificationEmail: 'test@example.com'
|
|
212
|
+
} as FormMetadata
|
|
213
|
+
|
|
214
|
+
const formStatus = {
|
|
215
|
+
isPreview: false,
|
|
216
|
+
state: FormStatus.Live
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const body = format(
|
|
220
|
+
context,
|
|
221
|
+
items,
|
|
222
|
+
model,
|
|
223
|
+
submitResponse,
|
|
224
|
+
formStatus,
|
|
225
|
+
formMetadata
|
|
226
|
+
)
|
|
227
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
228
|
+
|
|
229
|
+
expect(parsedBody.meta).toEqual({
|
|
230
|
+
schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
|
|
231
|
+
timestamp: '2024-01-15T10:30:00.000Z',
|
|
232
|
+
referenceNumber: 'foobar',
|
|
233
|
+
formName: definition.name,
|
|
234
|
+
formId: 'form-123',
|
|
235
|
+
formSlug: 'test-form',
|
|
236
|
+
status: FormStatus.Live,
|
|
237
|
+
isPreview: false,
|
|
238
|
+
notificationEmail: 'test@example.com'
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
expect(parsedBody.data).toEqual({
|
|
242
|
+
main: {
|
|
243
|
+
exampleField: 'hello world',
|
|
244
|
+
exampleField2: 'hello world'
|
|
245
|
+
},
|
|
246
|
+
repeaters: {
|
|
247
|
+
exampleRepeat: [
|
|
248
|
+
{
|
|
249
|
+
subItem1_1: 'hello world',
|
|
250
|
+
subItem1_2: 'hello world'
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
subItem2_1: 'hello world'
|
|
254
|
+
}
|
|
255
|
+
]
|
|
256
|
+
},
|
|
257
|
+
files: {
|
|
258
|
+
exampleFile1: [
|
|
259
|
+
{
|
|
260
|
+
fileId: '123-456-789',
|
|
261
|
+
userDownloadLink: 'https://forms-designer/file-download/123-456-789'
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
fileId: '456-789-123',
|
|
265
|
+
userDownloadLink: 'https://forms-designer/file-download/456-789-123'
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('should handle preview form status correctly', () => {
|
|
273
|
+
const formMetadata: FormMetadata = {
|
|
274
|
+
id: 'form-123',
|
|
275
|
+
slug: 'test-form',
|
|
276
|
+
title: 'Test Form',
|
|
277
|
+
notificationEmail: 'test@example.com'
|
|
278
|
+
} as FormMetadata
|
|
279
|
+
|
|
280
|
+
const formStatus = {
|
|
281
|
+
isPreview: true,
|
|
282
|
+
state: FormStatus.Draft
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const body = format(
|
|
286
|
+
context,
|
|
287
|
+
items,
|
|
288
|
+
model,
|
|
289
|
+
submitResponse,
|
|
290
|
+
formStatus,
|
|
291
|
+
formMetadata
|
|
292
|
+
)
|
|
293
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
294
|
+
|
|
295
|
+
expect(parsedBody.meta.status).toBe(FormStatus.Draft)
|
|
296
|
+
expect(parsedBody.meta.isPreview).toBe(true)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('should handle missing formMetadata with empty strings', () => {
|
|
300
|
+
const formStatus = {
|
|
301
|
+
isPreview: false,
|
|
302
|
+
state: FormStatus.Live
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const body = format(context, items, model, submitResponse, formStatus)
|
|
306
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
307
|
+
|
|
308
|
+
expect(parsedBody.meta.formId).toBe('')
|
|
309
|
+
expect(parsedBody.meta.formSlug).toBe('')
|
|
310
|
+
expect(parsedBody.meta.notificationEmail).toBe('')
|
|
311
|
+
expect(parsedBody.meta.formName).toBe(definition.name)
|
|
312
|
+
expect(parsedBody.meta.referenceNumber).toBe('foobar')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('should handle partial formMetadata', () => {
|
|
316
|
+
const formMetadata: FormMetadata = {
|
|
317
|
+
id: 'form-456',
|
|
318
|
+
slug: 'partial-form',
|
|
319
|
+
title: 'Partial Form'
|
|
320
|
+
} as FormMetadata
|
|
321
|
+
|
|
322
|
+
const formStatus = {
|
|
323
|
+
isPreview: true,
|
|
324
|
+
state: FormStatus.Draft
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const body = format(
|
|
328
|
+
context,
|
|
329
|
+
items,
|
|
330
|
+
model,
|
|
331
|
+
submitResponse,
|
|
332
|
+
formStatus,
|
|
333
|
+
formMetadata
|
|
334
|
+
)
|
|
335
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
336
|
+
|
|
337
|
+
expect(parsedBody.meta.formId).toBe('form-456')
|
|
338
|
+
expect(parsedBody.meta.formSlug).toBe('partial-form')
|
|
339
|
+
expect(parsedBody.meta.notificationEmail).toBe('')
|
|
340
|
+
expect(parsedBody.meta.status).toBe(FormStatus.Draft)
|
|
341
|
+
expect(parsedBody.meta.isPreview).toBe(true)
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
it('should use correct schema version', () => {
|
|
345
|
+
const formStatus = {
|
|
346
|
+
isPreview: false,
|
|
347
|
+
state: FormStatus.Live
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const body = format(context, items, model, submitResponse, formStatus)
|
|
351
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
352
|
+
|
|
353
|
+
expect(parsedBody.meta.schemaVersion).toBe(
|
|
354
|
+
FormAdapterSubmissionSchemaVersion.V1
|
|
355
|
+
)
|
|
356
|
+
expect(parsedBody.meta.schemaVersion).toBe(1)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it('should generate valid timestamp', () => {
|
|
360
|
+
const formStatus = {
|
|
361
|
+
isPreview: false,
|
|
362
|
+
state: FormStatus.Live
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const body = format(context, items, model, submitResponse, formStatus)
|
|
366
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
367
|
+
|
|
368
|
+
expect(parsedBody.meta.timestamp).toBe('2024-01-15T10:30:00.000Z')
|
|
369
|
+
expect(typeof parsedBody.meta.timestamp).toBe('string')
|
|
370
|
+
|
|
371
|
+
expect(new Date(parsedBody.meta.timestamp)).toEqual(
|
|
372
|
+
new Date('2024-01-15T10:30:00.000Z')
|
|
373
|
+
)
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('should handle empty items array', () => {
|
|
377
|
+
const formStatus = {
|
|
378
|
+
isPreview: false,
|
|
379
|
+
state: FormStatus.Live
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const body = format(context, [], model, submitResponse, formStatus)
|
|
383
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
384
|
+
|
|
385
|
+
expect(parsedBody.data.main).toEqual({})
|
|
386
|
+
expect(parsedBody.data.repeaters).toEqual({})
|
|
387
|
+
expect(parsedBody.data.files).toEqual({})
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('should handle different form statuses', () => {
|
|
391
|
+
const testCases = [
|
|
392
|
+
{
|
|
393
|
+
isPreview: false,
|
|
394
|
+
state: FormStatus.Live,
|
|
395
|
+
expectedStatus: FormStatus.Live
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
isPreview: true,
|
|
399
|
+
state: FormStatus.Draft,
|
|
400
|
+
expectedStatus: FormStatus.Draft
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
isPreview: true,
|
|
404
|
+
state: FormStatus.Live,
|
|
405
|
+
expectedStatus: FormStatus.Draft
|
|
406
|
+
}
|
|
407
|
+
]
|
|
408
|
+
|
|
409
|
+
testCases.forEach(({ isPreview, state, expectedStatus }) => {
|
|
410
|
+
const formStatus = { isPreview, state }
|
|
411
|
+
const body = format(context, items, model, submitResponse, formStatus)
|
|
412
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
413
|
+
|
|
414
|
+
expect(parsedBody.meta.status).toBe(expectedStatus)
|
|
415
|
+
expect(parsedBody.meta.isPreview).toBe(isPreview)
|
|
416
|
+
})
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it('should return valid JSON string', () => {
|
|
420
|
+
const formStatus = {
|
|
421
|
+
isPreview: false,
|
|
422
|
+
state: FormStatus.Live
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const body = format(context, items, model, submitResponse, formStatus)
|
|
426
|
+
|
|
427
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
428
|
+
expect(() => JSON.parse(body)).not.toThrow()
|
|
429
|
+
expect(typeof body).toBe('string')
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
it('should handle formMetadata with only id', () => {
|
|
433
|
+
const formMetadata: FormMetadata = {
|
|
434
|
+
id: 'only-id-form'
|
|
435
|
+
} as FormMetadata
|
|
436
|
+
|
|
437
|
+
const formStatus = {
|
|
438
|
+
isPreview: false,
|
|
439
|
+
state: FormStatus.Live
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const body = format(
|
|
443
|
+
context,
|
|
444
|
+
items,
|
|
445
|
+
model,
|
|
446
|
+
submitResponse,
|
|
447
|
+
formStatus,
|
|
448
|
+
formMetadata
|
|
449
|
+
)
|
|
450
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
451
|
+
|
|
452
|
+
expect(parsedBody.meta.formId).toBe('only-id-form')
|
|
453
|
+
expect(parsedBody.meta.formSlug).toBe('')
|
|
454
|
+
expect(parsedBody.meta.notificationEmail).toBe('')
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('should handle formMetadata with only slug', () => {
|
|
458
|
+
const formMetadata: FormMetadata = {
|
|
459
|
+
slug: 'only-slug-form'
|
|
460
|
+
} as FormMetadata
|
|
461
|
+
|
|
462
|
+
const formStatus = {
|
|
463
|
+
isPreview: false,
|
|
464
|
+
state: FormStatus.Live
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const body = format(
|
|
468
|
+
context,
|
|
469
|
+
items,
|
|
470
|
+
model,
|
|
471
|
+
submitResponse,
|
|
472
|
+
formStatus,
|
|
473
|
+
formMetadata
|
|
474
|
+
)
|
|
475
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
476
|
+
|
|
477
|
+
expect(parsedBody.meta.formId).toBe('')
|
|
478
|
+
expect(parsedBody.meta.formSlug).toBe('only-slug-form')
|
|
479
|
+
expect(parsedBody.meta.notificationEmail).toBe('')
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
it('should handle formMetadata with only notificationEmail', () => {
|
|
483
|
+
const formMetadata: FormMetadata = {
|
|
484
|
+
notificationEmail: 'only-email@example.com'
|
|
485
|
+
} as FormMetadata
|
|
486
|
+
|
|
487
|
+
const formStatus = {
|
|
488
|
+
isPreview: false,
|
|
489
|
+
state: FormStatus.Live
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const body = format(
|
|
493
|
+
context,
|
|
494
|
+
items,
|
|
495
|
+
model,
|
|
496
|
+
submitResponse,
|
|
497
|
+
formStatus,
|
|
498
|
+
formMetadata
|
|
499
|
+
)
|
|
500
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
501
|
+
|
|
502
|
+
expect(parsedBody.meta.formId).toBe('')
|
|
503
|
+
expect(parsedBody.meta.formSlug).toBe('')
|
|
504
|
+
expect(parsedBody.meta.notificationEmail).toBe('only-email@example.com')
|
|
505
|
+
})
|
|
506
|
+
})
|