@defra/forms-engine-plugin 3.0.3 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.server/server/plugins/engine/models/__snapshots__/SummaryViewModel.test.ts.snap +52 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +15 -5
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +38 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +1 -1
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/package.json +1 -1
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +142 -112
- package/src/server/plugins/engine/models/__snapshots__/SummaryViewModel.test.ts.snap +52 -0
- package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +40 -0
- package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +17 -14
- package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +41 -0
- package/src/server/plugins/engine/outputFormatters/machine/v2.ts +1 -1
- package/src/server/plugins/engine/types.ts +1 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
|
2
|
+
|
|
3
|
+
exports[`SummaryViewModel Check answers (0 items) should use correct summary labels 1`] = `
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
"label": "How would you like to receive your pizza?",
|
|
7
|
+
"name": "orderType",
|
|
8
|
+
"title": "How you would like to receive your pizza",
|
|
9
|
+
"value": "Collection",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"label": "Pizza",
|
|
13
|
+
"name": "pizza",
|
|
14
|
+
"title": "Pizzas",
|
|
15
|
+
"value": "",
|
|
16
|
+
},
|
|
17
|
+
]
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
exports[`SummaryViewModel Check answers (1 item) should use correct summary labels 1`] = `
|
|
21
|
+
[
|
|
22
|
+
{
|
|
23
|
+
"label": "How would you like to receive your pizza?",
|
|
24
|
+
"name": "orderType",
|
|
25
|
+
"title": "How you would like to receive your pizza",
|
|
26
|
+
"value": "Delivery",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"label": "Pizza",
|
|
30
|
+
"name": "pizza",
|
|
31
|
+
"title": "Pizza added",
|
|
32
|
+
"value": "You added 1 Pizza",
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
exports[`SummaryViewModel Check answers (2 items) should use correct summary labels 1`] = `
|
|
38
|
+
[
|
|
39
|
+
{
|
|
40
|
+
"label": "How would you like to receive your pizza?",
|
|
41
|
+
"name": "orderType",
|
|
42
|
+
"title": "How you would like to receive your pizza",
|
|
43
|
+
"value": "Delivery",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"label": "Pizza",
|
|
47
|
+
"name": "pizza",
|
|
48
|
+
"title": "Pizzas added",
|
|
49
|
+
"value": "You added 2 Pizzas",
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
`;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { categoriseData } from "../machine/v2.js";
|
|
2
2
|
import { FormAdapterSubmissionSchemaVersion } from "../../types/enums.js";
|
|
3
3
|
import { FormStatus } from "../../../../routes/types.js";
|
|
4
4
|
export function format(context, items, model, submitResponse, formStatus, formMetadata) {
|
|
5
|
-
const v2DataString = machineV2(context, items, model, submitResponse, formStatus);
|
|
6
|
-
const v2DataParsed = JSON.parse(v2DataString);
|
|
7
5
|
const csvFiles = extractCsvFiles(submitResponse);
|
|
8
|
-
const
|
|
6
|
+
const {
|
|
7
|
+
main: v2Main,
|
|
8
|
+
...v2Data
|
|
9
|
+
} = categoriseData(items);
|
|
9
10
|
const versionMetadata = getVersionMetadata(context.submittedVersionNumber, formMetadata);
|
|
10
11
|
const meta = {
|
|
11
12
|
schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
|
|
@@ -21,7 +22,16 @@ export function format(context, items, model, submitResponse, formStatus, formMe
|
|
|
21
22
|
if (versionMetadata) {
|
|
22
23
|
meta.versionMetadata = versionMetadata;
|
|
23
24
|
}
|
|
24
|
-
const
|
|
25
|
+
const main = Object.fromEntries(Object.entries(v2Main).map(([key, value]) => {
|
|
26
|
+
if (value === undefined) {
|
|
27
|
+
return [key, null];
|
|
28
|
+
}
|
|
29
|
+
return [key, value];
|
|
30
|
+
}));
|
|
31
|
+
const data = {
|
|
32
|
+
main,
|
|
33
|
+
...v2Data
|
|
34
|
+
};
|
|
25
35
|
const result = {
|
|
26
36
|
files: csvFiles
|
|
27
37
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v1.js","names":["
|
|
1
|
+
{"version":3,"file":"v1.js","names":["categoriseData","FormAdapterSubmissionSchemaVersion","FormStatus","format","context","items","model","submitResponse","formStatus","formMetadata","csvFiles","extractCsvFiles","main","v2Main","v2Data","versionMetadata","getVersionMetadata","submittedVersionNumber","meta","schemaVersion","V1","timestamp","Date","referenceNumber","formName","name","formId","id","formSlug","slug","status","isPreview","Draft","Live","notificationEmail","Object","fromEntries","entries","map","key","value","undefined","data","result","files","payload","JSON","stringify","versions","length","submittedVersion","find","v","versionNumber","createdAt","firstVersion","repeaters"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/adapter/v1.ts"],"sourcesContent":["import {\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { categoriseData } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'\nimport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\nimport {\n type FormAdapterSubmissionMessageData,\n type FormAdapterSubmissionMessageMeta,\n type FormAdapterSubmissionMessagePayload,\n type FormAdapterSubmissionMessageResult,\n type FormContext\n} from '~/src/server/plugins/engine/types.js'\nimport { FormStatus } from '~/src/server/routes/types.js'\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n submitResponse: SubmitResponsePayload,\n formStatus: ReturnType<typeof checkFormStatus>,\n formMetadata?: FormMetadata\n): string {\n const csvFiles = extractCsvFiles(submitResponse)\n\n const { main: v2Main, ...v2Data } = categoriseData(items)\n\n const versionMetadata = getVersionMetadata(\n context.submittedVersionNumber,\n formMetadata\n )\n\n const meta: FormAdapterSubmissionMessageMeta = {\n schemaVersion: FormAdapterSubmissionSchemaVersion.V1,\n timestamp: new Date(),\n referenceNumber: context.referenceNumber,\n formName: model.name,\n formId: formMetadata?.id ?? '',\n formSlug: formMetadata?.slug ?? '',\n status: formStatus.isPreview ? FormStatus.Draft : FormStatus.Live,\n isPreview: formStatus.isPreview,\n notificationEmail: formMetadata?.notificationEmail ?? ''\n }\n\n if (versionMetadata) {\n meta.versionMetadata = versionMetadata\n }\n\n const main = Object.fromEntries(\n Object.entries(v2Main).map(([key, value]) => {\n if (value === undefined) {\n return [key, null]\n }\n\n return [key, value]\n })\n )\n\n const data: FormAdapterSubmissionMessageData = {\n main,\n ...v2Data\n }\n\n const result: FormAdapterSubmissionMessageResult = {\n files: csvFiles\n }\n\n const payload: FormAdapterSubmissionMessagePayload = {\n meta,\n data,\n result\n }\n\n return JSON.stringify(payload)\n}\n\nexport function getVersionMetadata(\n submittedVersionNumber: number | undefined,\n formMetadata?: FormMetadata\n): { versionNumber: number; createdAt: Date } | undefined {\n if (!formMetadata?.versions?.length) {\n return undefined\n }\n\n if (submittedVersionNumber !== undefined) {\n const submittedVersion = formMetadata.versions.find(\n (v) => v.versionNumber === submittedVersionNumber\n )\n if (submittedVersion) {\n return {\n versionNumber: submittedVersion.versionNumber,\n createdAt: submittedVersion.createdAt\n }\n }\n }\n\n // fallback to first available version\n const firstVersion = formMetadata.versions[0]\n return {\n versionNumber: firstVersion.versionNumber,\n createdAt: firstVersion.createdAt\n }\n}\n\nfunction extractCsvFiles(\n submitResponse: SubmitResponsePayload\n): FormAdapterSubmissionMessageResult['files'] {\n const result =\n submitResponse.result as Partial<FormAdapterSubmissionMessageResult>\n\n return {\n main: result.files?.main ?? '',\n repeaters: result.files?.repeaters ?? {}\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -2,5 +2,42 @@ import { type SubmitResponsePayload } from '@defra/forms-model';
|
|
|
2
2
|
import { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js';
|
|
3
3
|
import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
|
|
4
4
|
import { type DetailItem } from '~/src/server/plugins/engine/models/types.js';
|
|
5
|
-
import { type FormContext } from '~/src/server/plugins/engine/types.js';
|
|
5
|
+
import { type FormContext, type RichFormValue } from '~/src/server/plugins/engine/types.js';
|
|
6
6
|
export declare function format(context: FormContext, items: DetailItem[], model: FormModel, _submitResponse: SubmitResponsePayload, _formStatus: ReturnType<typeof checkFormStatus>): string;
|
|
7
|
+
/**
|
|
8
|
+
* Categories the form submission data into the "main" body and "repeaters".
|
|
9
|
+
*
|
|
10
|
+
* {
|
|
11
|
+
* main: {
|
|
12
|
+
* componentName: 'componentValue',
|
|
13
|
+
* },
|
|
14
|
+
* repeaters: {
|
|
15
|
+
* repeaterName: [
|
|
16
|
+
* {
|
|
17
|
+
* textComponentName: 'componentValue'
|
|
18
|
+
* },
|
|
19
|
+
* {
|
|
20
|
+
* richComponentName: { foo: 'bar', 'baz': true }
|
|
21
|
+
* }
|
|
22
|
+
* ]
|
|
23
|
+
* },
|
|
24
|
+
* files: {
|
|
25
|
+
* fileComponentName: [
|
|
26
|
+
* {
|
|
27
|
+
* fileId: '123-456-789',
|
|
28
|
+
* fileName: 'example.pdf',
|
|
29
|
+
* userDownloadLink: 'https://forms-designer/file-download/123-456-789'
|
|
30
|
+
* }
|
|
31
|
+
* ]
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
export declare function categoriseData(items: DetailItem[]): {
|
|
36
|
+
main: Record<string, RichFormValue>;
|
|
37
|
+
repeaters: Record<string, Record<string, RichFormValue>[]>;
|
|
38
|
+
files: Record<string, {
|
|
39
|
+
fileId: string;
|
|
40
|
+
fileName: string;
|
|
41
|
+
userDownloadLink: string;
|
|
42
|
+
}[]>;
|
|
43
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2.js","names":["config","FileUploadField","designerUrl","get","format","context","items","model","_submitResponse","_formStatus","now","Date","categorisedData","categoriseData","meta","schemaVersion","timestamp","toISOString","definition","def","referenceNumber","data","body","JSON","stringify","output","main","repeaters","files","forEach","item","name","state","extractRepeaters","isFileUploadFieldItem","extractFileUploads","field","getFormValueFromState","subItems","inputRepeaterItem","outputRepeaterItem","repeaterComponent","push","fileUploadState","map","fileState","file","status","form","fileId","fileName","filename","userDownloadLink"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/machine/v2.ts"],"sourcesContent":["import { type SubmitResponsePayload } from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FileUploadFieldDetailitem,\n type FormAdapterFile,\n type FormContext,\n type RichFormValue\n} from '~/src/server/plugins/engine/types.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n _submitResponse: SubmitResponsePayload,\n _formStatus: ReturnType<typeof checkFormStatus>\n) {\n const now = new Date()\n\n const categorisedData = categoriseData(items)\n\n const meta: Record<string, unknown> = {\n schemaVersion: '2',\n timestamp: now.toISOString(),\n definition: model.def,\n referenceNumber: context.referenceNumber\n }\n\n const data = {\n meta,\n data: categorisedData\n }\n\n const body = JSON.stringify(data)\n\n return body\n}\n\n/**\n * Categories the form submission data into the \"main\" body and \"repeaters\".\n *\n * {\n * main: {\n * componentName: 'componentValue',\n * },\n * repeaters: {\n * repeaterName: [\n * {\n * textComponentName: 'componentValue'\n * },\n * {\n * richComponentName: { foo: 'bar', 'baz': true }\n * }\n * ]\n * },\n * files: {\n * fileComponentName: [\n * {\n * fileId: '123-456-789',\n * fileName: 'example.pdf',\n * userDownloadLink: 'https://forms-designer/file-download/123-456-789'\n * }\n * ]\n * }\n * }\n */\
|
|
1
|
+
{"version":3,"file":"v2.js","names":["config","FileUploadField","designerUrl","get","format","context","items","model","_submitResponse","_formStatus","now","Date","categorisedData","categoriseData","meta","schemaVersion","timestamp","toISOString","definition","def","referenceNumber","data","body","JSON","stringify","output","main","repeaters","files","forEach","item","name","state","extractRepeaters","isFileUploadFieldItem","extractFileUploads","field","getFormValueFromState","subItems","inputRepeaterItem","outputRepeaterItem","repeaterComponent","push","fileUploadState","map","fileState","file","status","form","fileId","fileName","filename","userDownloadLink"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/machine/v2.ts"],"sourcesContent":["import { type SubmitResponsePayload } from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FileUploadFieldDetailitem,\n type FormAdapterFile,\n type FormContext,\n type RichFormValue\n} from '~/src/server/plugins/engine/types.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n _submitResponse: SubmitResponsePayload,\n _formStatus: ReturnType<typeof checkFormStatus>\n) {\n const now = new Date()\n\n const categorisedData = categoriseData(items)\n\n const meta: Record<string, unknown> = {\n schemaVersion: '2',\n timestamp: now.toISOString(),\n definition: model.def,\n referenceNumber: context.referenceNumber\n }\n\n const data = {\n meta,\n data: categorisedData\n }\n\n const body = JSON.stringify(data)\n\n return body\n}\n\n/**\n * Categories the form submission data into the \"main\" body and \"repeaters\".\n *\n * {\n * main: {\n * componentName: 'componentValue',\n * },\n * repeaters: {\n * repeaterName: [\n * {\n * textComponentName: 'componentValue'\n * },\n * {\n * richComponentName: { foo: 'bar', 'baz': true }\n * }\n * ]\n * },\n * files: {\n * fileComponentName: [\n * {\n * fileId: '123-456-789',\n * fileName: 'example.pdf',\n * userDownloadLink: 'https://forms-designer/file-download/123-456-789'\n * }\n * ]\n * }\n * }\n */\nexport function categoriseData(items: DetailItem[]) {\n const output: {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<\n string,\n { fileId: string; fileName: string; userDownloadLink: string }[]\n >\n } = { main: {}, repeaters: {}, files: {} }\n\n items.forEach((item) => {\n const { name, state } = item\n\n if ('subItems' in item) {\n output.repeaters[name] = extractRepeaters(item)\n } else if (isFileUploadFieldItem(item)) {\n output.files[name] = extractFileUploads(item)\n } else {\n output.main[name] = item.field.getFormValueFromState(state)\n }\n })\n\n return output\n}\n\n/**\n * Returns the \"repeaters\" section of the response body\n * @param item - the repeater item\n * @returns the repeater item\n */\nfunction extractRepeaters(item: DetailItemRepeat) {\n const repeaters: Record<string, RichFormValue>[] = []\n\n item.subItems.forEach((inputRepeaterItem) => {\n const outputRepeaterItem: Record<string, RichFormValue> = {}\n\n inputRepeaterItem.forEach((repeaterComponent) => {\n const { field, state } = repeaterComponent\n\n outputRepeaterItem[repeaterComponent.name] =\n field.getFormValueFromState(state)\n })\n\n repeaters.push(outputRepeaterItem)\n })\n\n return repeaters\n}\n\n/**\n * Returns the \"files\" section of the response body\n * @param item - the file upload item in the form\n * @returns the file upload data\n */\nfunction extractFileUploads(\n item: FileUploadFieldDetailitem\n): FormAdapterFile[] {\n const fileUploadState = item.field.getFormValueFromState(item.state) ?? []\n\n return fileUploadState.map((fileState) => {\n const { file } = fileState.status.form\n return {\n fileId: file.fileId,\n fileName: file.filename,\n userDownloadLink: `${designerUrl}/file-download/${file.fileId}`\n }\n })\n}\n\nfunction isFileUploadFieldItem(\n item: DetailItemField\n): item is FileUploadFieldDetailitem {\n return item.field instanceof FileUploadField\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -317,7 +317,7 @@ export type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {
|
|
|
317
317
|
};
|
|
318
318
|
export type RichFormValue = FormValue | FormPayload | DatePartsState | MonthYearState | UkAddressState;
|
|
319
319
|
export interface FormAdapterSubmissionMessageData {
|
|
320
|
-
main: Record<string, RichFormValue>;
|
|
320
|
+
main: Record<string, RichFormValue | null>;
|
|
321
321
|
repeaters: Record<string, Record<string, RichFormValue>[]>;
|
|
322
322
|
files: Record<string, FormAdapterFile[]>;
|
|
323
323
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.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 FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\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 submittedVersionNumber?: number\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 {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\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 allowSaveAndExit: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n allowSaveAndExit: boolean\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: AnyFormRequest,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\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 versionMetadata?: FormVersionMetadata\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\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":"AAqDA;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;;AAsGA,SACEA,UAAU,EACVC,YAAY;;AA8Nd;AACA;AACA","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.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 FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\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 submittedVersionNumber?: number\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 {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\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 allowSaveAndExit: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n allowSaveAndExit: boolean\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: AnyFormRequest,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\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 versionMetadata?: FormVersionMetadata\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue | null>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\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":"AAqDA;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;;AAsGA,SACEA,UAAU,EACVC,YAAY;;AA8Nd;AACA;AACA","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -65,7 +65,9 @@ describe('SummaryViewModel', () => {
|
|
|
65
65
|
'Pizzas',
|
|
66
66
|
'Pizza'
|
|
67
67
|
],
|
|
68
|
-
values: ['Collection', 'Not supplied']
|
|
68
|
+
values: ['Collection', 'Not supplied'],
|
|
69
|
+
answers: ['Collection', ''],
|
|
70
|
+
names: ['orderType', 'pizza']
|
|
69
71
|
},
|
|
70
72
|
{
|
|
71
73
|
description: '1 item',
|
|
@@ -87,7 +89,9 @@ describe('SummaryViewModel', () => {
|
|
|
87
89
|
'Pizzas',
|
|
88
90
|
'Pizza'
|
|
89
91
|
],
|
|
90
|
-
values: ['Delivery', 'You added 1 Pizza']
|
|
92
|
+
values: ['Delivery', 'You added 1 Pizza'],
|
|
93
|
+
answers: ['Delivery', 'You added 1 Pizza'],
|
|
94
|
+
names: ['orderType', 'pizza']
|
|
91
95
|
},
|
|
92
96
|
{
|
|
93
97
|
description: '2 items',
|
|
@@ -114,142 +118,168 @@ describe('SummaryViewModel', () => {
|
|
|
114
118
|
'Pizzas',
|
|
115
119
|
'Pizza'
|
|
116
120
|
],
|
|
117
|
-
values: ['Delivery', 'You added 2 Pizzas']
|
|
121
|
+
values: ['Delivery', 'You added 2 Pizzas'],
|
|
122
|
+
answers: ['Delivery', 'You added 2 Pizzas'],
|
|
123
|
+
names: ['orderType', 'pizza']
|
|
118
124
|
}
|
|
119
|
-
])(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
])(
|
|
126
|
+
'Check answers ($description)',
|
|
127
|
+
({ state, keys, values, names, answers }) => {
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
context = model.getFormContext(request, state)
|
|
130
|
+
summaryViewModel = new SummaryViewModel(request, page, context)
|
|
131
|
+
})
|
|
124
132
|
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
it('should add title for each section', () => {
|
|
134
|
+
const [checkAnswers1, checkAnswers2] = summaryViewModel.checkAnswers
|
|
127
135
|
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
// 1st summary list has no title
|
|
137
|
+
expect(checkAnswers1).toHaveProperty('title', undefined)
|
|
130
138
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
139
|
+
// 2nd summary list has section title
|
|
140
|
+
expect(checkAnswers2).toHaveProperty('title', {
|
|
141
|
+
text: 'Food'
|
|
142
|
+
})
|
|
134
143
|
})
|
|
135
|
-
})
|
|
136
144
|
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
it('should add summary list for each section', () => {
|
|
146
|
+
expect(summaryViewModel.checkAnswers).toHaveLength(2)
|
|
139
147
|
|
|
140
|
-
|
|
148
|
+
const [checkAnswers1, checkAnswers2] = summaryViewModel.checkAnswers
|
|
141
149
|
|
|
142
|
-
|
|
143
|
-
|
|
150
|
+
const { summaryList: summaryList1 } = checkAnswers1
|
|
151
|
+
const { summaryList: summaryList2 } = checkAnswers2
|
|
144
152
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
153
|
+
expect(summaryList1).toHaveProperty('rows', [
|
|
154
|
+
{
|
|
155
|
+
key: {
|
|
156
|
+
text: keys[2]
|
|
157
|
+
},
|
|
158
|
+
value: {
|
|
159
|
+
classes: 'app-prose-scope',
|
|
160
|
+
html: values[0]
|
|
161
|
+
},
|
|
162
|
+
actions: {
|
|
163
|
+
items: [
|
|
164
|
+
{
|
|
165
|
+
classes: 'govuk-link--no-visited-state',
|
|
166
|
+
href: `${basePath}/delivery-or-collection?returnUrl=${encodeURIComponent(`${basePath}/summary`)}`,
|
|
167
|
+
text: 'Change',
|
|
168
|
+
visuallyHiddenText: keys[0]
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
}
|
|
163
172
|
}
|
|
164
|
-
|
|
165
|
-
])
|
|
173
|
+
])
|
|
166
174
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
175
|
+
expect(summaryList2).toHaveProperty('rows', [
|
|
176
|
+
{
|
|
177
|
+
key: {
|
|
178
|
+
text: keys[1]
|
|
179
|
+
},
|
|
180
|
+
value: {
|
|
181
|
+
classes: 'app-prose-scope',
|
|
182
|
+
html: values[1]
|
|
183
|
+
},
|
|
184
|
+
actions: {
|
|
185
|
+
items: [
|
|
186
|
+
{
|
|
187
|
+
classes: 'govuk-link--no-visited-state',
|
|
188
|
+
href: `${basePath}/pizza-order/summary?returnUrl=${encodeURIComponent(`${basePath}/summary`)}`,
|
|
189
|
+
text: 'Change',
|
|
190
|
+
visuallyHiddenText: 'Pizza'
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
185
194
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
})
|
|
195
|
+
])
|
|
196
|
+
})
|
|
189
197
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
198
|
+
it('should add summary list for each section (preview URL direct access)', () => {
|
|
199
|
+
request.query.force = '' // Preview URL '?force'
|
|
200
|
+
context = model.getFormContext(request, state)
|
|
201
|
+
summaryViewModel = new SummaryViewModel(request, page, context)
|
|
194
202
|
|
|
195
|
-
|
|
203
|
+
expect(summaryViewModel.checkAnswers).toHaveLength(2)
|
|
196
204
|
|
|
197
|
-
|
|
205
|
+
const [checkAnswers1, checkAnswers2] = summaryViewModel.checkAnswers
|
|
198
206
|
|
|
199
|
-
|
|
200
|
-
|
|
207
|
+
const { summaryList: summaryList1 } = checkAnswers1
|
|
208
|
+
const { summaryList: summaryList2 } = checkAnswers2
|
|
201
209
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
expect(summaryList1).toHaveProperty('rows', [
|
|
211
|
+
{
|
|
212
|
+
key: {
|
|
213
|
+
text: keys[2]
|
|
214
|
+
},
|
|
215
|
+
value: {
|
|
216
|
+
classes: 'app-prose-scope',
|
|
217
|
+
html: values[0]
|
|
218
|
+
},
|
|
219
|
+
actions: {
|
|
220
|
+
items: []
|
|
221
|
+
}
|
|
213
222
|
}
|
|
214
|
-
|
|
215
|
-
])
|
|
223
|
+
])
|
|
216
224
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
expect(summaryList2).toHaveProperty('rows', [
|
|
226
|
+
{
|
|
227
|
+
key: {
|
|
228
|
+
text: keys[1]
|
|
229
|
+
},
|
|
230
|
+
value: {
|
|
231
|
+
classes: 'app-prose-scope',
|
|
232
|
+
html: values[1]
|
|
233
|
+
},
|
|
234
|
+
actions: {
|
|
235
|
+
items: []
|
|
236
|
+
}
|
|
228
237
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
})
|
|
238
|
+
])
|
|
239
|
+
})
|
|
232
240
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
241
|
+
it('should use correct summary labels', () => {
|
|
242
|
+
request.query.force = '' // Preview URL '?force'
|
|
243
|
+
context = model.getFormContext(request, state)
|
|
244
|
+
summaryViewModel = new SummaryViewModel(request, page, context)
|
|
237
245
|
|
|
238
|
-
|
|
246
|
+
expect(summaryViewModel.details).toHaveLength(2)
|
|
239
247
|
|
|
240
|
-
|
|
248
|
+
const [details1, details2] = summaryViewModel.details
|
|
241
249
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
250
|
+
expect(details1.items[0]).toMatchObject({
|
|
251
|
+
name: names[0],
|
|
252
|
+
value: answers[0],
|
|
253
|
+
title: keys[2],
|
|
254
|
+
label: keys[0]
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
expect(details2.items[0]).toMatchObject({
|
|
258
|
+
name: names[1],
|
|
259
|
+
value: answers[1],
|
|
260
|
+
title: keys[1],
|
|
261
|
+
label: keys[4]
|
|
262
|
+
})
|
|
246
263
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
264
|
+
const snapshot = [
|
|
265
|
+
{
|
|
266
|
+
name: names[0],
|
|
267
|
+
value: answers[0],
|
|
268
|
+
title: keys[2],
|
|
269
|
+
label: keys[0]
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: names[1],
|
|
273
|
+
value: answers[1],
|
|
274
|
+
title: keys[1],
|
|
275
|
+
label: keys[4]
|
|
276
|
+
}
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
expect(snapshot).toMatchSnapshot()
|
|
250
280
|
})
|
|
251
|
-
}
|
|
252
|
-
|
|
281
|
+
}
|
|
282
|
+
)
|
|
253
283
|
})
|
|
254
284
|
|
|
255
285
|
describe('SummaryPageController', () => {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
|
2
|
+
|
|
3
|
+
exports[`SummaryViewModel Check answers (0 items) should use correct summary labels 1`] = `
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
"label": "How would you like to receive your pizza?",
|
|
7
|
+
"name": "orderType",
|
|
8
|
+
"title": "How you would like to receive your pizza",
|
|
9
|
+
"value": "Collection",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"label": "Pizza",
|
|
13
|
+
"name": "pizza",
|
|
14
|
+
"title": "Pizzas",
|
|
15
|
+
"value": "",
|
|
16
|
+
},
|
|
17
|
+
]
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
exports[`SummaryViewModel Check answers (1 item) should use correct summary labels 1`] = `
|
|
21
|
+
[
|
|
22
|
+
{
|
|
23
|
+
"label": "How would you like to receive your pizza?",
|
|
24
|
+
"name": "orderType",
|
|
25
|
+
"title": "How you would like to receive your pizza",
|
|
26
|
+
"value": "Delivery",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"label": "Pizza",
|
|
30
|
+
"name": "pizza",
|
|
31
|
+
"title": "Pizza added",
|
|
32
|
+
"value": "You added 1 Pizza",
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
exports[`SummaryViewModel Check answers (2 items) should use correct summary labels 1`] = `
|
|
38
|
+
[
|
|
39
|
+
{
|
|
40
|
+
"label": "How would you like to receive your pizza?",
|
|
41
|
+
"name": "orderType",
|
|
42
|
+
"title": "How you would like to receive your pizza",
|
|
43
|
+
"value": "Delivery",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"label": "Pizza",
|
|
47
|
+
"name": "pizza",
|
|
48
|
+
"title": "Pizzas added",
|
|
49
|
+
"value": "You added 2 Pizzas",
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
`;
|
|
@@ -723,6 +723,46 @@ describe('Adapter v1 formatter', () => {
|
|
|
723
723
|
expect(parsedBody.meta.versionMetadata).toBeUndefined()
|
|
724
724
|
})
|
|
725
725
|
|
|
726
|
+
it('should handle optional fields that are undefined', () => {
|
|
727
|
+
const formMetadata: Partial<FormMetadata> = {
|
|
728
|
+
id: 'form-123',
|
|
729
|
+
slug: 'test-form',
|
|
730
|
+
title: 'Test Form',
|
|
731
|
+
notificationEmail: 'test@example.com'
|
|
732
|
+
} as FormMetadata
|
|
733
|
+
|
|
734
|
+
const formStatus = {
|
|
735
|
+
isPreview: false,
|
|
736
|
+
state: FormStatus.Live
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const dummyField: Field = {
|
|
740
|
+
getFormValueFromState: (_) => undefined
|
|
741
|
+
} as Field
|
|
742
|
+
|
|
743
|
+
const items: DetailItem[] = [
|
|
744
|
+
{
|
|
745
|
+
name: 'exampleField3',
|
|
746
|
+
label: 'Example Field 3',
|
|
747
|
+
href: '/example-field-3',
|
|
748
|
+
title: 'Example Field 3 Title',
|
|
749
|
+
field: dummyField,
|
|
750
|
+
value: ''
|
|
751
|
+
} as DetailItemField
|
|
752
|
+
]
|
|
753
|
+
|
|
754
|
+
const body = format(
|
|
755
|
+
context,
|
|
756
|
+
items,
|
|
757
|
+
model,
|
|
758
|
+
submitResponse,
|
|
759
|
+
formStatus,
|
|
760
|
+
formMetadata as FormMetadata
|
|
761
|
+
)
|
|
762
|
+
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
|
|
763
|
+
expect(parsedBody.data.main).toEqual({ exampleField3: null })
|
|
764
|
+
})
|
|
765
|
+
|
|
726
766
|
describe('version metadata handling', () => {
|
|
727
767
|
it('should include versionMetadata when context has submittedVersionNumber and formMetadata has versions', () => {
|
|
728
768
|
const formMetadata: Partial<FormMetadata> = {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
|
|
7
7
|
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
8
8
|
import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
|
|
9
|
-
import {
|
|
9
|
+
import { categoriseData } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'
|
|
10
10
|
import { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'
|
|
11
11
|
import {
|
|
12
12
|
type FormAdapterSubmissionMessageData,
|
|
@@ -25,20 +25,9 @@ export function format(
|
|
|
25
25
|
formStatus: ReturnType<typeof checkFormStatus>,
|
|
26
26
|
formMetadata?: FormMetadata
|
|
27
27
|
): string {
|
|
28
|
-
const v2DataString = machineV2(
|
|
29
|
-
context,
|
|
30
|
-
items,
|
|
31
|
-
model,
|
|
32
|
-
submitResponse,
|
|
33
|
-
formStatus
|
|
34
|
-
)
|
|
35
|
-
const v2DataParsed = JSON.parse(v2DataString) as {
|
|
36
|
-
data: FormAdapterSubmissionMessageData
|
|
37
|
-
}
|
|
38
|
-
|
|
39
28
|
const csvFiles = extractCsvFiles(submitResponse)
|
|
40
29
|
|
|
41
|
-
const
|
|
30
|
+
const { main: v2Main, ...v2Data } = categoriseData(items)
|
|
42
31
|
|
|
43
32
|
const versionMetadata = getVersionMetadata(
|
|
44
33
|
context.submittedVersionNumber,
|
|
@@ -60,7 +49,21 @@ export function format(
|
|
|
60
49
|
if (versionMetadata) {
|
|
61
50
|
meta.versionMetadata = versionMetadata
|
|
62
51
|
}
|
|
63
|
-
|
|
52
|
+
|
|
53
|
+
const main = Object.fromEntries(
|
|
54
|
+
Object.entries(v2Main).map(([key, value]) => {
|
|
55
|
+
if (value === undefined) {
|
|
56
|
+
return [key, null]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return [key, value]
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const data: FormAdapterSubmissionMessageData = {
|
|
64
|
+
main,
|
|
65
|
+
...v2Data
|
|
66
|
+
}
|
|
64
67
|
|
|
65
68
|
const result: FormAdapterSubmissionMessageResult = {
|
|
66
69
|
files: csvFiles
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
type DetailItemRepeat
|
|
10
10
|
} from '~/src/server/plugins/engine/models/types.js'
|
|
11
11
|
import { format } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'
|
|
12
|
+
import {
|
|
13
|
+
SummaryPageController,
|
|
14
|
+
getFormSubmissionData
|
|
15
|
+
} from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'
|
|
12
16
|
import { buildFormContextRequest } from '~/src/server/plugins/engine/pageControllers/__stubs__/request.js'
|
|
13
17
|
import {
|
|
14
18
|
FileStatus,
|
|
@@ -267,4 +271,41 @@ describe('getPersonalisation', () => {
|
|
|
267
271
|
expect(parsedBody.meta.referenceNumber).toBe('foobar')
|
|
268
272
|
expect(parsedBody.data).toEqual(expectedData)
|
|
269
273
|
})
|
|
274
|
+
|
|
275
|
+
it('should return the machine output 2', () => {
|
|
276
|
+
const pageDef = definition.pages[2]
|
|
277
|
+
const controller = new SummaryPageController(model, pageDef)
|
|
278
|
+
|
|
279
|
+
const summaryViewModel = controller.getSummaryViewModel(request, context)
|
|
280
|
+
|
|
281
|
+
const items = getFormSubmissionData(
|
|
282
|
+
summaryViewModel.context,
|
|
283
|
+
summaryViewModel.details
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
const body = format(context, items, model, submitResponse, formStatus)
|
|
287
|
+
|
|
288
|
+
const parsedBody = JSON.parse(body)
|
|
289
|
+
|
|
290
|
+
const expectedData = {
|
|
291
|
+
main: {
|
|
292
|
+
orderType: 'delivery'
|
|
293
|
+
},
|
|
294
|
+
repeaters: {
|
|
295
|
+
pizza: [
|
|
296
|
+
{
|
|
297
|
+
quantity: 2,
|
|
298
|
+
toppings: 'Ham'
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
quantity: 1,
|
|
302
|
+
toppings: 'Pepperoni'
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
},
|
|
306
|
+
files: {}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
expect(parsedBody.data).toEqual(expectedData)
|
|
310
|
+
})
|
|
270
311
|
})
|
|
@@ -74,7 +74,7 @@ export function format(
|
|
|
74
74
|
* }
|
|
75
75
|
* }
|
|
76
76
|
*/
|
|
77
|
-
function categoriseData(items: DetailItem[]) {
|
|
77
|
+
export function categoriseData(items: DetailItem[]) {
|
|
78
78
|
const output: {
|
|
79
79
|
main: Record<string, RichFormValue>
|
|
80
80
|
repeaters: Record<string, Record<string, RichFormValue>[]>
|
|
@@ -446,7 +446,7 @@ export type RichFormValue =
|
|
|
446
446
|
| UkAddressState
|
|
447
447
|
|
|
448
448
|
export interface FormAdapterSubmissionMessageData {
|
|
449
|
-
main: Record<string, RichFormValue>
|
|
449
|
+
main: Record<string, RichFormValue | null>
|
|
450
450
|
repeaters: Record<string, Record<string, RichFormValue>[]>
|
|
451
451
|
files: Record<string, FormAdapterFile[]>
|
|
452
452
|
}
|