Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions src/server/plugins/engine/models/FormModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
ConditionsModel,
ControllerPath,
ControllerType,
Engine,
SchemaVersion,
convertConditionWrapperFromV2,
formDefinitionSchema,
Expand All @@ -19,6 +18,7 @@ import {
type ConditionWrapperV2,
type ConditionsModelData,
type DateUnits,
type Engine,
type FormDefinition,
type List,
type Page
Expand Down Expand Up @@ -363,10 +363,7 @@ export class FormModel {
// Add page to context
context.relevantPages.push(nextPage)

// Engine.V2 is excluded here as this will have already been done in initialiseContext()
if (this.engine !== Engine.V2) {
this.assignEvaluationState(context, nextPage)
}
this.assignEvaluationState(context, nextPage)

this.assignRelevantState(context, nextPage)

Expand All @@ -392,12 +389,19 @@ export class FormModel {
}

private initialiseContext(context: FormContext) {
// For the V2 engine, we initialise `evaluationState` for all keys.
// Initialise `evaluationState` for all keys using empty state.
// This is because the current condition evaluation library (eval-expr)
// will throw if an expression uses a key that is undefined.
if (this.engine === Engine.V2) {
for (const page of this.pages) {
this.assignEvaluationState(context, page)
const emptyState = Object.freeze({})

for (const page of this.pages) {
const { collection, pageDef } = page

if (!hasRepeater(pageDef)) {
Object.assign(
context.evaluationState,
collection.getContextValueFromState(emptyState)
)
}
}
}
Expand Down
253 changes: 235 additions & 18 deletions src/server/plugins/engine/pageControllers/QuestionPageController.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type PageQuestion } from '@defra/forms-model'

import { getForm } from '~/src/server/plugins/engine/configureEnginePlugin.js'
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
import {
Expand All @@ -8,6 +9,8 @@ import {
} from '~/src/server/plugins/engine/pageControllers/__stubs__/request.js'
import { serverWithSaveAndExit } from '~/src/server/plugins/engine/pageControllers/__stubs__/server.js'
import {
FileStatus,
UploadStatus,
type FormContext,
type FormPageViewModel,
type FormState,
Expand Down Expand Up @@ -321,7 +324,11 @@ describe('QuestionPageController', () => {
'AddressLine2',
'Town',
'Postcode'
]
],
applicantTwoAddress: null,
applicantTwoFirstName: null,
applicantTwoLastName: null,
applicantTwoMiddleName: null
})

// Our context should know which pages are relevant
Expand Down Expand Up @@ -358,15 +365,18 @@ describe('QuestionPageController', () => {
'/summary'
])

// Our context should no longer know anything about our applicant
expect(stateAfter).not.toHaveProperty('numberOfApplicants')
expect(stateAfter).not.toHaveProperty('applicantOneFirstName')
expect(stateAfter).not.toHaveProperty('applicantOneMiddleName')
expect(stateAfter).not.toHaveProperty('applicantOneLastName')
expect(stateAfter).not.toHaveProperty('applicantOneAddress')

// Our evaluation context should have default values for irrelevant fields
expect(stateAfter).toEqual({
ukPassport: false
ukPassport: false,
numberOfApplicants: null,
applicantOneFirstName: null,
applicantOneMiddleName: null,
applicantOneLastName: null,
applicantOneAddress: null,
applicantTwoAddress: null,
applicantTwoFirstName: null,
applicantTwoLastName: null,
applicantTwoMiddleName: null
})
})

Expand Down Expand Up @@ -521,6 +531,206 @@ describe('QuestionPageController', () => {
}
])
})

