Skip to content

Commit

Permalink
Merge pull request #68 from DEFRA/583-3
Browse files Browse the repository at this point in the history
[583] new section validation
  • Loading branch information
DrogoNevets authored Dec 12, 2024
2 parents 21155ee + 5cbf3d5 commit 767d4d7
Show file tree
Hide file tree
Showing 17 changed files with 257 additions and 54 deletions.
14 changes: 8 additions & 6 deletions src/server/common/model/application/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Origin } from '../section/origin.js'
import { validateApplication } from './validation.js'

/**
* @import {SectionModel} from '../section/section-model.js'
* @import {SectionModel} from '../section/section-model/section-model.js'
*/

/**
Expand All @@ -16,12 +16,7 @@ import { validateApplication } from './validation.js'
* @import {AddressData} from '../answer/address.js'
*/

/**
* @typedef {{[key:string]: SectionModel}} ApplicationPayload
*/

export class Application {
/** @type {ApplicationPayload} */
_data

constructor(data) {
Expand All @@ -33,6 +28,13 @@ export class Application {
return validateApplication(this._data)
}

/**
* @returns {Origin}
*/
get origin() {
return Origin.fromState(this._data.origin)
}

/* eslint-disable @typescript-eslint/no-unused-vars */

/**
Expand Down
13 changes: 10 additions & 3 deletions src/server/common/model/application/application.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Application } from './application.js'
import { OnOffFarm } from '../answer/on-off-farm.js'

/**
* @import {Origin} from '../section/origin.js'
*/

const originDefaultState = {
onOffFarm: new OnOffFarm({ onOffFarm: 'on' }).toState(),
cphNumber: '12/123/1234',
Expand All @@ -27,9 +31,12 @@ describe('Application', () => {
const application = Application.fromState(state)

expect(application).toBeInstanceOf(Application)
expect(application._data.origin._data.address.value).toBeDefined()
expect(application._data.origin._data.cphNumber.value).toBeDefined()
expect(application._data.origin._data.onOffFarm.value).toBeDefined()

const origin = application.origin

expect(origin.address.value).toBeDefined()
expect(origin.cphNumber.value).toBeDefined()
expect(origin.onOffFarm.value).toBeDefined()
expect(application._data.licence._data.emailAddress.value).toBeDefined()
})

Expand Down
2 changes: 1 addition & 1 deletion src/server/common/model/application/validation.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @import {SectionValidationResult} from '../section/validation.js'
* @import {SectionModel} from '../section/section-model.js'
* @import {SectionModel} from '../section/section-model/section-model.js'
*/

import mapValues from 'lodash/mapValues.js'
Expand Down
2 changes: 1 addition & 1 deletion src/server/common/model/application/validation.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SectionModel } from '../section/section-model.js'
import { SectionModel } from '../section/section-model/section-model.js'
import { validateApplication } from './validation.js'

class ValidSection extends SectionModel {
Expand Down
2 changes: 1 addition & 1 deletion src/server/common/model/section/destination.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SectionModel } from './section-model.js'
import { SectionModel } from './section-model/section-model.js'

export class Destination extends SectionModel {
validate() {
Expand Down
2 changes: 1 addition & 1 deletion src/server/common/model/section/licence.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SectionModel } from './section-model.js'
import { SectionModel } from './section-model/section-model.js'
import { EmailAddress } from '../answer/email-address.js'

/**
Expand Down
31 changes: 23 additions & 8 deletions src/server/common/model/section/origin.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { SectionModel } from '../section/section-model.js'
import { OnOffFarm } from '../answer/on-off-farm.js'
import { SectionModel } from '../section/section-model/index.js'
import { CphNumber } from '../answer/cph-number.js'
import { Address } from '../answer/address.js'

import { OnOffFarm } from '../answer/on-off-farm.js'
import { OnOffFarmPage } from '../../../origin/on-off-farm/index.js'
import { CphNumberPage } from '../../../origin/cph-number/index.js'
import { OriginAddressPage } from '../../../origin/address/index.js'

/**
* export @typedef {{
* onOffFarm: OnOffFarmData | undefined;
Expand All @@ -15,16 +19,18 @@ import { Address } from '../answer/address.js'
*/

export class Origin extends SectionModel {
firstPage = new OnOffFarmPage()

get onOffFarm() {
return this._data?.onOffFarm
return this._data?.onOffFarm.answer
}

get cphNumber() {
return this._data?.cphNumber
return this._data?.cphNumber.answer
}

get address() {
return this._data?.address
return this._data?.address.answer
}

/**
Expand All @@ -33,9 +39,18 @@ export class Origin extends SectionModel {
*/
static fromState(state) {
return new Origin({
onOffFarm: OnOffFarm.fromState(state?.onOffFarm),
cphNumber: CphNumber.fromState(state?.cphNumber),
address: Address.fromState(state?.address)
onOffFarm: {
page: new OnOffFarmPage(),
answer: OnOffFarm.fromState(state?.onOffFarm)
},
cphNumber: {
page: new CphNumberPage(),
answer: CphNumber.fromState(state?.cphNumber)
},
address: {
page: new OriginAddressPage(),
answer: Address.fromState(state?.address)
}
})
}
}
17 changes: 8 additions & 9 deletions src/server/common/model/section/origin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Origin } from './origin.js'
import { OnOffFarm } from '../answer/on-off-farm.js'
import { CphNumber } from '../answer/cph-number.js'
import { Address } from '../answer/address.js'
import { OnOffFarmPage } from '~/src/server/origin/on-off-farm/index.js'
/** @import { OnOffFarmData } from '../answer/on-off-farm.js' */

const validCphNumber = '12/345/6789'
Expand All @@ -25,18 +26,18 @@ describe('Origin', () => {
const origin = Origin.fromState(originData)

expect(origin).toBeInstanceOf(Origin)
expect(origin._data?.onOffFarm).toBeInstanceOf(OnOffFarm)
expect(origin._data?.cphNumber).toBeInstanceOf(CphNumber)
expect(origin._data?.address).toBeInstanceOf(Address)
expect(origin.onOffFarm).toBeInstanceOf(OnOffFarm)
expect(origin.cphNumber).toBeInstanceOf(CphNumber)
expect(origin.address).toBeInstanceOf(Address)
})

it('should handle undefined state gracefully', () => {
const origin = Origin.fromState(undefined)

expect(origin).toBeInstanceOf(Origin)
expect(origin._data?.onOffFarm.value).toBeUndefined()
expect(origin._data?.cphNumber.value).toBeUndefined()
expect(origin._data?.address.value).toBeUndefined()
expect(origin.onOffFarm.value).toBeUndefined()
expect(origin.cphNumber.value).toBeUndefined()
expect(origin.address.value).toBeUndefined()
})
})

