@defra/forms-engine-plugin 4.0.29 → 4.0.31
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/FormModel.d.ts +2 -1
- package/.server/server/plugins/engine/models/FormModel.js +3 -0
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/models/__snapshots__/SummaryViewModel.test.ts.snap +17 -17
- package/.server/server/plugins/engine/pageControllers/PageController.js +3 -1
- package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
- package/.server/server/plugins/engine/views/summary.html +5 -2
- package/package.json +2 -2
- package/src/server/plugins/engine/models/FormModel.test.ts +70 -0
- package/src/server/plugins/engine/models/FormModel.ts +8 -1
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +45 -37
- package/src/server/plugins/engine/models/SummaryViewModel.ts +1 -1
- package/src/server/plugins/engine/models/__snapshots__/SummaryViewModel.test.ts.snap +17 -17
- package/src/server/plugins/engine/pageControllers/PageController.ts +3 -3
- package/src/server/plugins/engine/pageControllers/QuestionPageController.test.ts +7 -5
- package/src/server/plugins/engine/views/summary.html +5 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SchemaVersion, type ComponentDef, type ConditionWrapper, type ConditionWrapperV2, type ConditionsModelData, type Engine, type FormDefinition, type List, type Page } from '@defra/forms-model';
|
|
1
|
+
import { SchemaVersion, type ComponentDef, type ConditionWrapper, type ConditionWrapperV2, type ConditionsModelData, type Engine, type FormDefinition, type List, type Page, type Section } from '@defra/forms-model';
|
|
2
2
|
import { Parser, type Value } from 'expr-eval-fork';
|
|
3
3
|
import joi from 'joi';
|
|
4
4
|
import { type Component } from '~/src/server/plugins/engine/components/helpers/components.js';
|
|
@@ -51,6 +51,7 @@ export declare class FormModel {
|
|
|
51
51
|
toConditionContext(evaluationState: FormState, conditions: Partial<Record<string, ExecutableCondition>>): Extract<Value, Record<string, Value>>;
|
|
52
52
|
toConditionExpression(value: ConditionsModelData, parser: Parser): import("expr-eval-fork").Expression;
|
|
53
53
|
getList(nameOrId: string): List | undefined;
|
|
54
|
+
getSection(nameOrId: string): Section | undefined;
|
|
54
55
|
/**
|
|
55
56
|
* Form context for the current page
|
|
56
57
|
*/
|
|
@@ -198,6 +198,9 @@ export class FormModel {
|
|
|
198
198
|
getList(nameOrId) {
|
|
199
199
|
return this.schemaVersion === SchemaVersion.V1 ? this.lists.find(list => list.name === nameOrId) : this.lists.find(list => list.id === nameOrId);
|
|
200
200
|
}
|
|
201
|
+
getSection(nameOrId) {
|
|
202
|
+
return this.schemaVersion === SchemaVersion.V1 ? this.sections.find(section => section.name === nameOrId) : this.sections.find(section => section.id === nameOrId);
|
|
203
|
+
}
|
|
201
204
|
|
|
202
205
|
/**
|
|
203
206
|
* Form context for the current page
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","formId","values","basePath","versionNumber","ordnanceSurveyApiKey","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","emptyState","freeze","getContextValueFromState","key","keys","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","SaveAndExit","startsWith","External","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type Engine,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval-fork'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n formId: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: {\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n formId?: string\n },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.formId = options.formId ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.ordnanceSurveyApiKey = options.ordnanceSurveyApiKey\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormSubmissionState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // Initialise `evaluationState` for all keys using empty state.\n // This is because the current condition evaluation library (eval-expr)\n // will throw if an expression uses a key that is undefined.\n const emptyState = Object.freeze({})\n\n for (const page of this.pages) {\n const { collection, pageDef } = page\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(emptyState)\n )\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action &&\n ![FormAction.Validate, FormAction.SaveAndExit].includes(action) &&\n !action.startsWith(FormAction.External))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormSubmissionState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","formId","values","basePath","versionNumber","ordnanceSurveyApiKey","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getSection","section","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","emptyState","freeze","getContextValueFromState","key","keys","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","SaveAndExit","startsWith","External","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type Engine,\n type FormDefinition,\n type List,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval-fork'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n formId: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: {\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n formId?: string\n },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.formId = options.formId ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.ordnanceSurveyApiKey = options.ordnanceSurveyApiKey\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n getSection(nameOrId: string): Section | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.sections.find((section) => section.name === nameOrId)\n : this.sections.find((section) => section.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormSubmissionState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // Initialise `evaluationState` for all keys using empty state.\n // This is because the current condition evaluation library (eval-expr)\n // will throw if an expression uses a key that is undefined.\n const emptyState = Object.freeze({})\n\n for (const page of this.pages) {\n const { collection, pageDef } = page\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(emptyState)\n )\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action &&\n ![FormAction.Validate, FormAction.SaveAndExit].includes(action) &&\n !action.startsWith(FormAction.External))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormSubmissionState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -103,7 +103,7 @@ export class SummaryViewModel {
|
|
|
103
103
|
state
|
|
104
104
|
} = context;
|
|
105
105
|
const details = [];
|
|
106
|
-
[
|
|
106
|
+
[...sections, undefined].forEach(section => {
|
|
107
107
|
const items = [];
|
|
108
108
|
const sectionPages = relevantPages.filter(page => page.section === section);
|
|
109
109
|
sectionPages.forEach(page => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SummaryViewModel.js","names":["SchemaVersion","getAnswer","evaluateTemplate","getError","getPageHref","RepeatPageController","validationOptions","opts","SummaryViewModel","page","pageTitle","declaration","details","checkAnswers","context","name","backLink","feedbackLink","phaseTag","errors","serviceUrl","hasMissingNotificationEmail","components","allowSaveAndExit","constructor","request","model","basePath","def","sections","isForceAccess","title","schema","V2","result","makeFilteredSchema","relevantPages","validate","relevantState","stripUnknown","error","map","summaryDetails","detail","rows","items","item","push","href","text","classes","visuallyHiddenText","label","key","value","html","actions","undefined","summaryList","state","forEach","section","sectionPages","filter","collection","path","ItemRepeat","getSummaryPath","field","fields","ItemField","length","options","repeat","values","getListFromState","unit","returnUrl","subItems","repeatState","required","getFirstError"],"sources":["../../../../../src/server/plugins/engine/models/SummaryViewModel.ts"],"sourcesContent":["import { SchemaVersion, type Section } from '@defra/forms-model'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n type BackLink,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport {\n evaluateTemplate,\n getError,\n getPageHref\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type CheckAnswers,\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type SummaryListAction,\n type SummaryListRow\n} from '~/src/server/plugins/engine/types.js'\n\nexport class SummaryViewModel {\n /**\n * Responsible for parsing state values to the govuk-frontend summary list template\n */\n\n page: PageControllerClass\n pageTitle: string\n declaration?: string\n details: Detail[]\n checkAnswers: CheckAnswers[]\n context: FormContext\n name: string | undefined\n backLink?: BackLink\n feedbackLink?: string\n phaseTag?: string\n errors?: FormSubmissionError[]\n serviceUrl: string\n hasMissingNotificationEmail?: boolean\n components?: ComponentViewModel[]\n allowSaveAndExit = false\n\n constructor(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n ) {\n const { model } = page\n const { basePath, def, sections } = model\n const { isForceAccess } = context\n\n this.page = page\n this.pageTitle = page.title\n if (def.schema === SchemaVersion.V2 && !page.title) {\n this.pageTitle = 'Check your answers before sending your form'\n }\n\n this.serviceUrl = `/${basePath}`\n this.name = def.name\n this.declaration = def.declaration\n this.context = context\n\n const result = model\n .makeFilteredSchema(this.context.relevantPages)\n .validate(this.context.relevantState, { ...opts, stripUnknown: true })\n\n // Format errors\n this.errors = result.error?.details.map(getError)\n this.details = this.summaryDetails(request, sections)\n\n // Format check answers\n this.checkAnswers = this.details.map((detail): CheckAnswers => {\n const { title } = detail\n\n const rows = detail.items.map((item): SummaryListRow => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: item.href,\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: item.label\n })\n }\n\n return {\n key: {\n text: evaluateTemplate(item.title, context)\n },\n value: {\n classes: 'app-prose-scope',\n html: item.value || 'Not provided'\n },\n actions: {\n items\n }\n }\n })\n\n return {\n title: title ? { text: title } : undefined,\n summaryList: { rows }\n }\n })\n }\n\n private summaryDetails(request: FormContextRequest, sections: Section[]) {\n const { context, errors } = this\n const { relevantPages, state } = context\n\n const details: Detail[] = []\n\n ;[undefined, ...sections].forEach((section) => {\n const items: DetailItem[] = []\n\n const sectionPages = relevantPages.filter(\n (page) => page.section === section\n )\n\n sectionPages.forEach((page) => {\n const { collection, path } = page\n\n if (page instanceof RepeatPageController) {\n items.push(\n ItemRepeat(page, state, {\n path: page.getSummaryPath(request),\n errors\n })\n )\n } else {\n for (const field of collection.fields) {\n items.push(ItemField(page, state, field, { path, errors }))\n }\n }\n })\n\n if (items.length) {\n details.push({\n name: section?.name,\n title: section?.title,\n items\n })\n }\n })\n\n return details\n }\n}\n\n/**\n * Creates a repeater detail item\n * @see {@link DetailItemField}\n */\nfunction ItemRepeat(\n page: RepeatPageController,\n state: FormState,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemRepeat {\n const { collection, repeat } = page\n const { name, title } = repeat.options\n\n const values = page.getListFromState(state)\n const unit = values.length === 1 ? title : `${title}s`\n\n return {\n name,\n label: title,\n title: values.length ? `${unit} added` : unit,\n value: values.length ? `You added ${values.length} ${unit}` : '',\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n\n // Repeater field detail items\n subItems: values.map((repeatState) =>\n collection.fields.map((field) =>\n ItemField(page, repeatState, field, options)\n )\n )\n }\n}\n\n/**\n * Creates a form field detail item\n * @see {@link DetailItemField}\n */\nexport function ItemField(\n page: PageControllerClass,\n state: FormState,\n field: Field,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemField {\n return {\n name: field.name,\n label: field.title,\n title:\n field.options.required === false\n ? `${field.label} (optional)`\n : field.label,\n error: field.getFirstError(options.errors),\n value: getAnswer(field, state),\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n field\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"SummaryViewModel.js","names":["SchemaVersion","getAnswer","evaluateTemplate","getError","getPageHref","RepeatPageController","validationOptions","opts","SummaryViewModel","page","pageTitle","declaration","details","checkAnswers","context","name","backLink","feedbackLink","phaseTag","errors","serviceUrl","hasMissingNotificationEmail","components","allowSaveAndExit","constructor","request","model","basePath","def","sections","isForceAccess","title","schema","V2","result","makeFilteredSchema","relevantPages","validate","relevantState","stripUnknown","error","map","summaryDetails","detail","rows","items","item","push","href","text","classes","visuallyHiddenText","label","key","value","html","actions","undefined","summaryList","state","forEach","section","sectionPages","filter","collection","path","ItemRepeat","getSummaryPath","field","fields","ItemField","length","options","repeat","values","getListFromState","unit","returnUrl","subItems","repeatState","required","getFirstError"],"sources":["../../../../../src/server/plugins/engine/models/SummaryViewModel.ts"],"sourcesContent":["import { SchemaVersion, type Section } from '@defra/forms-model'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n type BackLink,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport {\n evaluateTemplate,\n getError,\n getPageHref\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type CheckAnswers,\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type SummaryListAction,\n type SummaryListRow\n} from '~/src/server/plugins/engine/types.js'\n\nexport class SummaryViewModel {\n /**\n * Responsible for parsing state values to the govuk-frontend summary list template\n */\n\n page: PageControllerClass\n pageTitle: string\n declaration?: string\n details: Detail[]\n checkAnswers: CheckAnswers[]\n context: FormContext\n name: string | undefined\n backLink?: BackLink\n feedbackLink?: string\n phaseTag?: string\n errors?: FormSubmissionError[]\n serviceUrl: string\n hasMissingNotificationEmail?: boolean\n components?: ComponentViewModel[]\n allowSaveAndExit = false\n\n constructor(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n ) {\n const { model } = page\n const { basePath, def, sections } = model\n const { isForceAccess } = context\n\n this.page = page\n this.pageTitle = page.title\n if (def.schema === SchemaVersion.V2 && !page.title) {\n this.pageTitle = 'Check your answers before sending your form'\n }\n\n this.serviceUrl = `/${basePath}`\n this.name = def.name\n this.declaration = def.declaration\n this.context = context\n\n const result = model\n .makeFilteredSchema(this.context.relevantPages)\n .validate(this.context.relevantState, { ...opts, stripUnknown: true })\n\n // Format errors\n this.errors = result.error?.details.map(getError)\n this.details = this.summaryDetails(request, sections)\n\n // Format check answers\n this.checkAnswers = this.details.map((detail): CheckAnswers => {\n const { title } = detail\n\n const rows = detail.items.map((item): SummaryListRow => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: item.href,\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: item.label\n })\n }\n\n return {\n key: {\n text: evaluateTemplate(item.title, context)\n },\n value: {\n classes: 'app-prose-scope',\n html: item.value || 'Not provided'\n },\n actions: {\n items\n }\n }\n })\n\n return {\n title: title ? { text: title } : undefined,\n summaryList: { rows }\n }\n })\n }\n\n private summaryDetails(request: FormContextRequest, sections: Section[]) {\n const { context, errors } = this\n const { relevantPages, state } = context\n\n const details: Detail[] = []\n\n ;[...sections, undefined].forEach((section) => {\n const items: DetailItem[] = []\n\n const sectionPages = relevantPages.filter(\n (page) => page.section === section\n )\n\n sectionPages.forEach((page) => {\n const { collection, path } = page\n\n if (page instanceof RepeatPageController) {\n items.push(\n ItemRepeat(page, state, {\n path: page.getSummaryPath(request),\n errors\n })\n )\n } else {\n for (const field of collection.fields) {\n items.push(ItemField(page, state, field, { path, errors }))\n }\n }\n })\n\n if (items.length) {\n details.push({\n name: section?.name,\n title: section?.title,\n items\n })\n }\n })\n\n return details\n }\n}\n\n/**\n * Creates a repeater detail item\n * @see {@link DetailItemField}\n */\nfunction ItemRepeat(\n page: RepeatPageController,\n state: FormState,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemRepeat {\n const { collection, repeat } = page\n const { name, title } = repeat.options\n\n const values = page.getListFromState(state)\n const unit = values.length === 1 ? title : `${title}s`\n\n return {\n name,\n label: title,\n title: values.length ? `${unit} added` : unit,\n value: values.length ? `You added ${values.length} ${unit}` : '',\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n\n // Repeater field detail items\n subItems: values.map((repeatState) =>\n collection.fields.map((field) =>\n ItemField(page, repeatState, field, options)\n )\n )\n }\n}\n\n/**\n * Creates a form field detail item\n * @see {@link DetailItemField}\n */\nexport function ItemField(\n page: PageControllerClass,\n state: FormState,\n field: Field,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemField {\n return {\n name: field.name,\n label: field.title,\n title:\n field.options.required === false\n ? `${field.label} (optional)`\n : field.label,\n error: field.getFirstError(options.errors),\n value: getAnswer(field, state),\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n field\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -2,51 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`SummaryViewModel Check answers (0 items) should use correct summary labels 1`] = `
|
|
4
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
5
|
{
|
|
12
6
|
"label": "Pizza",
|
|
13
7
|
"name": "pizza",
|
|
14
8
|
"title": "Pizzas",
|
|
15
9
|
"value": "",
|
|
16
10
|
},
|
|
17
|
-
]
|
|
18
|
-
`;
|
|
19
|
-
|
|
20
|
-
exports[`SummaryViewModel Check answers (1 item) should use correct summary labels 1`] = `
|
|
21
|
-
[
|
|
22
11
|
{
|
|
23
12
|
"label": "How would you like to receive your pizza?",
|
|
24
13
|
"name": "orderType",
|
|
25
14
|
"title": "How you would like to receive your pizza",
|
|
26
|
-
"value": "
|
|
15
|
+
"value": "Collection",
|
|
27
16
|
},
|
|
17
|
+
]
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
exports[`SummaryViewModel Check answers (1 item) should use correct summary labels 1`] = `
|
|
21
|
+
[
|
|
28
22
|
{
|
|
29
23
|
"label": "Pizza",
|
|
30
24
|
"name": "pizza",
|
|
31
25
|
"title": "Pizza added",
|
|
32
26
|
"value": "You added 1 Pizza",
|
|
33
27
|
},
|
|
34
|
-
]
|
|
35
|
-
`;
|
|
36
|
-
|
|
37
|
-
exports[`SummaryViewModel Check answers (2 items) should use correct summary labels 1`] = `
|
|
38
|
-
[
|
|
39
28
|
{
|
|
40
29
|
"label": "How would you like to receive your pizza?",
|
|
41
30
|
"name": "orderType",
|
|
42
31
|
"title": "How you would like to receive your pizza",
|
|
43
32
|
"value": "Delivery",
|
|
44
33
|
},
|
|
34
|
+
]
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
exports[`SummaryViewModel Check answers (2 items) should use correct summary labels 1`] = `
|
|
38
|
+
[
|
|
45
39
|
{
|
|
46
40
|
"label": "Pizza",
|
|
47
41
|
"name": "pizza",
|
|
48
42
|
"title": "Pizzas added",
|
|
49
43
|
"value": "You added 2 Pizzas",
|
|
50
44
|
},
|
|
45
|
+
{
|
|
46
|
+
"label": "How would you like to receive your pizza?",
|
|
47
|
+
"name": "orderType",
|
|
48
|
+
"title": "How you would like to receive your pizza",
|
|
49
|
+
"value": "Delivery",
|
|
50
|
+
},
|
|
51
51
|
]
|
|
52
52
|
`;
|
|
@@ -28,7 +28,9 @@ export class PageController {
|
|
|
28
28
|
this.events = pageDef.events;
|
|
29
29
|
|
|
30
30
|
// Resolve section
|
|
31
|
-
|
|
31
|
+
if (pageDef.section) {
|
|
32
|
+
this.section = model.getSection(pageDef.section);
|
|
33
|
+
}
|
|
32
34
|
|
|
33
35
|
// Resolve condition
|
|
34
36
|
if (pageDef.condition) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageController.js","names":["ControllerPath","Boom","getSaveAndExitHelpers","getStartPath","normalisePath","PageController","def","name","model","pageDef","title","section","condition","events","collection","viewName","allowSaveAndExit","constructor","
|
|
1
|
+
{"version":3,"file":"PageController.js","names":["ControllerPath","Boom","getSaveAndExitHelpers","getStartPath","normalisePath","PageController","def","name","model","pageDef","title","section","condition","events","collection","viewName","allowSaveAndExit","constructor","getSection","conditions","view","path","href","getHref","keys","getRouteOptions","postRouteOptions","viewModel","showTitle","pageTitle","sectionTitle","hideTitle","page","isStartPage","serviceUrl","feedbackLink","phaseTag","formId","phaseBanner","phase","basePath","relativeTargetPath","startsWith","substring","finalPath","replace","getSummaryPath","Summary","valueOf","getStatusPath","Status","makeGetRouteHandler","request","context","h","makePostRouteHandler","badRequest","shouldShowSaveAndExit","server","undefined"],"sources":["../../../../../src/server/plugins/engine/pageControllers/PageController.ts"],"sourcesContent":["import {\n ControllerPath,\n type Events,\n type FormDefinition,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type Lifecycle, type RouteOptions, type Server } from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n getSaveAndExitHelpers,\n getStartPath,\n normalisePath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FormContext,\n type PageViewModelBase\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class PageController {\n /**\n * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.\n */\n def: FormDefinition\n name?: string\n model: FormModel\n pageDef: Page\n title: string\n section?: Section\n condition?: ExecutableCondition\n events?: Events\n collection?: ComponentCollection\n viewName = 'index'\n allowSaveAndExit = false\n\n constructor(model: FormModel, pageDef: Page) {\n const { def } = model\n\n this.def = def\n this.name = def.name\n this.model = model\n this.pageDef = pageDef\n this.title = pageDef.title\n this.events = pageDef.events\n\n // Resolve section\n if (pageDef.section) {\n this.section = model.getSection(pageDef.section)\n }\n\n // Resolve condition\n if (pageDef.condition) {\n this.condition = model.conditions[pageDef.condition]\n }\n\n // Override view name\n if (pageDef.view) {\n this.viewName = pageDef.view\n }\n }\n\n get path() {\n return this.pageDef.path\n }\n\n get href() {\n const { path } = this\n return this.getHref(`/${normalisePath(path)}`)\n }\n\n get keys() {\n return this.collection?.keys ?? []\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {}\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {}\n }\n\n get viewModel(): PageViewModelBase {\n const { name, section, title } = this\n\n const showTitle = true\n const pageTitle = title\n const sectionTitle = section?.hideTitle !== true ? section?.title : ''\n\n return {\n name,\n page: this,\n pageTitle,\n sectionTitle,\n showTitle,\n isStartPage: false,\n serviceUrl: this.getHref('/'),\n feedbackLink: this.feedbackLink,\n phaseTag: this.phaseTag\n }\n }\n\n get feedbackLink() {\n return `/form/feedback?formId=${this.model.formId}`\n }\n\n get phaseTag() {\n const { def } = this\n return def.phaseBanner?.phase\n }\n\n getHref(path: string): string {\n const basePath = this.model.basePath\n\n if (path === '/') {\n return `/${basePath}`\n }\n\n // if ever the path is not prefixed with a slash, add it\n const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path\n let finalPath = `/${basePath}`\n if (relativeTargetPath) {\n finalPath += `/${relativeTargetPath}`\n }\n finalPath = finalPath.replace(/\\/{2,}/g, '/')\n\n return finalPath\n }\n\n getStartPath() {\n return getStartPath(this.model)\n }\n\n getSummaryPath() {\n return ControllerPath.Summary.valueOf()\n }\n\n getStatusPath() {\n return ControllerPath.Status.valueOf()\n }\n\n makeGetRouteHandler(): (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => ReturnType<Lifecycle.Method<FormRequestRefs>> {\n return (request, context, h) => {\n const { viewModel, viewName } = this\n return h.view(viewName, viewModel)\n }\n }\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {\n throw Boom.badRequest('Unsupported POST route handler for this page')\n }\n\n shouldShowSaveAndExit(server: Server): boolean {\n return getSaveAndExitHelpers(server) !== undefined && this.allowSaveAndExit\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -33,9 +33,12 @@
|
|
|
33
33
|
<h2 class="govuk-heading-m">
|
|
34
34
|
{{ section.title.text }}
|
|
35
35
|
</h2>
|
|
36
|
+
{{ govukSummaryList(section.summaryList) }}
|
|
37
|
+
{% else %}
|
|
38
|
+
<div class="{% if not loop.first %}govuk-!-margin-top-9 govuk-!-padding-top-4{% endif %}">
|
|
39
|
+
{{ govukSummaryList(section.summaryList) }}
|
|
40
|
+
</div>
|
|
36
41
|
{% endif %}
|
|
37
|
-
|
|
38
|
-
{{ govukSummaryList(section.summaryList) }}
|
|
39
42
|
{% endfor %}
|
|
40
43
|
|
|
41
44
|
<form method="post" novalidate>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.31",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"license": "SEE LICENSE IN LICENSE",
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@defra/forms-model": "^3.0.
|
|
73
|
+
"@defra/forms-model": "^3.0.597",
|
|
74
74
|
"@defra/hapi-tracing": "^1.29.0",
|
|
75
75
|
"@elastic/ecs-pino-format": "^1.5.0",
|
|
76
76
|
"@hapi/boom": "^10.0.1",
|
|
@@ -716,4 +716,74 @@ describe('FormModel - Joined Conditions', () => {
|
|
|
716
716
|
expect(Object.keys(model.conditions)).toHaveLength(3)
|
|
717
717
|
})
|
|
718
718
|
})
|
|
719
|
+
|
|
720
|
+
describe('getSection', () => {
|
|
721
|
+
it('should look up section by name for V1 schema', () => {
|
|
722
|
+
const v1Definition = {
|
|
723
|
+
...definition,
|
|
724
|
+
sections: [
|
|
725
|
+
{ name: 'personal', title: 'Personal details' },
|
|
726
|
+
{ name: 'contact', title: 'Contact details' }
|
|
727
|
+
]
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const model = new FormModel(v1Definition, { basePath: 'test' })
|
|
731
|
+
|
|
732
|
+
expect(model.getSection('personal')).toEqual(
|
|
733
|
+
expect.objectContaining({
|
|
734
|
+
name: 'personal',
|
|
735
|
+
title: 'Personal details'
|
|
736
|
+
})
|
|
737
|
+
)
|
|
738
|
+
expect(model.getSection('contact')).toEqual(
|
|
739
|
+
expect.objectContaining({
|
|
740
|
+
name: 'contact',
|
|
741
|
+
title: 'Contact details'
|
|
742
|
+
})
|
|
743
|
+
)
|
|
744
|
+
expect(model.getSection('nonexistent')).toBeUndefined()
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
it('should look up section by ID for V2 schema', () => {
|
|
748
|
+
const v2Definition = {
|
|
749
|
+
...definitionV2,
|
|
750
|
+
sections: [
|
|
751
|
+
{
|
|
752
|
+
id: 'a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d',
|
|
753
|
+
name: 'personal',
|
|
754
|
+
title: 'Personal details'
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
id: 'b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e',
|
|
758
|
+
name: 'contact',
|
|
759
|
+
title: 'Contact details'
|
|
760
|
+
}
|
|
761
|
+
]
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
formDefinitionV2Schema.validate = jest
|
|
765
|
+
.fn()
|
|
766
|
+
.mockReturnValue({ value: v2Definition })
|
|
767
|
+
|
|
768
|
+
const model = new FormModel(v2Definition, { basePath: 'test' })
|
|
769
|
+
|
|
770
|
+
expect(model.getSection('a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d')).toEqual(
|
|
771
|
+
expect.objectContaining({
|
|
772
|
+
id: 'a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d',
|
|
773
|
+
name: 'personal',
|
|
774
|
+
title: 'Personal details'
|
|
775
|
+
})
|
|
776
|
+
)
|
|
777
|
+
expect(model.getSection('b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e')).toEqual(
|
|
778
|
+
expect.objectContaining({
|
|
779
|
+
id: 'b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e',
|
|
780
|
+
name: 'contact',
|
|
781
|
+
title: 'Contact details'
|
|
782
|
+
})
|
|
783
|
+
)
|
|
784
|
+
// V2 should not find by name
|
|
785
|
+
expect(model.getSection('personal')).toBeUndefined()
|
|
786
|
+
expect(model.getSection('nonexistent')).toBeUndefined()
|
|
787
|
+
})
|
|
788
|
+
})
|
|
719
789
|
})
|
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
type Engine,
|
|
22
22
|
type FormDefinition,
|
|
23
23
|
type List,
|
|
24
|
-
type Page
|
|
24
|
+
type Page,
|
|
25
|
+
type Section
|
|
25
26
|
} from '@defra/forms-model'
|
|
26
27
|
import { add, format } from 'date-fns'
|
|
27
28
|
import { Parser, type Value } from 'expr-eval-fork'
|
|
@@ -321,6 +322,12 @@ export class FormModel {
|
|
|
321
322
|
: this.lists.find((list) => list.id === nameOrId)
|
|
322
323
|
}
|
|
323
324
|
|
|
325
|
+
getSection(nameOrId: string): Section | undefined {
|
|
326
|
+
return this.schemaVersion === SchemaVersion.V1
|
|
327
|
+
? this.sections.find((section) => section.name === nameOrId)
|
|
328
|
+
: this.sections.find((section) => section.id === nameOrId)
|
|
329
|
+
}
|
|
330
|
+
|
|
324
331
|
/**
|
|
325
332
|
* Form context for the current page
|
|
326
333
|
*/
|
|
@@ -140,13 +140,13 @@ describe('SummaryViewModel', () => {
|
|
|
140
140
|
it('should add title for each section', () => {
|
|
141
141
|
const [checkAnswers1, checkAnswers2] = summaryViewModel.checkAnswers
|
|
142
142
|
|
|
143
|
-
// 1st summary list has
|
|
144
|
-
expect(checkAnswers1).toHaveProperty('title',
|
|
145
|
-
|
|
146
|
-
// 2nd summary list has section title
|
|
147
|
-
expect(checkAnswers2).toHaveProperty('title', {
|
|
143
|
+
// 1st summary list has section title
|
|
144
|
+
expect(checkAnswers1).toHaveProperty('title', {
|
|
148
145
|
text: 'Food'
|
|
149
146
|
})
|
|
147
|
+
|
|
148
|
+
// 2nd summary list has no title (unsectioned questions at bottom)
|
|
149
|
+
expect(checkAnswers2).toHaveProperty('title', undefined)
|
|
150
150
|
})
|
|
151
151
|
|
|
152
152
|
it('should add summary list for each section', () => {
|
|
@@ -157,44 +157,46 @@ describe('SummaryViewModel', () => {
|
|
|
157
157
|
const { summaryList: summaryList1 } = checkAnswers1
|
|
158
158
|
const { summaryList: summaryList2 } = checkAnswers2
|
|
159
159
|
|
|
160
|
+
// 1st summary list contains sectioned questions (Food section)
|
|
160
161
|
expect(summaryList1).toHaveProperty('rows', [
|
|
161
162
|
{
|
|
162
163
|
key: {
|
|
163
|
-
text: keys[
|
|
164
|
+
text: keys[1]
|
|
164
165
|
},
|
|
165
166
|
value: {
|
|
166
167
|
classes: 'app-prose-scope',
|
|
167
|
-
html: values[
|
|
168
|
+
html: values[1]
|
|
168
169
|
},
|
|
169
170
|
actions: {
|
|
170
171
|
items: [
|
|
171
172
|
{
|
|
172
173
|
classes: 'govuk-link--no-visited-state',
|
|
173
|
-
href: `${basePath}/
|
|
174
|
+
href: `${basePath}/pizza-order/summary?returnUrl=${encodeURIComponent(`${basePath}/summary`)}`,
|
|
174
175
|
text: 'Change',
|
|
175
|
-
visuallyHiddenText:
|
|
176
|
+
visuallyHiddenText: 'Pizza'
|
|
176
177
|
}
|
|
177
178
|
]
|
|
178
179
|
}
|
|
179
180
|
}
|
|
180
181
|
])
|
|
181
182
|
|
|
183
|
+
// 2nd summary list contains unsectioned questions (at bottom)
|
|
182
184
|
expect(summaryList2).toHaveProperty('rows', [
|
|
183
185
|
{
|
|
184
186
|
key: {
|
|
185
|
-
text: keys[
|
|
187
|
+
text: keys[2]
|
|
186
188
|
},
|
|
187
189
|
value: {
|
|
188
190
|
classes: 'app-prose-scope',
|
|
189
|
-
html: values[
|
|
191
|
+
html: values[0]
|
|
190
192
|
},
|
|
191
193
|
actions: {
|
|
192
194
|
items: [
|
|
193
195
|
{
|
|
194
196
|
classes: 'govuk-link--no-visited-state',
|
|
195
|
-
href: `${basePath}/
|
|
197
|
+
href: `${basePath}/delivery-or-collection?returnUrl=${encodeURIComponent(`${basePath}/summary`)}`,
|
|
196
198
|
text: 'Change',
|
|
197
|
-
visuallyHiddenText:
|
|
199
|
+
visuallyHiddenText: keys[0]
|
|
198
200
|
}
|
|
199
201
|
]
|
|
200
202
|
}
|
|
@@ -214,14 +216,15 @@ describe('SummaryViewModel', () => {
|
|
|
214
216
|
const { summaryList: summaryList1 } = checkAnswers1
|
|
215
217
|
const { summaryList: summaryList2 } = checkAnswers2
|
|
216
218
|
|
|
219
|
+
// 1st summary list contains sectioned questions (Food section)
|
|
217
220
|
expect(summaryList1).toHaveProperty('rows', [
|
|
218
221
|
{
|
|
219
222
|
key: {
|
|
220
|
-
text: keys[
|
|
223
|
+
text: keys[1]
|
|
221
224
|
},
|
|
222
225
|
value: {
|
|
223
226
|
classes: 'app-prose-scope',
|
|
224
|
-
html: values[
|
|
227
|
+
html: values[1]
|
|
225
228
|
},
|
|
226
229
|
actions: {
|
|
227
230
|
items: []
|
|
@@ -229,14 +232,15 @@ describe('SummaryViewModel', () => {
|
|
|
229
232
|
}
|
|
230
233
|
])
|
|
231
234
|
|
|
235
|
+
// 2nd summary list contains unsectioned questions (at bottom)
|
|
232
236
|
expect(summaryList2).toHaveProperty('rows', [
|
|
233
237
|
{
|
|
234
238
|
key: {
|
|
235
|
-
text: keys[
|
|
239
|
+
text: keys[2]
|
|
236
240
|
},
|
|
237
241
|
value: {
|
|
238
242
|
classes: 'app-prose-scope',
|
|
239
|
-
html: values[
|
|
243
|
+
html: values[0]
|
|
240
244
|
},
|
|
241
245
|
actions: {
|
|
242
246
|
items: []
|
|
@@ -254,32 +258,34 @@ describe('SummaryViewModel', () => {
|
|
|
254
258
|
|
|
255
259
|
const [details1, details2] = summaryViewModel.details
|
|
256
260
|
|
|
261
|
+
// 1st details contains sectioned questions (Food section)
|
|
257
262
|
expect(details1.items[0]).toMatchObject({
|
|
258
|
-
name: names[0],
|
|
259
|
-
value: answers[0],
|
|
260
|
-
title: keys[2],
|
|
261
|
-
label: keys[0]
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
expect(details2.items[0]).toMatchObject({
|
|
265
263
|
name: names[1],
|
|
266
264
|
value: answers[1],
|
|
267
265
|
title: keys[1],
|
|
268
266
|
label: keys[4]
|
|
269
267
|
})
|
|
270
268
|
|
|
269
|
+
// 2nd details contains unsectioned questions (at bottom)
|
|
270
|
+
expect(details2.items[0]).toMatchObject({
|
|
271
|
+
name: names[0],
|
|
272
|
+
value: answers[0],
|
|
273
|
+
title: keys[2],
|
|
274
|
+
label: keys[0]
|
|
275
|
+
})
|
|
276
|
+
|
|
271
277
|
const snapshot = [
|
|
272
|
-
{
|
|
273
|
-
name: names[0],
|
|
274
|
-
value: answers[0],
|
|
275
|
-
title: keys[2],
|
|
276
|
-
label: keys[0]
|
|
277
|
-
},
|
|
278
278
|
{
|
|
279
279
|
name: names[1],
|
|
280
280
|
value: answers[1],
|
|
281
281
|
title: keys[1],
|
|
282
282
|
label: keys[4]
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: names[0],
|
|
286
|
+
value: answers[0],
|
|
287
|
+
title: keys[2],
|
|
288
|
+
label: keys[0]
|
|
283
289
|
}
|
|
284
290
|
]
|
|
285
291
|
|
|
@@ -316,19 +322,21 @@ describe('SummaryViewModel', () => {
|
|
|
316
322
|
|
|
317
323
|
const [details1, details2] = summaryViewModel.details
|
|
318
324
|
|
|
325
|
+
// 1st details contains sectioned questions (Food section)
|
|
319
326
|
expect(details1.items[0]).toMatchObject({
|
|
320
|
-
name: 'orderType',
|
|
321
|
-
value: 'Collection',
|
|
322
|
-
title: 'How you would like to receive your pizza (optional)',
|
|
323
|
-
label: 'How would you like to receive your pizza?'
|
|
324
|
-
})
|
|
325
|
-
|
|
326
|
-
expect(details2.items[0]).toMatchObject({
|
|
327
327
|
name: 'pizza',
|
|
328
328
|
value: '',
|
|
329
329
|
title: 'Pizzas',
|
|
330
330
|
label: 'Pizza'
|
|
331
331
|
})
|
|
332
|
+
|
|
333
|
+
// 2nd details contains unsectioned questions (at bottom)
|
|
334
|
+
expect(details2.items[0]).toMatchObject({
|
|
335
|
+
name: 'orderType',
|
|
336
|
+
value: 'Collection',
|
|
337
|
+
title: 'How you would like to receive your pizza (optional)',
|
|
338
|
+
label: 'How would you like to receive your pizza?'
|
|
339
|
+
})
|
|
332
340
|
})
|
|
333
341
|
})
|
|
334
342
|
|
|
@@ -125,7 +125,7 @@ export class SummaryViewModel {
|
|
|
125
125
|
|
|
126
126
|
const details: Detail[] = []
|
|
127
127
|
|
|
128
|
-
;[
|
|
128
|
+
;[...sections, undefined].forEach((section) => {
|
|
129
129
|
const items: DetailItem[] = []
|
|
130
130
|
|
|
131
131
|
const sectionPages = relevantPages.filter(
|
|
@@ -2,51 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`SummaryViewModel Check answers (0 items) should use correct summary labels 1`] = `
|
|
4
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
5
|
{
|
|
12
6
|
"label": "Pizza",
|
|
13
7
|
"name": "pizza",
|
|
14
8
|
"title": "Pizzas",
|
|
15
9
|
"value": "",
|
|
16
10
|
},
|
|
17
|
-
]
|
|
18
|
-
`;
|
|
19
|
-
|
|
20
|
-
exports[`SummaryViewModel Check answers (1 item) should use correct summary labels 1`] = `
|
|
21
|
-
[
|
|
22
11
|
{
|
|
23
12
|
"label": "How would you like to receive your pizza?",
|
|
24
13
|
"name": "orderType",
|
|
25
14
|
"title": "How you would like to receive your pizza",
|
|
26
|
-
"value": "
|
|
15
|
+
"value": "Collection",
|
|
27
16
|
},
|
|
17
|
+
]
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
exports[`SummaryViewModel Check answers (1 item) should use correct summary labels 1`] = `
|
|
21
|
+
[
|
|
28
22
|
{
|
|
29
23
|
"label": "Pizza",
|
|
30
24
|
"name": "pizza",
|
|
31
25
|
"title": "Pizza added",
|
|
32
26
|
"value": "You added 1 Pizza",
|
|
33
27
|
},
|
|
34
|
-
]
|
|
35
|
-
`;
|
|
36
|
-
|
|
37
|
-
exports[`SummaryViewModel Check answers (2 items) should use correct summary labels 1`] = `
|
|
38
|
-
[
|
|
39
28
|
{
|
|
40
29
|
"label": "How would you like to receive your pizza?",
|
|
41
30
|
"name": "orderType",
|
|
42
31
|
"title": "How you would like to receive your pizza",
|
|
43
32
|
"value": "Delivery",
|
|
44
33
|
},
|
|
34
|
+
]
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
exports[`SummaryViewModel Check answers (2 items) should use correct summary labels 1`] = `
|
|
38
|
+
[
|
|
45
39
|
{
|
|
46
40
|
"label": "Pizza",
|
|
47
41
|
"name": "pizza",
|
|
48
42
|
"title": "Pizzas added",
|
|
49
43
|
"value": "You added 2 Pizzas",
|
|
50
44
|
},
|
|
45
|
+
{
|
|
46
|
+
"label": "How would you like to receive your pizza?",
|
|
47
|
+
"name": "orderType",
|
|
48
|
+
"title": "How you would like to receive your pizza",
|
|
49
|
+
"value": "Delivery",
|
|
50
|
+
},
|
|
51
51
|
]
|
|
52
52
|
`;
|
|
@@ -55,9 +55,9 @@ export class PageController {
|
|
|
55
55
|
this.events = pageDef.events
|
|
56
56
|
|
|
57
57
|
// Resolve section
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
if (pageDef.section) {
|
|
59
|
+
this.section = model.getSection(pageDef.section)
|
|
60
|
+
}
|
|
61
61
|
|
|
62
62
|
// Resolve condition
|
|
63
63
|
if (pageDef.condition) {
|
|
@@ -1007,11 +1007,13 @@ describe('QuestionPageController V2', () => {
|
|
|
1007
1007
|
|
|
1008
1008
|
it('returns the page section', () => {
|
|
1009
1009
|
expect(controller1).toHaveProperty('section', undefined)
|
|
1010
|
-
expect(controller2).
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1010
|
+
expect(controller2.section).toEqual(
|
|
1011
|
+
expect.objectContaining({
|
|
1012
|
+
name: 'marriage',
|
|
1013
|
+
title: 'Your marriage',
|
|
1014
|
+
hideTitle: false
|
|
1015
|
+
})
|
|
1016
|
+
)
|
|
1015
1017
|
})
|
|
1016
1018
|
})
|
|
1017
1019
|
|
|
@@ -33,9 +33,12 @@
|
|
|
33
33
|
<h2 class="govuk-heading-m">
|
|
34
34
|
{{ section.title.text }}
|
|
35
35
|
</h2>
|
|
36
|
+
{{ govukSummaryList(section.summaryList) }}
|
|
37
|
+
{% else %}
|
|
38
|
+
<div class="{% if not loop.first %}govuk-!-margin-top-9 govuk-!-padding-top-4{% endif %}">
|
|
39
|
+
{{ govukSummaryList(section.summaryList) }}
|
|
40
|
+
</div>
|
|
36
41
|
{% endif %}
|
|
37
|
-
|
|
38
|
-
{{ govukSummaryList(section.summaryList) }}
|
|
39
42
|
{% endfor %}
|
|
40
43
|
|
|
41
44
|
<form method="post" novalidate>
|