@defra/forms-engine-plugin 3.0.5 → 3.0.7

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.
@@ -1,4 +1,4 @@
1
- import { Engine, SchemaVersion, type ComponentDef, type ConditionWrapper, type ConditionWrapperV2, type ConditionsModelData, 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 } from '@defra/forms-model';
2
2
  import { Parser, type Value } from 'expr-eval';
3
3
  import joi from 'joi';
4
4
  import { type Component } from '~/src/server/plugins/engine/components/helpers/components.js';
@@ -1,4 +1,4 @@
1
- import { ComponentType, ConditionsModel, ControllerPath, ControllerType, Engine, SchemaVersion, convertConditionWrapperFromV2, formDefinitionSchema, formDefinitionV2Schema, generateConditionAlias, hasComponents, hasRepeater, isConditionWrapperV2, yesNoListId, yesNoListName } from '@defra/forms-model';
1
+ import { ComponentType, ConditionsModel, ControllerPath, ControllerType, SchemaVersion, convertConditionWrapperFromV2, formDefinitionSchema, formDefinitionV2Schema, generateConditionAlias, hasComponents, hasRepeater, isConditionWrapperV2, yesNoListId, yesNoListName } from '@defra/forms-model';
2
2
  import { add, format } from 'date-fns';
3
3
  import { Parser } from 'expr-eval';
4
4
  import joi from 'joi';
@@ -260,14 +260,17 @@ export class FormModel {
260
260
  return context;
261
261
  }