Expand All @@ -62,9 +63,7 @@ describe('Origin', () => {
const result = Origin.fromState(originData).validate()

expect(result.isValid).toBe(false)
expect(result.result.onOffFarm.isValid).toBe(false)
expect(result.result.cphNumber.isValid).toBe(true)
expect(result.result.address.isValid).toBe(true)
expect(result.firstInvalidPage).toBeInstanceOf(OnOffFarmPage)
})
})
})
3 changes: 3 additions & 0 deletions src/server/common/model/section/section-model/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { SectionModelUpdated } from './section-model-updated.js'

export const SectionModel = SectionModelUpdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { NotImplementedError } from '../../../helpers/not-implemented-error.js'
import { QuestionPage } from '../../page/question-page-model.js'

/**
* @import { Page } from '../../page/page-model.js'
* @import {AnswerModel} from '../../answer/answer-model.js'
*/

/**
* @typedef {{ page: QuestionPage, answer: AnswerModel }} PageAnswer
* @typedef {{[key: string]: PageAnswer}} SectionPayload
*/

/**
* @typedef {{ isValid: boolean, firstInvalidPage?: QuestionPage }} SectionValidation
*/

export class SectionModelUpdated {
/** @type {SectionPayload} */
_data

/** @type {QuestionPage} */
firstPage

constructor(data) {
this._data = data
}

get value() {
return this._data
}

get pages() {
const pages = []

/** @type {QuestionPage} */
let page = this._data[this.firstPage.questionKey].page

while (page instanceof QuestionPage) {
const currPage = this._data[page.questionKey]

pages.push(page)

page = /** @type {QuestionPage} */ (page.nextPage(currPage.answer))
}

return pages
}

/** @returns {SectionValidation} */
validate() {
const pages = this.pages

if (pages.length === 0) {
return { isValid: false, firstInvalidPage: this.firstPage }
}

for (const visitedPage of pages) {
const { page, answer } = this._data[visitedPage.questionKey]
if (!answer.validate().isValid) {
return { isValid: false, firstInvalidPage: page }
}
}

return { isValid: true }
}

/**
* @param {object} _data
* @returns {SectionModelUpdated}
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
static fromState(_data) {
throw new NotImplementedError()
}

/* eslint-enable @typescript-eslint/no-unused-vars */
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { CphNumberPage } from '~/src/server/origin/cph-number/index.js'
import { OnOffFarmPage } from '~/src/server/origin/on-off-farm/index.js'
import { OnOffFarm } from '~/src/server/common/model/answer/on-off-farm.js'
import { Origin } from '../origin.js'

/** @import {OnOffFarmData} from '~/src/server/common/model/answer/on-off-farm.js' */

const validAddress = {
addressLine1: 'Starfleet Headquarters',
addressTown: 'San Francisco',
addressPostcode: 'RG24 8RR'
}

const validState = {
onOffFarm: /** @type {OnOffFarmData} */ ('off'),
cphNumber: '12/345/6789',
address: validAddress
}

const invalidState = {
onOffFarm: /** @type {OnOffFarmData} */ ('off'),
cphNumber: 'not-a-cph',
address: validAddress // this is unreachable in the journey, since we've got an invalid question ahead of it
}

const exitState = {
onOffFarm: /** @type {OnOffFarmData} */ ('on'),
cphNumber: 'not-a-cph', // this is unreachable in the journey, because we will have exited already
address: validAddress // this is unreachable in the journey, because we will have exited already
}

describe('SectionModel.value', () => {
it('should short-circuit on an exit page', () => {
const origin = Origin.fromState(exitState)
const pages = origin.pages

expect(pages).toHaveLength(1)
expect(pages.at(0)).toBeInstanceOf(OnOffFarmPage)
expect(origin[pages.at(0)?.questionKey ?? 'invalid']).toBeInstanceOf(
OnOffFarm
)
})
})

describe('SectionModel.validate', () => {
it('should return valid if all questions in journey are validly answered', () => {
const origin = Origin.fromState(validState)

expect(origin.validate()).toEqual({ isValid: true })
})

// Reason: We have not finalised how exit pages will behave
it.skip('should return ... invalid ? ... if the section hits an exit condition before its complete', () => {
const origin = Origin.fromState(exitState)

expect(origin.validate().isValid).toBe(true)
})

it('should return invalid if the section hits a page with an invalid answer', () => {
const origin = Origin.fromState(invalidState)
const { isValid, firstInvalidPage } = origin.validate()

expect(isValid).toBe(false)
expect(firstInvalidPage).toBeInstanceOf(CphNumberPage)
})
})

describe('SectionModel.fromState', () => {
it('should return an instance of the class that produced it', () => {
expect(Origin.fromState(validState)).toBeInstanceOf(Origin)
})
})
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { NotImplementedError } from '../../helpers/not-implemented-error.js'
import { validateSection } from './validation.js'
import { NotImplementedError } from '../../../helpers/not-implemented-error.js'
import { validateSection } from '../validation.js'

/**
* @import {AnswerModel} from '../answer/answer-model.js'
* @import {AnswerModel} from '../../answer/answer-model.js'
*/

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NotImplementedError } from '../../helpers/not-implemented-error.js'
import { NotImplementedError } from '~/src/server/common/helpers/not-implemented-error.js'
import { SectionModel } from './section-model.js'

describe('SectionModel', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/server/common/model/section/tests.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SectionModel } from './section-model.js'
import { SectionModel } from './section-model/section-model.js'

export class Tests extends SectionModel {
validate() {
Expand Down
Loading

0 comments on commit 767d4d7

Please sign in to comment.