it('correctly initialises default values', async () => {
const components = await getForm(
'../../../../test/form/definitions/components.json'
)
const { pages } = components

const model = new FormModel(components, {
basePath: 'test'
})

const controller = new QuestionPageController(model, pages[0])

const state: FormSubmissionState = { $$__referenceNumber: 'foobar' }

let request = buildFormContextRequest({
method: 'get',
url: new URL('http://example.com/test/all-components'),
path: '/test/all-components',
params: {
path: 'all-components',
slug: 'test'
},
query: {},
app: { model }
})

// Calculate our context based on the page we're attempting to load and the above state we provide
let context = controller.model.getFormContext(request, state)

// Context paths should be all pages up to the one we requested
expect(context.paths).toEqual(['/all-components'])

// Our context should have default values for all input fields
expect(context.evaluationState).toEqual({
textField: null,
multilineTextField: null,
numberField: null,
datePartsField: null,
monthYearField: null,
yesNoField: null,
emailAddressField: null,
telephoneNumberField: null,
addressField: null,
radiosField: null,
selectField: null,
autocompleteField: null,
checkboxesSingle: [],
checkboxesMultiple: [],
checkboxesSingleNumber: [],
checkboxesMultipleNumber: [],
fileUpload: null
})

Object.assign(state, {
textField: 'Text field',
multilineTextField: 'Multiline text field',
numberField: 1,
datePartsField__day: 12,
datePartsField__month: 12,
datePartsField__year: 2012,
monthYearField__month: 12,
monthYearField__year: 2012,
yesNoField: 'true',
emailAddressField: 'user@email.com',
telephoneNumberField: '+447900000000',
addressField__addressLine1: 'Address line 1',
addressField__addressLine2: 'Address line 2',
addressField__town: 'Town or city',
addressField__county: 'Cheshire',
addressField__postcode: 'CW1 1AB',
radiosField: 'privateLimitedCompany',
selectField: 910400000,
autocompleteField: 910400044,
checkboxesSingle: ['Shetland'],
checkboxesMultiple: ['Arabian', 'Shire', 'Race'],
checkboxesSingleNumber: [1],
checkboxesMultipleNumber: [0, 1]
})

request = buildFormContextRequest({
method: 'get',
url: new URL('http://example.com/test/methodology-statement'),
path: '/test/methodology-statement',
params: {
path: 'methodology-statement',
slug: 'test'
},
query: {},
app: { model }
})

// Calculate our context based on the page we're attempting to load and the above state we provide
context = controller.model.getFormContext(request, state)

// Context paths should be all pages up to the one we requested
expect(context.paths).toEqual([
'/all-components',
'/methodology-statement'
])

// Our context should have evaluation state values for relevant fields and default values for everything else
expect(context.evaluationState).toEqual({
textField: 'Text field',
multilineTextField: 'Multiline text field',
numberField: 1,
datePartsField: '2012-12-12',
monthYearField: '2012-12',
yesNoField: null,
emailAddressField: 'user@email.com',
telephoneNumberField: '+447900000000',
addressField: [
'Address line 1',
'Address line 2',
'Town or city',
'Cheshire',
'CW1 1AB'
],
radiosField: 'privateLimitedCompany',
selectField: 910400000,
autocompleteField: 910400044,
checkboxesSingle: ['Shetland'],
checkboxesMultiple: ['Arabian', 'Shire', 'Race'],
checkboxesSingleNumber: [1],
checkboxesMultipleNumber: [0, 1],
fileUpload: null
})

Object.assign(state, {
fileUpload: [
{
uploadId: '348e1878-59c0-4d2e-a52b-e5042ad729f0',
status: {
uploadStatus: UploadStatus.ready,
metadata: {
retrievalKey: 'enrique.chase@defra.gov.uk'
},
form: {
file: {
fileId: 'fd5db541-179c-4107-a4d0-149d09672ffc',
filename: 'test.jpg',
fileStatus: FileStatus.complete,
contentLength: 3671
}
},
numberOfRejectedFiles: 0
}
}
]
})

request = buildFormContextRequest({
method: 'get',
url: new URL('http://example.com/test/summary'),
path: '/test/summary',
params: {
path: 'summary',
slug: 'test'
},
query: {},
app: { model }
})

// Calculate our context based on the page we're attempting to load and the above state we provide
context = controller.model.getFormContext(request, state)

// Context paths should be all pages up to the one we requested
expect(context.paths).toEqual([
'/all-components',
'/methodology-statement',
'/summary'
])

// Our context should now have evaluation state values all fields
expect(context.evaluationState).toEqual({
textField: 'Text field',
multilineTextField: 'Multiline text field',
numberField: 1,
datePartsField: '2012-12-12',
monthYearField: '2012-12',
yesNoField: null,
emailAddressField: 'user@email.com',
telephoneNumberField: '+447900000000',
addressField: [
'Address line 1',
'Address line 2',
'Town or city',
'Cheshire',
'CW1 1AB'
],
radiosField: 'privateLimitedCompany',
selectField: 910400000,
autocompleteField: 910400044,
checkboxesSingle: ['Shetland'],
checkboxesMultiple: ['Arabian', 'Shire', 'Race'],
checkboxesSingleNumber: [1],
checkboxesMultipleNumber: [0, 1],
fileUpload: ['fd5db541-179c-4107-a4d0-149d09672ffc']
})
})
})

describe('Form validation', () => {
Expand Down Expand Up @@ -1005,7 +1215,11 @@ describe('QuestionPageController V2', () => {
'AddressLine2',
'Town',
'Postcode'
]
],
applicantTwoAddress: null,
applicantTwoFirstName: null,
applicantTwoLastName: null,
applicantTwoMiddleName: null
})

// Our context should know which pages are relevant
Expand Down Expand Up @@ -1042,15 +1256,18 @@ describe('QuestionPageController V2', () => {
'/summary'
])

// Our context should no longer know anything about our applicant
expect(stateAfter).not.toHaveProperty('numberOfApplicants')
expect(stateAfter).not.toHaveProperty('applicantOneFirstName')
expect(stateAfter).not.toHaveProperty('applicantOneMiddleName')
expect(stateAfter).not.toHaveProperty('applicantOneLastName')
expect(stateAfter).not.toHaveProperty('applicantOneAddress')

// Our evaluation context should have default values for irrelevant fields
expect(stateAfter).toEqual({
ukPassport: false
ukPassport: false,
numberOfApplicants: null,
applicantOneFirstName: null,
applicantOneMiddleName: null,
applicantOneLastName: null,
applicantOneAddress: null,
applicantTwoAddress: null,
applicantTwoFirstName: null,
applicantTwoLastName: null,
applicantTwoMiddleName: null
})
})

Expand Down
Loading
Loading