262
262
  initialiseContext(context) {
263
- // For the V2 engine, we need to initialise `evaluationState` to null
264
- // for all keys. This is because the current condition evaluation
265
- // library (eval-expr) will throw if an expression uses a key that is undefined.
266
- if (this.engine === Engine.V2) {
267
- for (const page of this.pages) {
268
- for (const key of page.keys) {
269
- context.evaluationState[key] = null;
270
- }
263
+ // Initialise `evaluationState` for all keys using empty state.
264
+ // This is because the current condition evaluation library (eval-expr)
265
+ // will throw if an expression uses a key that is undefined.
266
+ const emptyState = Object.freeze({});
267
+ for (const page of this.pages) {
268
+ const {
269
+ collection,
270
+ pageDef
271
+ } = page;
272
+ if (!hasRepeater(pageDef)) {
273
+ Object.assign(context.evaluationState, collection.getContextValueFromState(emptyState));
271
274
  }
272
275
  }
273
276
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","Engine","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","values","basePath","versionNumber","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","key","keys","getContextValueFromState","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","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n Engine,\n 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 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'\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 values: FormDefinition\n basePath: string\n versionNumber?: number\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: { basePath: string; versionNumber?: number },\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.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\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: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n 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 // For the V2 engine, we need to initialise `evaluationState` to null\n // for all keys. This is because the current condition evaluation\n // library (eval-expr) will throw if an expression uses a key that is undefined.\n if (this.engine === Engine.V2) {\n for (const page of this.pages) {\n for (const key of page.keys) {\n context.evaluationState[key] = null\n }\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n 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 && ![FormAction.Validate, FormAction.SaveAndExit].includes(action))\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: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
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","values","basePath","versionNumber","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","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'\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 values: FormDefinition\n basePath: string\n versionNumber?: number\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: { basePath: string; versionNumber?: number },\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.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\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: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n 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 && ![FormAction.Validate, FormAction.SaveAndExit].includes(action))\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: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "3.0.5",
3
+ "version": "3.0.7",
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.552",
73
+ "@defra/forms-model": "^3.0.555",
74
74
  "@defra/hapi-tracing": "^1.26.0",
75
75
  "@elastic/ecs-pino-format": "^1.5.0",
76
76
  "@hapi/boom": "^10.0.1",
@@ -3,7 +3,6 @@ import {
3
3
  ConditionsModel,
4
4
  ControllerPath,
5
5
  ControllerType,
6
- Engine,
7
6
  SchemaVersion,
8
7
  convertConditionWrapperFromV2,
9
8
  formDefinitionSchema,
@@ -19,6 +18,7 @@ import {
19
18
  type ConditionWrapperV2,
20
19
  type ConditionsModelData,
21
20
  type DateUnits,
21
+ type Engine,
22
22
  type FormDefinition,
23
23
  type List,
24
24
  type Page
@@ -389,14 +389,19 @@ export class FormModel {
389
389
  }
390
390
 
391
391
  private initialiseContext(context: FormContext) {
392
- // For the V2 engine, we need to initialise `evaluationState` to null
393
- // for all keys. This is because the current condition evaluation
394
- // library (eval-expr) will throw if an expression uses a key that is undefined.
395
- if (this.engine === Engine.V2) {
396
- for (const page of this.pages) {
397
- for (const key of page.keys) {
398
- context.evaluationState[key] = null
399
- }
392
+ // Initialise `evaluationState` for all keys using empty state.
393
+ // This is because the current condition evaluation library (eval-expr)
394
+ // will throw if an expression uses a key that is undefined.
395
+ const emptyState = Object.freeze({})
396
+
397
+ for (const page of this.pages) {
398
+ const { collection, pageDef } = page
399
+
400
+ if (!hasRepeater(pageDef)) {
401
+ Object.assign(
402
+ context.evaluationState,
403
+ collection.getContextValueFromState(emptyState)
404
+ )
400
405
  }
401
406
  }
402
407
  }
@@ -1,5 +1,6 @@
1
1
  import { type PageQuestion } from '@defra/forms-model'
2
2
 
3
+ import { getForm } from '~/src/server/plugins/engine/configureEnginePlugin.js'
3
4
  import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
4
5
  import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
5
6
  import {
@@ -8,6 +9,8 @@ import {
8
9
  } from '~/src/server/plugins/engine/pageControllers/__stubs__/request.js'
9
10
  import { serverWithSaveAndExit } from '~/src/server/plugins/engine/pageControllers/__stubs__/server.js'
10
11
  import {
12
+ FileStatus,
13
+ UploadStatus,
11
14
  type FormContext,
12
15
  type FormPageViewModel,
13
16
  type FormState,
@@ -321,7 +324,11 @@ describe('QuestionPageController', () => {
321
324
  'AddressLine2',
322
325
  'Town',
323
326
  'Postcode'
324
- ]
327
+ ],
328
+ applicantTwoAddress: null,
329
+ applicantTwoFirstName: null,
330
+ applicantTwoLastName: null,
331
+ applicantTwoMiddleName: null
325
332
  })
326
333
 
327
334
  // Our context should know which pages are relevant
@@ -358,15 +365,18 @@ describe('QuestionPageController', () => {
358
365
  '/summary'
359
366
  ])
360
367
 
361
- // Our context should no longer know anything about our applicant
362
- expect(stateAfter).not.toHaveProperty('numberOfApplicants')
363
- expect(stateAfter).not.toHaveProperty('applicantOneFirstName')
364
- expect(stateAfter).not.toHaveProperty('applicantOneMiddleName')
365
- expect(stateAfter).not.toHaveProperty('applicantOneLastName')
366
- expect(stateAfter).not.toHaveProperty('applicantOneAddress')
367
-
368
+ // Our evaluation context should have default values for irrelevant fields
368
369
  expect(stateAfter).toEqual({
369
- ukPassport: false
370
+ ukPassport: false,
371
+ numberOfApplicants: null,
372
+ applicantOneFirstName: null,
373
+ applicantOneMiddleName: null,
374
+ applicantOneLastName: null,
375
+ applicantOneAddress: null,
376
+ applicantTwoAddress: null,
377
+ applicantTwoFirstName: null,
378
+ applicantTwoLastName: null,
379
+ applicantTwoMiddleName: null
370
380
  })
371
381
  })
372
382
 
@@ -521,6 +531,206 @@ describe('QuestionPageController', () => {
521
531
  }
522
532
  ])
523
533
  })
534
+
535
+ it('correctly initialises default values', async () => {
536
+ const components = await getForm(
537
+ '../../../../test/form/definitions/components.json'
538
+ )
539
+ const { pages } = components
540
+
541
+ const model = new FormModel(components, {
542
+ basePath: 'test'
543
+ })
544
+
545
+ const controller = new QuestionPageController(model, pages[0])
546
+
547
+ const state: FormSubmissionState = { $$__referenceNumber: 'foobar' }
548
+
549
+ let request = buildFormContextRequest({
550
+ method: 'get',
551
+ url: new URL('http://example.com/test/all-components'),
552
+ path: '/test/all-components',
553
+ params: {
554
+ path: 'all-components',
555
+ slug: 'test'
556
+ },
557
+ query: {},
558
+ app: { model }
559
+ })
560
+
561
+ // Calculate our context based on the page we're attempting to load and the above state we provide
562
+ let context = controller.model.getFormContext(request, state)
563
+
564
+ // Context paths should be all pages up to the one we requested
565
+ expect(context.paths).toEqual(['/all-components'])
566
+
567
+ // Our context should have default values for all input fields
568
+ expect(context.evaluationState).toEqual({
569
+ textField: null,
570
+ multilineTextField: null,
571
+ numberField: null,
572
+ datePartsField: null,
573
+ monthYearField: null,
574
+ yesNoField: null,
575
+ emailAddressField: null,
576
+ telephoneNumberField: null,
577
+ addressField: null,
578
+ radiosField: null,
579
+ selectField: null,
580
+ autocompleteField: null,
581
+ checkboxesSingle: [],
582
+ checkboxesMultiple: [],
583
+ checkboxesSingleNumber: [],
584
+ checkboxesMultipleNumber: [],
585
+ fileUpload: null
586
+ })
587
+
588
+ Object.assign(state, {
589
+ textField: 'Text field',
590
+ multilineTextField: 'Multiline text field',
591
+ numberField: 1,
592
+ datePartsField__day: 12,
593
+ datePartsField__month: 12,
594
+ datePartsField__year: 2012,
595
+ monthYearField__month: 12,
596
+ monthYearField__year: 2012,
597
+ yesNoField: 'true',
598
+ emailAddressField: 'user@email.com',
599
+ telephoneNumberField: '+447900000000',
600
+ addressField__addressLine1: 'Address line 1',
601
+ addressField__addressLine2: 'Address line 2',
602
+ addressField__town: 'Town or city',
603
+ addressField__county: 'Cheshire',
604
+ addressField__postcode: 'CW1 1AB',
605
+ radiosField: 'privateLimitedCompany',
606
+ selectField: 910400000,
607
+ autocompleteField: 910400044,
608
+ checkboxesSingle: ['Shetland'],
609
+ checkboxesMultiple: ['Arabian', 'Shire', 'Race'],
610
+ checkboxesSingleNumber: [1],
611
+ checkboxesMultipleNumber: [0, 1]
612
+ })
613
+
614
+ request = buildFormContextRequest({
615
+ method: 'get',
616
+ url: new URL('http://example.com/test/methodology-statement'),
617
+ path: '/test/methodology-statement',
618
+ params: {
619
+ path: 'methodology-statement',
620
+ slug: 'test'
621
+ },
622
+ query: {},
623
+ app: { model }
624
+ })
625
+
626
+ // Calculate our context based on the page we're attempting to load and the above state we provide
627
+ context = controller.model.getFormContext(request, state)
628
+
629
+ // Context paths should be all pages up to the one we requested
630
+ expect(context.paths).toEqual([
631
+ '/all-components',
632
+ '/methodology-statement'
633
+ ])
634
+
635
+ // Our context should have evaluation state values for relevant fields and default values for everything else
636
+ expect(context.evaluationState).toEqual({
637
+ textField: 'Text field',
638
+ multilineTextField: 'Multiline text field',
639
+ numberField: 1,
640
+ datePartsField: '2012-12-12',
641
+ monthYearField: '2012-12',
642
+ yesNoField: null,
643
+ emailAddressField: 'user@email.com',
644
+ telephoneNumberField: '+447900000000',
645
+ addressField: [
646
+ 'Address line 1',
647
+ 'Address line 2',
648
+ 'Town or city',
649
+ 'Cheshire',
650
+ 'CW1 1AB'
651
+ ],
652
+ radiosField: 'privateLimitedCompany',
653
+ selectField: 910400000,
654
+ autocompleteField: 910400044,
655
+ checkboxesSingle: ['Shetland'],
656
+ checkboxesMultiple: ['Arabian', 'Shire', 'Race'],
657
+ checkboxesSingleNumber: [1],
658
+ checkboxesMultipleNumber: [0, 1],
659
+ fileUpload: null
660
+ })
661
+
662
+ Object.assign(state, {
663
+ fileUpload: [
664
+ {
665
+ uploadId: '348e1878-59c0-4d2e-a52b-e5042ad729f0',
666
+ status: {
667
+ uploadStatus: UploadStatus.ready,
668
+ metadata: {
669
+ retrievalKey: 'enrique.chase@defra.gov.uk'
670
+ },
671
+ form: {
672
+ file: {
673
+ fileId: 'fd5db541-179c-4107-a4d0-149d09672ffc',
674
+ filename: 'test.jpg',
675
+ fileStatus: FileStatus.complete,
676
+ contentLength: 3671
677
+ }
678
+ },
679
+ numberOfRejectedFiles: 0
680
+ }
681
+ }
682
+ ]
683
+ })
684
+
685
+ request = buildFormContextRequest({
686
+ method: 'get',
687
+ url: new URL('http://example.com/test/summary'),
688
+ path: '/test/summary',
689
+ params: {
690
+ path: 'summary',
691
+ slug: 'test'
692
+ },
693
+ query: {},
694
+ app: { model }
695
+ })
696
+
697
+ // Calculate our context based on the page we're attempting to load and the above state we provide
698
+ context = controller.model.getFormContext(request, state)
699
+
700
+ // Context paths should be all pages up to the one we requested
701
+ expect(context.paths).toEqual([
702
+ '/all-components',
703
+ '/methodology-statement',
704
+ '/summary'
705
+ ])
706
+
707
+ // Our context should now have evaluation state values all fields
708
+ expect(context.evaluationState).toEqual({
709
+ textField: 'Text field',
710
+ multilineTextField: 'Multiline text field',
711
+ numberField: 1,
712
+ datePartsField: '2012-12-12',
713
+ monthYearField: '2012-12',
714
+ yesNoField: null,
715
+ emailAddressField: 'user@email.com',
716
+ telephoneNumberField: '+447900000000',
717
+ addressField: [
718
+ 'Address line 1',
719
+ 'Address line 2',
720
+ 'Town or city',
721
+ 'Cheshire',
722
+ 'CW1 1AB'
723
+ ],
724
+ radiosField: 'privateLimitedCompany',
725
+ selectField: 910400000,
726
+ autocompleteField: 910400044,
727
+ checkboxesSingle: ['Shetland'],
728
+ checkboxesMultiple: ['Arabian', 'Shire', 'Race'],
729
+ checkboxesSingleNumber: [1],
730
+ checkboxesMultipleNumber: [0, 1],
731
+ fileUpload: ['fd5db541-179c-4107-a4d0-149d09672ffc']
732
+ })
733
+ })
524
734
  })
