@defra/forms-engine-plugin 2.0.3 → 2.1.1
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/.public/javascripts/application.min.js +1 -1
- package/.public/javascripts/application.min.js.map +1 -1
- package/.public/javascripts/shared.min.js +1 -1
- package/.public/javascripts/shared.min.js.map +1 -1
- package/.public/stylesheets/application.min.css +2 -2
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/javascripts/file-upload.js +3 -3
- package/.server/client/javascripts/file-upload.js.map +1 -1
- package/.server/server/forms/page-events.yaml +87 -0
- package/.server/server/index.js +2 -1
- package/.server/server/index.js.map +1 -1
- package/.server/server/plugins/engine/routes/questions.d.ts +4 -2
- package/.server/server/plugins/engine/routes/questions.js +67 -43
- package/.server/server/plugins/engine/routes/questions.js.map +1 -1
- package/.server/server/plugins/engine/services/localFormsService.js +7 -9
- package/.server/server/plugins/engine/services/localFormsService.js.map +1 -1
- package/.server/server/routes/dummy-api.d.ts +38 -0
- package/.server/server/routes/dummy-api.js +33 -0
- package/.server/server/routes/dummy-api.js.map +1 -0
- package/.server/server/routes/index.d.ts +1 -0
- package/.server/server/routes/index.js +1 -0
- package/.server/server/routes/index.js.map +1 -1
- package/package.json +12 -10
- package/src/client/javascripts/file-upload.js +3 -3
- package/src/server/forms/page-events.yaml +87 -0
- package/src/server/index.ts +4 -2
- package/src/server/plugins/engine/routes/questions.test.ts +416 -0
- package/src/server/plugins/engine/routes/questions.ts +96 -40
- package/src/server/plugins/engine/services/localFormsService.js +7 -8
- package/src/server/routes/dummy-api.test.ts +96 -0
- package/src/server/routes/dummy-api.ts +62 -0
- package/src/server/routes/index.ts +1 -0
- package/.server/server/forms/register-as-a-unicorn-breeder.json +0 -393
- package/src/server/forms/register-as-a-unicorn-breeder.json +0 -393
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function calculateAge(day, month, year) {
|
|
2
|
+
const dobDate = new Date(Number(day), Number(month) - 1, Number(year));
|
|
3
|
+
const today = new Date();
|
|
4
|
+
let age = today.getFullYear() - dobDate.getFullYear();
|
|
5
|
+
const m = today.getMonth() - dobDate.getMonth();
|
|
6
|
+
if (m < 0 || m === 0 && today.getDate() < dobDate.getDate()) {
|
|
7
|
+
age--;
|
|
8
|
+
}
|
|
9
|
+
return age;
|
|
10
|
+
}
|
|
11
|
+
export default [{
|
|
12
|
+
method: 'POST',
|
|
13
|
+
path: '/api/example/on-load-page',
|
|
14
|
+
handler(request, _h) {
|
|
15
|
+
return {
|
|
16
|
+
submissionEvent: 'GET',
|
|
17
|
+
submissionReferenceNumber: request.payload.meta.referenceNumber
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
path: '/api/example/on-summary',
|
|
23
|
+
handler(request, _h) {
|
|
24
|
+
const [day, month, year] = request.payload.data.main.dateOfBirth.split('-');
|
|
25
|
+
const age = calculateAge(day, month, year);
|
|
26
|
+
return {
|
|
27
|
+
calculatedAge: age,
|
|
28
|
+
submissionEvent: 'POST',
|
|
29
|
+
submissionReferenceNumber: request.payload.meta.referenceNumber // example of receiving a payload from DXT
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}];
|
|
33
|
+
//# sourceMappingURL=dummy-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dummy-api.js","names":["calculateAge","day","month","year","dobDate","Date","Number","today","age","getFullYear","m","getMonth","getDate","method","path","handler","request","_h","submissionEvent","submissionReferenceNumber","payload","meta","referenceNumber","data","main","dateOfBirth","split","calculatedAge"],"sources":["../../../src/server/routes/dummy-api.ts"],"sourcesContent":["import { type Request, type ResponseToolkit } from '@hapi/hapi'\n\nfunction calculateAge(day: string, month: string, year: string) {\n const dobDate = new Date(Number(day), Number(month) - 1, Number(year))\n\n const today = new Date()\n\n let age = today.getFullYear() - dobDate.getFullYear()\n const m = today.getMonth() - dobDate.getMonth()\n\n if (m < 0 || (m === 0 && today.getDate() < dobDate.getDate())) {\n age--\n }\n\n return age\n}\n\nexport default [\n {\n method: 'POST',\n path: '/api/example/on-load-page',\n handler(\n request: Request<{ Payload: { meta: { referenceNumber: string } } }>,\n _h: ResponseToolkit\n ) {\n return {\n submissionEvent: 'GET',\n submissionReferenceNumber: request.payload.meta.referenceNumber\n }\n }\n },\n {\n method: 'POST',\n path: '/api/example/on-summary',\n handler(\n request: Request<{\n Payload: {\n data: {\n main: {\n applicantFirstName: string\n applicantLastName: string\n dateOfBirth: string\n }\n }\n meta: { event: string; referenceNumber: string }\n }\n }>,\n _h: ResponseToolkit\n ) {\n const [day, month, year] =\n request.payload.data.main.dateOfBirth.split('-')\n\n const age = calculateAge(day, month, year)\n\n return {\n calculatedAge: age,\n submissionEvent: 'POST',\n submissionReferenceNumber: request.payload.meta.referenceNumber // example of receiving a payload from DXT\n }\n }\n }\n]\n"],"mappings":"AAEA,SAASA,YAAYA,CAACC,GAAW,EAAEC,KAAa,EAAEC,IAAY,EAAE;EAC9D,MAAMC,OAAO,GAAG,IAAIC,IAAI,CAACC,MAAM,CAACL,GAAG,CAAC,EAAEK,MAAM,CAACJ,KAAK,CAAC,GAAG,CAAC,EAAEI,MAAM,CAACH,IAAI,CAAC,CAAC;EAEtE,MAAMI,KAAK,GAAG,IAAIF,IAAI,CAAC,CAAC;EAExB,IAAIG,GAAG,GAAGD,KAAK,CAACE,WAAW,CAAC,CAAC,GAAGL,OAAO,CAACK,WAAW,CAAC,CAAC;EACrD,MAAMC,CAAC,GAAGH,KAAK,CAACI,QAAQ,CAAC,CAAC,GAAGP,OAAO,CAACO,QAAQ,CAAC,CAAC;EAE/C,IAAID,CAAC,GAAG,CAAC,IAAKA,CAAC,KAAK,CAAC,IAAIH,KAAK,CAACK,OAAO,CAAC,CAAC,GAAGR,OAAO,CAACQ,OAAO,CAAC,CAAE,EAAE;IAC7DJ,GAAG,EAAE;EACP;EAEA,OAAOA,GAAG;AACZ;AAEA,eAAe,CACb;EACEK,MAAM,EAAE,MAAM;EACdC,IAAI,EAAE,2BAA2B;EACjCC,OAAOA,CACLC,OAAoE,EACpEC,EAAmB,EACnB;IACA,OAAO;MACLC,eAAe,EAAE,KAAK;MACtBC,yBAAyB,EAAEH,OAAO,CAACI,OAAO,CAACC,IAAI,CAACC;IAClD,CAAC;EACH;AACF,CAAC,EACD;EACET,MAAM,EAAE,MAAM;EACdC,IAAI,EAAE,yBAAyB;EAC/BC,OAAOA,CACLC,OAWE,EACFC,EAAmB,EACnB;IACA,MAAM,CAAChB,GAAG,EAAEC,KAAK,EAAEC,IAAI,CAAC,GACtBa,OAAO,CAACI,OAAO,CAACG,IAAI,CAACC,IAAI,CAACC,WAAW,CAACC,KAAK,CAAC,GAAG,CAAC;IAElD,MAAMlB,GAAG,GAAGR,YAAY,CAACC,GAAG,EAAEC,KAAK,EAAEC,IAAI,CAAC;IAE1C,OAAO;MACLwB,aAAa,EAAEnB,GAAG;MAClBU,eAAe,EAAE,MAAM;MACvBC,yBAAyB,EAAEH,OAAO,CAACI,OAAO,CAACC,IAAI,CAACC,eAAe,CAAC;IAClE,CAAC;EACH;AACF,CAAC,CACF","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["default","publicRoutes"],"sources":["../../../src/server/routes/index.ts"],"sourcesContent":["export { default as publicRoutes } from '~/src/server/routes/public.js'\n"],"mappings":"AAAA,SAASA,OAAO,IAAIC,YAAY","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["default","publicRoutes","dummyApiRoutes"],"sources":["../../../src/server/routes/index.ts"],"sourcesContent":["export { default as publicRoutes } from '~/src/server/routes/public.js'\nexport { default as dummyApiRoutes } from '~/src/server/routes/dummy-api.js'\n"],"mappings":"AAAA,SAASA,OAAO,IAAIC,YAAY;AAChC,SAASD,OAAO,IAAIE,cAAc","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -87,9 +87,9 @@
|
|
|
87
87
|
"btoa": "^1.2.1",
|
|
88
88
|
"convict": "^6.2.4",
|
|
89
89
|
"date-fns": "^4.1.0",
|
|
90
|
-
"dotenv": "^17.
|
|
90
|
+
"dotenv": "^17.2.1",
|
|
91
91
|
"expr-eval": "^2.0.2",
|
|
92
|
-
"govuk-frontend": "^5.
|
|
92
|
+
"govuk-frontend": "^5.11.1",
|
|
93
93
|
"hapi-pino": "^12.1.0",
|
|
94
94
|
"hapi-pulse": "^3.0.1",
|
|
95
95
|
"highlight.js": "^11.11.1",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"nunjucks": "^3.2.3",
|
|
104
104
|
"outdent": "^0.8.0",
|
|
105
105
|
"pino": "^9.7.0",
|
|
106
|
-
"pino-pretty": "^13.
|
|
106
|
+
"pino-pretty": "^13.1.1",
|
|
107
107
|
"proxy-agent": "^6.5.0",
|
|
108
108
|
"resolve": "^1.22.10",
|
|
109
109
|
"yaml": "^2.8.0"
|
|
@@ -116,23 +116,23 @@
|
|
|
116
116
|
"@babel/preset-typescript": "^7.27.1",
|
|
117
117
|
"@hapi/basic": "^7.0.2",
|
|
118
118
|
"@testing-library/dom": "^10.4.0",
|
|
119
|
-
"@testing-library/jest-dom": "^6.6.
|
|
119
|
+
"@testing-library/jest-dom": "^6.6.4",
|
|
120
120
|
"@types/atob": "^2.1.4",
|
|
121
121
|
"@types/btoa": "^1.2.5",
|
|
122
122
|
"@types/convict": "^6.1.6",
|
|
123
123
|
"@types/eslint": "^9.6.1",
|
|
124
124
|
"@types/govuk-frontend": "^5.9.0",
|
|
125
125
|
"@types/hapi": "^18.0.14",
|
|
126
|
-
"@types/hapi__catbox-memory": "^
|
|
126
|
+
"@types/hapi__catbox-memory": "^6.0.2",
|
|
127
127
|
"@types/hapi__cookie": "^12.0.5",
|
|
128
128
|
"@types/hapi__crumb": "^7.3.7",
|
|
129
129
|
"@types/hapi__yar": "^10.1.6",
|
|
130
130
|
"@types/hoek": "^4.1.7",
|
|
131
131
|
"@types/jest": "^30.0.0",
|
|
132
132
|
"@types/jsdom": "^21.1.7",
|
|
133
|
-
"@types/lodash": "^4.17.
|
|
133
|
+
"@types/lodash": "^4.17.20",
|
|
134
134
|
"@types/mysql": "^2.15.27",
|
|
135
|
-
"@types/node": "^24.0
|
|
135
|
+
"@types/node": "^24.2.0",
|
|
136
136
|
"@types/nunjucks": "^3.2.6",
|
|
137
137
|
"@types/resolve": "^1.20.6",
|
|
138
138
|
"@types/url-parse": "^1.4.11",
|
|
@@ -164,10 +164,12 @@
|
|
|
164
164
|
"eslint-plugin-promise": "^6.6.0",
|
|
165
165
|
"global-jsdom": "^26.0.0",
|
|
166
166
|
"husky": "^9.1.7",
|
|
167
|
-
"jest": "^30.0.
|
|
168
|
-
"jest-extended": "^
|
|
167
|
+
"jest": "^30.0.5",
|
|
168
|
+
"jest-extended": "^6.0.0",
|
|
169
169
|
"jsdom": "^26.1.0",
|
|
170
170
|
"lint-staged": "^15.3.0",
|
|
171
|
+
"mockdate": "^3.0.5",
|
|
172
|
+
"nock": "^14.0.8",
|
|
171
173
|
"postcss": "^8.5.6",
|
|
172
174
|
"postcss-load-config": "^6.0.1",
|
|
173
175
|
"postcss-loader": "^8.1.1",
|
|
@@ -66,10 +66,10 @@ function findOrCreateSummaryList(form, fileCountP) {
|
|
|
66
66
|
summaryList = document.createElement('dl')
|
|
67
67
|
summaryList.className = 'govuk-summary-list govuk-summary-list--long-key'
|
|
68
68
|
|
|
69
|
-
const
|
|
69
|
+
const buttonGroup = form.querySelector('.govuk-button-group')
|
|
70
70
|
|
|
71
|
-
if (
|
|
72
|
-
form.insertBefore(summaryList,
|
|
71
|
+
if (buttonGroup) {
|
|
72
|
+
form.insertBefore(summaryList, buttonGroup)
|
|
73
73
|
} else {
|
|
74
74
|
form.insertBefore(summaryList, fileCountP.nextSibling)
|
|
75
75
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Page events
|
|
3
|
+
engine: V2
|
|
4
|
+
schema: 2
|
|
5
|
+
startPage: '/summary'
|
|
6
|
+
pages:
|
|
7
|
+
- title: Your name
|
|
8
|
+
path: '/your-name'
|
|
9
|
+
components:
|
|
10
|
+
- type: TextField
|
|
11
|
+
title: What is your first name?
|
|
12
|
+
name: applicantFirstName
|
|
13
|
+
shortDescription: Your first name
|
|
14
|
+
hint: ''
|
|
15
|
+
options:
|
|
16
|
+
required: true
|
|
17
|
+
schema: {}
|
|
18
|
+
id: 1fb8e182-c709-4792-8f83-e01d8b1fee1a
|
|
19
|
+
- type: TextField
|
|
20
|
+
title: What is your last name?
|
|
21
|
+
name: applicantLastName
|
|
22
|
+
shortDescription: Your last name
|
|
23
|
+
hint: ''
|
|
24
|
+
options:
|
|
25
|
+
required: true
|
|
26
|
+
schema: {}
|
|
27
|
+
id: b68df7f1-d4f4-4c17-83c8-402f584906c9
|
|
28
|
+
next: []
|
|
29
|
+
id: 622a35ec-3795-418a-81f3-a45746959045
|
|
30
|
+
- title: ''
|
|
31
|
+
path: '/date-of-birth'
|
|
32
|
+
events:
|
|
33
|
+
onLoad:
|
|
34
|
+
type: http
|
|
35
|
+
options:
|
|
36
|
+
method: 'POST'
|
|
37
|
+
url: http://localhost:3009/api/example/on-load-page
|
|
38
|
+
components:
|
|
39
|
+
- type: Html
|
|
40
|
+
# technically context.data.submissionReferenceNumber is redundant as the reference number is available locally as context.referenceNumber
|
|
41
|
+
# but this is to demonstrate that the server can send data back to the client
|
|
42
|
+
content: >
|
|
43
|
+
<p class="govuk-body">
|
|
44
|
+
The backend received a full copy of the form state even though you haven't submitted the form yet.
|
|
45
|
+
</p>
|
|
46
|
+
<p class="govuk-body">
|
|
47
|
+
Your submission was received with the reference: <strong>{{ context.data.submissionReferenceNumber }}</strong>.
|
|
48
|
+
</p>
|
|
49
|
+
id: 334b10dc-3373-4928-8fed-575578a67de8
|
|
50
|
+
- type: DatePartsField
|
|
51
|
+
title: When is {{ applicantFirstName }} {{ applicantLastName }}'s birthday?
|
|
52
|
+
name: dateOfBirth
|
|
53
|
+
shortDescription: Your birthday
|
|
54
|
+
hint: ''
|
|
55
|
+
options:
|
|
56
|
+
required: true
|
|
57
|
+
schema: {}
|
|
58
|
+
id: '00738799-3489-4ab2-a57b-542eecb31bfa'
|
|
59
|
+
next: []
|
|
60
|
+
id: da0fbdb4-a2de-4650-be16-9ba552af135f
|
|
61
|
+
- id: 449a45f6-4541-4a46-91bd-8b8931b07b50
|
|
62
|
+
title: Summary
|
|
63
|
+
path: '/summary'
|
|
64
|
+
controller: SummaryPageController
|
|
65
|
+
events:
|
|
66
|
+
onLoad:
|
|
67
|
+
type: http
|
|
68
|
+
options:
|
|
69
|
+
method: 'POST'
|
|
70
|
+
url: http://localhost:3009/api/example/on-summary
|
|
71
|
+
onSave:
|
|
72
|
+
type: http
|
|
73
|
+
options:
|
|
74
|
+
method: 'POST'
|
|
75
|
+
url: http://localhost:3009/api/example/on-summary
|
|
76
|
+
components:
|
|
77
|
+
- type: Html
|
|
78
|
+
content: >
|
|
79
|
+
<h2 class="govuk-heading-m">Your age</h1>
|
|
80
|
+
<p class="govuk-body">
|
|
81
|
+
We've calculated that you are {{ context.data.calculatedAge }} years old. Only proceed if this is correct.
|
|
82
|
+
</p>
|
|
83
|
+
id: c42ea488-a38c-4b67-b8fa-4cf543a4f82d
|
|
84
|
+
next: []
|
|
85
|
+
conditions: []
|
|
86
|
+
sections: []
|
|
87
|
+
lists: []
|
package/src/server/index.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { Engine as CatboxRedis } from '@hapi/catbox-redis'
|
|
|
3
3
|
import hapi, {
|
|
4
4
|
type Request,
|
|
5
5
|
type ResponseToolkit,
|
|
6
|
-
type ServerOptions
|
|
6
|
+
type ServerOptions,
|
|
7
|
+
type ServerRoute
|
|
7
8
|
} from '@hapi/hapi'
|
|
8
9
|
import inert from '@hapi/inert'
|
|
9
10
|
import Scooter from '@hapi/scooter'
|
|
@@ -21,7 +22,7 @@ import pluginErrorPages from '~/src/server/plugins/errorPages.js'
|
|
|
21
22
|
import { plugin as pluginViews } from '~/src/server/plugins/nunjucks/index.js'
|
|
22
23
|
import pluginPulse from '~/src/server/plugins/pulse.js'
|
|
23
24
|
import pluginSession from '~/src/server/plugins/session.js'
|
|
24
|
-
import { publicRoutes } from '~/src/server/routes/index.js'
|
|
25
|
+
import { dummyApiRoutes, publicRoutes } from '~/src/server/routes/index.js'
|
|
25
26
|
import { prepareSecureContext } from '~/src/server/secure-context.js'
|
|
26
27
|
import { type RouteConfig } from '~/src/server/types.js'
|
|
27
28
|
|
|
@@ -120,6 +121,7 @@ export async function createServer(routeConfig?: RouteConfig) {
|
|
|
120
121
|
name: 'router',
|
|
121
122
|
register: (server) => {
|
|
122
123
|
server.route(publicRoutes)
|
|
124
|
+
server.route(dummyApiRoutes as ServerRoute[])
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
})
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import Boom from '@hapi/boom'
|
|
2
|
+
import { type ResponseObject, type ResponseToolkit } from '@hapi/hapi'
|
|
3
|
+
// eslint-disable-next-line n/no-unpublished-import
|
|
4
|
+
import nock from 'nock'
|
|
5
|
+
|
|
6
|
+
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
7
|
+
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'
|
|
8
|
+
import { redirectOrMakeHandler } from '~/src/server/plugins/engine/routes/index.js'
|
|
9
|
+
import {
|
|
10
|
+
makeGetHandler,
|
|
11
|
+
makePostHandler
|
|
12
|
+
} from '~/src/server/plugins/engine/routes/questions.js'
|
|
13
|
+
import { type FormContext } from '~/src/server/plugins/engine/types.js'
|
|
14
|
+
import {
|
|
15
|
+
type FormRequest,
|
|
16
|
+
type FormRequestPayload
|
|
17
|
+
} from '~/src/server/routes/types.js'
|
|
18
|
+
jest.mock('~/src/server/plugins/engine/models/SummaryViewModel', () => ({
|
|
19
|
+
SummaryViewModel: class {
|
|
20
|
+
summary = 'mocked summary'
|
|
21
|
+
}
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
jest.mock(
|
|
25
|
+
'~/src/server/plugins/engine/pageControllers/SummaryPageController',
|
|
26
|
+
() => ({
|
|
27
|
+
getFormSubmissionData: jest.fn().mockReturnValue([])
|
|
28
|
+
})
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
jest.mock('~/src/server/plugins/engine/outputFormatters/machine/v1', () => ({
|
|
32
|
+
format: jest.fn().mockReturnValue('mocked format')
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
jest.mock('~/src/server/plugins/engine/routes/index')
|
|
36
|
+
|
|
37
|
+
describe('makeGetHandler', () => {
|
|
38
|
+
const hMock: Pick<ResponseToolkit, 'redirect' | 'view'> = {
|
|
39
|
+
redirect: jest.fn(),
|
|
40
|
+
view: jest.fn()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
nock('http://test').persist().post('/load').reply(200, {
|
|
45
|
+
wasGetCalled: true
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
jest.mocked(redirectOrMakeHandler).mockRestore()
|
|
51
|
+
nock.cleanAll()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('calls the callback when events.onLoad.type is http', async () => {
|
|
55
|
+
let data = {}
|
|
56
|
+
|
|
57
|
+
const modelMock = {
|
|
58
|
+
basePath: 'some-base-path',
|
|
59
|
+
def: { name: 'Hello world' }
|
|
60
|
+
} as FormModel
|
|
61
|
+
|
|
62
|
+
const pageMock = createMockPageController(
|
|
63
|
+
modelMock,
|
|
64
|
+
(
|
|
65
|
+
_request: FormRequest,
|
|
66
|
+
context: FormContext,
|
|
67
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
68
|
+
) => {
|
|
69
|
+
data = context.data
|
|
70
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
75
|
+
|
|
76
|
+
const requestMock = {
|
|
77
|
+
params: { path: 'some-path' },
|
|
78
|
+
app: { model: modelMock }
|
|
79
|
+
} as FormRequest
|
|
80
|
+
|
|
81
|
+
jest
|
|
82
|
+
.mocked(redirectOrMakeHandler)
|
|
83
|
+
.mockImplementation(
|
|
84
|
+
(
|
|
85
|
+
_req: FormRequest | FormRequestPayload,
|
|
86
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
87
|
+
fn
|
|
88
|
+
) => Promise.resolve(fn(pageMock, contextMock))
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
await makeGetHandler()(requestMock, hMock)
|
|
92
|
+
|
|
93
|
+
expect(data).toMatchObject({
|
|
94
|
+
wasGetCalled: true
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('does not call the callback when the events.onLoad.type is not http', async () => {
|
|
99
|
+
let data = {}
|
|
100
|
+
|
|
101
|
+
const modelMock = {
|
|
102
|
+
basePath: 'some-base-path',
|
|
103
|
+
def: { name: 'Hello world' }
|
|
104
|
+
} as FormModel
|
|
105
|
+
|
|
106
|
+
const pageMock = createMockPageController(
|
|
107
|
+
modelMock,
|
|
108
|
+
(
|
|
109
|
+
_request: FormRequest,
|
|
110
|
+
context: FormContext,
|
|
111
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
112
|
+
) => {
|
|
113
|
+
data = context.data
|
|
114
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
pageMock.events = {}
|
|
119
|
+
|
|
120
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
121
|
+
|
|
122
|
+
const requestMock = {
|
|
123
|
+
params: { path: 'some-path' },
|
|
124
|
+
app: { model: modelMock }
|
|
125
|
+
} as FormRequest
|
|
126
|
+
|
|
127
|
+
jest
|
|
128
|
+
.mocked(redirectOrMakeHandler)
|
|
129
|
+
.mockImplementation(
|
|
130
|
+
(
|
|
131
|
+
_req: FormRequest | FormRequestPayload,
|
|
132
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
133
|
+
fn
|
|
134
|
+
) => Promise.resolve(fn(pageMock, contextMock))
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
await makeGetHandler()(requestMock, hMock)
|
|
138
|
+
|
|
139
|
+
expect(data).toMatchObject({})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('throws when model is missing', async () => {
|
|
143
|
+
let error
|
|
144
|
+
|
|
145
|
+
const modelMock = {
|
|
146
|
+
basePath: 'some-base-path',
|
|
147
|
+
def: { name: 'Hello world' }
|
|
148
|
+
} as FormModel
|
|
149
|
+
|
|
150
|
+
const pageMock = createMockPageController(
|
|
151
|
+
modelMock,
|
|
152
|
+
(
|
|
153
|
+
_request: FormRequest,
|
|
154
|
+
_context: FormContext,
|
|
155
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
156
|
+
) => {
|
|
157
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
162
|
+
|
|
163
|
+
const requestMock = {
|
|
164
|
+
params: { path: 'some-path' },
|
|
165
|
+
app: {}
|
|
166
|
+
} as FormRequest
|
|
167
|
+
|
|
168
|
+
jest
|
|
169
|
+
.mocked(redirectOrMakeHandler)
|
|
170
|
+
.mockImplementation(
|
|
171
|
+
async (
|
|
172
|
+
_req: FormRequest | FormRequestPayload,
|
|
173
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
174
|
+
fn
|
|
175
|
+
) => {
|
|
176
|
+
try {
|
|
177
|
+
await fn(pageMock, contextMock)
|
|
178
|
+
} catch (err) {
|
|
179
|
+
error = err
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
await makeGetHandler()(requestMock, hMock)
|
|
187
|
+
|
|
188
|
+
expect(error).toEqual(Boom.notFound('No model found for /some-path'))
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('makePostHandler', () => {
|
|
193
|
+
const hMock: Pick<ResponseToolkit, 'redirect' | 'view'> = {
|
|
194
|
+
redirect: jest.fn(),
|
|
195
|
+
view: jest.fn()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
beforeEach(() => {
|
|
199
|
+
nock('http://test').post('/save').reply(200, {
|
|
200
|
+
wasPostCalled: true
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
afterEach(() => {
|
|
205
|
+
jest.mocked(redirectOrMakeHandler).mockRestore()
|
|
206
|
+
nock.cleanAll()
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('calls the callback when events.onSave.type is http and the page controller was successful', async () => {
|
|
210
|
+
const mockPostResponse: ResponseObject = {
|
|
211
|
+
statusCode: 200
|
|
212
|
+
} as ResponseObject
|
|
213
|
+
|
|
214
|
+
const modelMock = {
|
|
215
|
+
basePath: 'some-base-path',
|
|
216
|
+
def: { name: 'Hello world' }
|
|
217
|
+
} as FormModel
|
|
218
|
+
|
|
219
|
+
const pageMock = createMockPageController(
|
|
220
|
+
modelMock,
|
|
221
|
+
(
|
|
222
|
+
_request: FormRequest,
|
|
223
|
+
_context: FormContext,
|
|
224
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
225
|
+
) => {
|
|
226
|
+
// do return a valid ResponseObject wrapped in Promise.resolve
|
|
227
|
+
return mockPostResponse
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
232
|
+
|
|
233
|
+
const requestMock = {
|
|
234
|
+
params: { path: 'some-path' },
|
|
235
|
+
app: { model: modelMock },
|
|
236
|
+
payload: { some: 'payload' }
|
|
237
|
+
} as unknown as FormRequestPayload
|
|
238
|
+
|
|
239
|
+
jest
|
|
240
|
+
.mocked(redirectOrMakeHandler)
|
|
241
|
+
.mockImplementation(
|
|
242
|
+
(
|
|
243
|
+
_req: FormRequest | FormRequestPayload,
|
|
244
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
245
|
+
fn
|
|
246
|
+
) => Promise.resolve(fn(pageMock, contextMock))
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
const response = await makePostHandler()(requestMock, hMock)
|
|
250
|
+
|
|
251
|
+
expect(nock.pendingMocks()).toBeEmpty()
|
|
252
|
+
expect(response).toBe(mockPostResponse)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('does not call the callback when the events.onSave.type is not http', async () => {
|
|
256
|
+
const modelMock = {
|
|
257
|
+
basePath: 'some-base-path',
|
|
258
|
+
def: { name: 'Hello world' }
|
|
259
|
+
} as FormModel
|
|
260
|
+
|
|
261
|
+
const pageMock = createMockPageController(
|
|
262
|
+
modelMock,
|
|
263
|
+
(
|
|
264
|
+
_request: FormRequest,
|
|
265
|
+
_context: FormContext,
|
|
266
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
267
|
+
) => {
|
|
268
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
pageMock.events = {}
|
|
273
|
+
|
|
274
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
275
|
+
|
|
276
|
+
const requestMock = {
|
|
277
|
+
params: { path: 'some-path' },
|
|
278
|
+
app: { model: modelMock },
|
|
279
|
+
payload: { some: 'payload' }
|
|
280
|
+
} as unknown as FormRequestPayload
|
|
281
|
+
|
|
282
|
+
jest
|
|
283
|
+
.mocked(redirectOrMakeHandler)
|
|
284
|
+
.mockImplementation(
|
|
285
|
+
(
|
|
286
|
+
_req: FormRequest | FormRequestPayload,
|
|
287
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
288
|
+
fn
|
|
289
|
+
) => Promise.resolve(fn(pageMock, contextMock))
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
await makePostHandler()(requestMock, hMock)
|
|
293
|
+
|
|
294
|
+
expect(nock.pendingMocks()).not.toBeEmpty()
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('does not call the callback when events.onSave.type is http and the page controller was unsuccessful', async () => {
|
|
298
|
+
const mockPostResponse: ResponseObject = {
|
|
299
|
+
statusCode: 500
|
|
300
|
+
} as ResponseObject
|
|
301
|
+
|
|
302
|
+
const modelMock = {
|
|
303
|
+
basePath: 'some-base-path',
|
|
304
|
+
def: { name: 'Hello world' }
|
|
305
|
+
} as FormModel
|
|
306
|
+
|
|
307
|
+
const pageMock = createMockPageController(
|
|
308
|
+
modelMock,
|
|
309
|
+
(
|
|
310
|
+
_request: FormRequest,
|
|
311
|
+
_context: FormContext,
|
|
312
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
313
|
+
) => {
|
|
314
|
+
// do return a valid ResponseObject wrapped in Promise.resolve
|
|
315
|
+
return mockPostResponse
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
320
|
+
|
|
321
|
+
const requestMock = {
|
|
322
|
+
params: { path: 'some-path' },
|
|
323
|
+
app: { model: modelMock },
|
|
324
|
+
payload: { some: 'payload' }
|
|
325
|
+
} as unknown as FormRequestPayload
|
|
326
|
+
|
|
327
|
+
jest
|
|
328
|
+
.mocked(redirectOrMakeHandler)
|
|
329
|
+
.mockImplementation(
|
|
330
|
+
(
|
|
331
|
+
_req: FormRequest | FormRequestPayload,
|
|
332
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
333
|
+
fn
|
|
334
|
+
) => Promise.resolve(fn(pageMock, contextMock))
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
await makePostHandler()(requestMock, hMock)
|
|
338
|
+
|
|
339
|
+
expect(nock.pendingMocks()).not.toBeEmpty()
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('throws when model is missing', async () => {
|
|
343
|
+
let error
|
|
344
|
+
|
|
345
|
+
const modelMock = {
|
|
346
|
+
basePath: 'some-base-path',
|
|
347
|
+
def: { name: 'Hello world' }
|
|
348
|
+
} as FormModel
|
|
349
|
+
|
|
350
|
+
const pageMock = createMockPageController(
|
|
351
|
+
modelMock,
|
|
352
|
+
(
|
|
353
|
+
_request: FormRequest,
|
|
354
|
+
_context: FormContext,
|
|
355
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
356
|
+
) => {
|
|
357
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
358
|
+
}
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
const contextMock = { data: {}, model: {} } as unknown as FormContext
|
|
362
|
+
|
|
363
|
+
const requestMock = {
|
|
364
|
+
params: { path: 'some-path' },
|
|
365
|
+
app: {},
|
|
366
|
+
payload: { some: 'payload' }
|
|
367
|
+
} as unknown as FormRequestPayload
|
|
368
|
+
|
|
369
|
+
jest
|
|
370
|
+
.mocked(redirectOrMakeHandler)
|
|
371
|
+
.mockImplementation(
|
|
372
|
+
async (
|
|
373
|
+
_req: FormRequest | FormRequestPayload,
|
|
374
|
+
_h: Pick<ResponseToolkit, 'redirect' | 'view'>,
|
|
375
|
+
fn
|
|
376
|
+
) => {
|
|
377
|
+
try {
|
|
378
|
+
await fn(pageMock, contextMock)
|
|
379
|
+
} catch (err) {
|
|
380
|
+
error = err
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return Promise.resolve({} as unknown as ResponseObject)
|
|
384
|
+
}
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
await makePostHandler()(requestMock, hMock)
|
|
388
|
+
|
|
389
|
+
expect(error).toEqual(Boom.notFound('No model found for /some-path'))
|
|
390
|
+
})
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
function createMockPageController(
|
|
394
|
+
model: FormModel,
|
|
395
|
+
routeHandler: (
|
|
396
|
+
request: FormRequest,
|
|
397
|
+
context: FormContext,
|
|
398
|
+
h: Pick<ResponseToolkit, 'redirect' | 'view'>
|
|
399
|
+
) => ResponseObject | Promise<ResponseObject>
|
|
400
|
+
): PageControllerClass {
|
|
401
|
+
return {
|
|
402
|
+
model,
|
|
403
|
+
events: {
|
|
404
|
+
onLoad: {
|
|
405
|
+
type: 'http',
|
|
406
|
+
options: { method: 'POST', url: 'http://test/load' }
|
|
407
|
+
},
|
|
408
|
+
onSave: {
|
|
409
|
+
type: 'http',
|
|
410
|
+
options: { method: 'POST', url: 'http://test/save' }
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
makeGetRouteHandler: () => routeHandler,
|
|
414
|
+
makePostRouteHandler: () => routeHandler
|
|
415
|
+
} as unknown as PageControllerClass
|
|
416
|
+
}
|