Skip to content

Commit 462a1cc

Browse files
Backport reference number feature (#36)
* convert json confirmation page to confirmation-page.js and add unique ref (#81) * Test getFormContext throws an error for missing ref number --------- Co-authored-by: swdpcomputing <67058346+swdpcomputing@users.noreply.github.com>
1 parent 292595e commit 462a1cc

12 files changed

Lines changed: 175 additions & 20 deletions

File tree

src/server/plugins/engine/helpers.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,8 @@ describe('Helpers', () => {
561561
listDefMap: model.listDefMap,
562562
componentDefMap: model.componentDefMap,
563563
pageMap: model.pageMap,
564-
componentMap: model.componentMap
564+
componentMap: model.componentMap,
565+
referenceNumber: 'foobar'
565566
}
566567
})
567568

src/server/plugins/engine/models/FormModel.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ describe('FormModel', () => {
1818
basePath: '/components'
1919
})
2020

21-
const state = { checkboxesSingle: ['Arabian', 'Shetland'] }
21+
const state = {
22+
$$__referenceNumber: 'foobar',
23+
checkboxesSingle: ['Arabian', 'Shetland']
24+
}
2225
const pageUrl = new URL('http://example.com/components/fields-required')
2326

2427
const request: FormContextRequest = {
@@ -37,6 +40,58 @@ describe('FormModel', () => {
3740
expect(context.errors).toContainEqual(
3841
expect.objectContaining({ name: 'checkboxesSingle' })
3942
)
43+
expect(context.referenceNumber).toEqual(expect.any(String))
44+
})
45+
46+
it('handles missing reference numbers', () => {
47+
const formModel = new FormModel(fieldsRequiredDefinition, {
48+
basePath: '/components'
49+
})
50+
51+
const state = {
52+
checkboxesSingle: ['Arabian', 'Shetland']
53+
}
54+
const pageUrl = new URL('http://example.com/components/fields-required')
55+
56+
const request: FormContextRequest = {
57+
method: 'post',
58+
payload: { crumb: 'dummyCrumb', action: 'validate' },
59+
query: {},
60+
path: pageUrl.pathname,
61+
params: { path: 'components', slug: 'fields-required' },
62+
url: pageUrl,
63+
app: { model: formModel }
64+
}
65+
66+
expect(() => formModel.getFormContext(request, state)).toThrow(
67+
'Reference number not found in form state'
68+
)
69+
})
70+
71+
it('handles non-string reference numbers', () => {
72+
const formModel = new FormModel(fieldsRequiredDefinition, {
73+
basePath: '/components'
74+
})
75+
76+
const state = {
77+
$$__referenceNumber: 1232456,
78+
checkboxesSingle: ['Arabian', 'Shetland']
79+
}
80+
const pageUrl = new URL('http://example.com/components/fields-required')
81+
82+
const request: FormContextRequest = {
83+
method: 'post',
84+
payload: { crumb: 'dummyCrumb', action: 'validate' },
85+
query: {},
86+
path: pageUrl.pathname,
87+
params: { path: 'components', slug: 'fields-required' },
88+
url: pageUrl,
89+
app: { model: formModel }
90+
}
91+
92+
expect(() => formModel.getFormContext(request, state)).toThrow(
93+
'Reference number not found in form state'
94+
)
4095
})
4196
})
4297
})

src/server/plugins/engine/models/FormModel.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,8 @@ export class FormModel {
275275
listDefMap: this.listDefMap,
276276
componentDefMap: this.componentDefMap,
277277
pageMap: this.pageMap,
278-
componentMap: this.componentMap
278+
componentMap: this.componentMap,
279+
referenceNumber: getReferenceNumber(state)
279280
}
280281

281282
// Validate current page
@@ -441,3 +442,14 @@ function validateFormState(
441442

442443
return context
443444
}
445+
446+
function getReferenceNumber(state: FormState): string {
447+
if (
448+
!state.$$__referenceNumber ||
449+
typeof state.$$__referenceNumber !== 'string'
450+
) {
451+
throw Error('Reference number not found in form state')
452+
}
453+
454+
return state.$$__referenceNumber
455+
}