525
735
 
526
736
  describe('Form validation', () => {
@@ -1005,7 +1215,11 @@ describe('QuestionPageController V2', () => {
1005
1215
  'AddressLine2',
1006
1216
  'Town',
1007
1217
  'Postcode'
1008
- ]
1218
+ ],
1219
+ applicantTwoAddress: null,
1220
+ applicantTwoFirstName: null,
1221
+ applicantTwoLastName: null,
1222
+ applicantTwoMiddleName: null
1009
1223
  })
1010
1224
 
1011
1225
  // Our context should know which pages are relevant
@@ -1042,15 +1256,18 @@ describe('QuestionPageController V2', () => {
1042
1256
  '/summary'
1043
1257
  ])
1044
1258
 
1045
- // Our context should no longer know anything about our applicant
1046
- expect(stateAfter).not.toHaveProperty('numberOfApplicants')
1047
- expect(stateAfter).not.toHaveProperty('applicantOneFirstName')
1048
- expect(stateAfter).not.toHaveProperty('applicantOneMiddleName')
1049
- expect(stateAfter).not.toHaveProperty('applicantOneLastName')
1050
- expect(stateAfter).not.toHaveProperty('applicantOneAddress')
1051
-
1259
+ // Our evaluation context should have default values for irrelevant fields
1052
1260
  expect(stateAfter).toEqual({
1053
- ukPassport: false
1261
+ ukPassport: false,
1262
+ numberOfApplicants: null,
1263
+ applicantOneFirstName: null,
1264
+ applicantOneMiddleName: null,
1265
+ applicantOneLastName: null,
1266
+ applicantOneAddress: null,
1267
+ applicantTwoAddress: null,
1268
+ applicantTwoFirstName: null,
1269
+ applicantTwoLastName: null,
1270
+ applicantTwoMiddleName: null
1054
1271
  })
1055
1272
  })
1056
1273