src/server/plugins/engine/models/SummaryViewModel.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ describe('SummaryViewModel', () => {
5151
{
5252
description: '0 items',
5353
state: {
54+
$$__referenceNumber: 'foobar',
5455
orderType: 'collection',
5556
pizza: []
5657
} satisfies FormState,
@@ -60,6 +61,7 @@ describe('SummaryViewModel', () => {
6061
{
6162
description: '1 item',
6263
state: {
64+
$$__referenceNumber: 'foobar',
6365
orderType: 'delivery',
6466
pizza: [
6567
{
@@ -75,6 +77,7 @@ describe('SummaryViewModel', () => {
7577
{
7678
description: '2 items',
7779
state: {
80+
$$__referenceNumber: 'foobar',
7881
orderType: 'delivery',
7982
pizza: [
8083
{

src/server/plugins/engine/outputFormatters/human/v1.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const model = new FormModel(definition, {
3131
})
3232

3333
const state = {
34+
$$__referenceNumber: 'foobar',
3435
orderType: 'delivery',
3536
pizza: [
3637
{

src/server/plugins/engine/pageControllers/PageController.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,13 @@ describe('PageController', () => {
167167

168168
await controller1.makeGetRouteHandler()(
169169
request,
170-
model.getFormContext(request, {}),
170+
model.getFormContext(request, { $$__referenceNumber: 'foobar' }),
171171
h
172172
)
173173

174174
await controller2.makeGetRouteHandler()(
175175
request,
176-
model.getFormContext(request, {}),
176+
model.getFormContext(request, { $$__referenceNumber: 'foobar' }),
177177
h
178178
)
179179

src/server/plugins/engine/pageControllers/QuestionPageController.test.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,12 @@ describe('QuestionPageController', () => {
178178
beforeEach(() => {
179179
viewModel1 = controller1.getViewModel(
180180
requestPage1,
181-
model.getFormContext(requestPage1, {})
181+
model.getFormContext(requestPage1, { $$__referenceNumber: 'foobar' })
182182
)
183183

184184
viewModel2 = controller2.getViewModel(
185185
requestPage2,
186-
model.getFormContext(requestPage2, {})
186+
model.getFormContext(requestPage2, { $$__referenceNumber: 'foobar' })
187187
)
188188
})
189189

@@ -267,6 +267,7 @@ describe('QuestionPageController', () => {
267267

268268
// The state below shows we said we had a UKPassport and entered details for an applicant
269269
const state: FormSubmissionState = {
270+
$$__referenceNumber: 'foobar',
270271
ukPassport: true,
271272
numberOfApplicants: 2,
272273
applicantOneFirstName: 'Enrique',
@@ -384,6 +385,7 @@ describe('QuestionPageController', () => {
384385
} satisfies FormContextRequest
385386

386387
const context = controller.model.getFormContext(request, {
388+
$$__referenceNumber: 'foobar',
387389
dateField__day: 5,
388390
dateField__month: 1,
389391
dateField__year: 2024
@@ -411,7 +413,7 @@ describe('QuestionPageController', () => {
411413
const controller = new QuestionPageController(model, pages[0])
412414

413415
// The state below shows we said we had a UKPassport and entered details for an applicant
414-
const state: FormSubmissionState = {}
416+
const state: FormSubmissionState = { $$__referenceNumber: 'foobar' }
415417

416418
const request = {
417419
method: 'get',
@@ -467,7 +469,7 @@ describe('QuestionPageController', () => {
467469
const controller = new QuestionPageController(model, pages[0])
468470

469471
// The state below shows we said we had a UKPassport and entered details for an applicant
470-
const state: FormSubmissionState = {}
472+
const state: FormSubmissionState = { $$__referenceNumber: 'foobar' }
471473

472474
const request = {
473475
method: 'get',
@@ -531,15 +533,19 @@ describe('QuestionPageController', () => {
531533

532534
beforeEach(() => {
533535
// Empty state
534-
context = model.getFormContext(requestPage1, {})
536+
context = model.getFormContext(requestPage1, {
537+
$$__referenceNumber: 'foobar'
538+
})
535539

536540
// Question 1: Selected 'No'
537541
contextNo = model.getFormContext(requestPage1, {
542+
$$__referenceNumber: 'foobar',
538543
yesNoField: false
539544
})
540545

541546
// Question 1: Selected 'Yes'
542547
contextYes = model.getFormContext(requestPage1, {
548+
$$__referenceNumber: 'foobar',
543549
yesNoField: true
544550
})
545551
})
@@ -619,13 +625,13 @@ describe('QuestionPageController', () => {
619625

620626
await controller1.makeGetRouteHandler()(
621627
requestPage1,
622-
model.getFormContext(requestPage1, {}),
628+
model.getFormContext(requestPage1, { $$__referenceNumber: 'foobar' }),
623629
h
624630
)
625631

626632
await controller2.makeGetRouteHandler()(
627633
requestPage2,
628-
model.getFormContext(requestPage2, {}),
634+
model.getFormContext(requestPage2, { $$__referenceNumber: 'foobar' }),
629635
h
630636
)
631637

@@ -856,12 +862,12 @@ describe('QuestionPageController V2', () => {
856862
beforeEach(() => {
857863
viewModel1 = controller1.getViewModel(
858864
requestPage1,
859-
model.getFormContext(requestPage1, {})
865+
model.getFormContext(requestPage1, { $$__referenceNumber: 'foobar' })
860866
)
861867

862868
viewModel2 = controller2.getViewModel(
863869
requestPage2,
864-
model.getFormContext(requestPage2, {})
870+
model.getFormContext(requestPage2, { $$__referenceNumber: 'foobar' })
865871
)
866872
})
867873

@@ -945,6 +951,7 @@ describe('QuestionPageController V2', () => {
945951

946952
// The state below shows we said we had a UKPassport and entered details for an applicant
947953
const state: FormSubmissionState = {
954+
$$__referenceNumber: 'foobar',
948955
ukPassport: true,
949956
numberOfApplicants: 2,
950957
applicantOneFirstName: 'Enrique',
@@ -1062,6 +1069,7 @@ describe('QuestionPageController V2', () => {
10621069
} satisfies FormContextRequest
10631070

10641071
const context = controller.model.getFormContext(request, {
1072+
$$__referenceNumber: 'foobar',
10651073
dateField__day: 5,
10661074
dateField__month: 1,
10671075
dateField__year: 2024
@@ -1097,15 +1105,19 @@ describe('QuestionPageController V2', () => {
10971105

10981106
beforeEach(() => {
10991107
// Empty state
1100-
context = model.getFormContext(requestPage1, {})
1108+
context = model.getFormContext(requestPage1, {
1109+
$$__referenceNumber: 'foobar'
1110+
})
11011111

11021112
// Question 1: Selected 'No'
11031113
contextNo = model.getFormContext(requestPage1, {
1114+
$$__referenceNumber: 'foobar',
11041115
yesNoField: false
11051116
})
11061117

11071118
// Question 1: Selected 'Yes'
11081119
contextYes = model.getFormContext(requestPage1, {
1120+
$$__referenceNumber: 'foobar',
11091121
yesNoField: true
11101122
})
11111123
})
@@ -1174,13 +1186,13 @@ describe('QuestionPageController V2', () => {
11741186

11751187
await controller1.makeGetRouteHandler()(
11761188
requestPage1,
1177-
model.getFormContext(requestPage1, {}),
1189+
model.getFormContext(requestPage1, { $$__referenceNumber: 'foobar' }),
11781190
h
11791191
)
11801192

11811193
await controller2.makeGetRouteHandler()(
11821194
requestPage2,
1183-
model.getFormContext(requestPage2, {}),
1195+
model.getFormContext(requestPage2, { $$__referenceNumber: 'foobar' }),
11841196
h
11851197
)
11861198

src/server/plugins/engine/pageControllers/RepeatPageController.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ describe('RepeatPageController', () => {
128128
beforeEach(() => {
129129
viewModel = controller.getViewModel(
130130
requestPageItem,
131-
model.getFormContext(requestPageItem, {})
131+
model.getFormContext(requestPageItem, { $$__referenceNumber: 'foobar' })
132132
)
133133
})
134134

@@ -190,7 +190,9 @@ describe('RepeatPageController', () => {
190190
beforeEach(() => {
191191
viewModel = controller.getListSummaryViewModel(
192192
requestPageSummary,
193-
model.getFormContext(requestPageSummary, {}),
193+
model.getFormContext(requestPageSummary, {
194+
$$__referenceNumber: 'foobar'
195+
}),
194196
list
195197
)
196198
})

src/server/plugins/engine/plugin.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { type PageController } from '~/src/server/plugins/engine/pageControllers
4040
import { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'
4141
import { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'
4242
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'
43+
import { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'
4344
import * as defaultServices from '~/src/server/plugins/engine/services/index.js'
4445
import { getUploadStatus } from '~/src/server/plugins/engine/services/uploadService.js'
4546
import {
@@ -265,7 +266,23 @@ export const plugin = {
265266

266267
const cacheService = getCacheService(request.server)
267268
const page = getPage(model, request)
268-
const state = await page.getState(request)
269+
let state = await page.getState(request)
270+
271+
if (!state.$$__referenceNumber) {
272+
const prefix = model.def.metadata?.referenceNumberPrefix ?? ''
273+
274+
if (typeof prefix !== 'string') {
275+
throw Boom.badImplementation(
276+
'Reference number prefix must be a string or undefined'
277+
)
278+
}
279+
280+
const referenceNumber = generateUniqueReference(prefix)
281+
state = await page.mergeState(request, state, {
282+
$$__referenceNumber: referenceNumber
283+
})
284+
}
285+
269286
const flash = cacheService.getFlash(request)
270287
const context = model.getFormContext(request, state, flash?.errors)
271288
const relevantPath = page.getRelevantPath(request, context)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'
2+
3+
describe('generateUniqueReference', () => {
4+
it('should generate a reference number with 3 segments when no prefix is provided', () => {
5+
const referenceNumber = generateUniqueReference()
6+
const segments = referenceNumber.split('-')
7+
8+
expect(segments).toHaveLength(3)
9+
10+
segments.forEach((segment) => {
11+
expect(segment).toHaveLength(3)
12+
})
13+
})
14+
15+
it('should generate a reference number with 2 segments when a prefix is provided', () => {
16+
const prefix = 'ABC'
17+
const referenceNumber = generateUniqueReference(prefix)
18+
const segments = referenceNumber.split('-')
19+
20+
expect(segments).toHaveLength(3) // 1 for prefix and 2 for segments
21+
expect(segments[0]).toBe(prefix)
22+
23+
segments.slice(1).forEach((segment) => {
24+
expect(segment).toHaveLength(3)
25+
})
26+
})
27+
28+
it('should generate different reference numbers on subsequent calls', () => {
29+
const referenceNumber1 = generateUniqueReference()
30+
const referenceNumber2 = generateUniqueReference()
31+
expect(referenceNumber1).not.toBe(referenceNumber2)
32+
})
33+
})

0 commit comments

Comments
 (0)