@dwp/govuk-casa 8.15.0 → 9.1.0
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/README.md +9 -9
- package/dist/assets/css/casa.css +2 -1
- package/dist/assets/css/casa.css.map +1 -0
- package/dist/casa.d.ts +122 -99
- package/dist/casa.js +120 -88
- package/dist/casa.js.map +1 -1
- package/dist/lib/CasaTemplateLoader.d.ts +4 -4
- package/dist/lib/CasaTemplateLoader.js +16 -16
- package/dist/lib/CasaTemplateLoader.js.map +1 -1
- package/dist/lib/JourneyContext.d.ts +38 -40
- package/dist/lib/JourneyContext.js +89 -75
- package/dist/lib/JourneyContext.js.map +1 -1
- package/dist/lib/MutableRouter.d.ts +40 -41
- package/dist/lib/MutableRouter.js +64 -71
- package/dist/lib/MutableRouter.js.map +1 -1
- package/dist/lib/Plan.d.ts +29 -26
- package/dist/lib/Plan.js +85 -71
- package/dist/lib/Plan.js.map +1 -1
- package/dist/lib/ValidationError.d.ts +16 -15
- package/dist/lib/ValidationError.js +21 -20
- package/dist/lib/ValidationError.js.map +1 -1
- package/dist/lib/ValidatorFactory.d.ts +15 -13
- package/dist/lib/ValidatorFactory.js +14 -12
- package/dist/lib/ValidatorFactory.js.map +1 -1
- package/dist/lib/configuration-ingestor.d.ts +37 -40
- package/dist/lib/configuration-ingestor.js +93 -93
- package/dist/lib/configuration-ingestor.js.map +1 -1
- package/dist/lib/configure.d.ts +6 -6
- package/dist/lib/configure.js +14 -12
- package/dist/lib/configure.js.map +1 -1
- package/dist/lib/constants.d.ts +1 -3
- package/dist/lib/constants.js +9 -11
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/context-id-generators.d.ts +3 -5
- package/dist/lib/context-id-generators.js +7 -6
- package/dist/lib/context-id-generators.js.map +1 -1
- package/dist/lib/end-session.d.ts +4 -4
- package/dist/lib/end-session.js +5 -5
- package/dist/lib/field.d.ts +24 -29
- package/dist/lib/field.js +41 -70
- package/dist/lib/field.js.map +1 -1
- package/dist/lib/index.d.ts +13 -13
- package/dist/lib/logger.d.ts +7 -6
- package/dist/lib/logger.js +7 -7
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/mount.d.ts +5 -5
- package/dist/lib/mount.js +12 -17
- package/dist/lib/mount.js.map +1 -1
- package/dist/lib/nunjucks-filters.d.ts +10 -12
- package/dist/lib/nunjucks-filters.js +35 -35
- package/dist/lib/nunjucks-filters.js.map +1 -1
- package/dist/lib/nunjucks.d.ts +7 -5
- package/dist/lib/nunjucks.js +10 -8
- package/dist/lib/nunjucks.js.map +1 -1
- package/dist/lib/utils.d.ts +19 -19
- package/dist/lib/utils.js +62 -55
- package/dist/lib/utils.js.map +1 -1
- package/dist/lib/validators/dateObject.d.ts +29 -22
- package/dist/lib/validators/dateObject.js +58 -49
- package/dist/lib/validators/dateObject.js.map +1 -1
- package/dist/lib/validators/email.d.ts +4 -4
- package/dist/lib/validators/email.js +4 -4
- package/dist/lib/validators/inArray.d.ts +4 -4
- package/dist/lib/validators/inArray.js +7 -8
- package/dist/lib/validators/inArray.js.map +1 -1
- package/dist/lib/validators/index.d.ts +10 -10
- package/dist/lib/validators/index.js +1 -3
- package/dist/lib/validators/index.js.map +1 -1
- package/dist/lib/validators/nino.d.ts +9 -8
- package/dist/lib/validators/nino.js +14 -10
- package/dist/lib/validators/nino.js.map +1 -1
- package/dist/lib/validators/postalAddressObject.d.ts +37 -24
- package/dist/lib/validators/postalAddressObject.js +65 -46
- package/dist/lib/validators/postalAddressObject.js.map +1 -1
- package/dist/lib/validators/range.d.ts +12 -8
- package/dist/lib/validators/range.js +11 -9
- package/dist/lib/validators/range.js.map +1 -1
- package/dist/lib/validators/regex.d.ts +4 -4
- package/dist/lib/validators/regex.js +5 -5
- package/dist/lib/validators/required.d.ts +6 -6
- package/dist/lib/validators/required.js +9 -11
- package/dist/lib/validators/required.js.map +1 -1
- package/dist/lib/validators/strlen.d.ts +12 -8
- package/dist/lib/validators/strlen.js +13 -11
- package/dist/lib/validators/strlen.js.map +1 -1
- package/dist/lib/validators/wordCount.d.ts +12 -8
- package/dist/lib/validators/wordCount.js +15 -11
- package/dist/lib/validators/wordCount.js.map +1 -1
- package/dist/lib/waypoint-url.d.ts +16 -13
- package/dist/lib/waypoint-url.js +39 -36
- package/dist/lib/waypoint-url.js.map +1 -1
- package/dist/middleware/body-parser.d.ts +1 -1
- package/dist/middleware/body-parser.js +6 -6
- package/dist/middleware/body-parser.js.map +1 -1
- package/dist/middleware/data.d.ts +1 -1
- package/dist/middleware/data.js +8 -7
- package/dist/middleware/data.js.map +1 -1
- package/dist/middleware/gather-fields.d.ts +2 -2
- package/dist/middleware/gather-fields.js +6 -4
- package/dist/middleware/gather-fields.js.map +1 -1
- package/dist/middleware/i18n.js +13 -15
- package/dist/middleware/i18n.js.map +1 -1
- package/dist/middleware/post.js +30 -18
- package/dist/middleware/post.js.map +1 -1
- package/dist/middleware/pre.d.ts +2 -2
- package/dist/middleware/pre.js +46 -26
- package/dist/middleware/pre.js.map +1 -1
- package/dist/middleware/progress-journey.d.ts +1 -1
- package/dist/middleware/progress-journey.js +5 -5
- package/dist/middleware/progress-journey.js.map +1 -1
- package/dist/middleware/sanitise-fields.d.ts +1 -1
- package/dist/middleware/sanitise-fields.js +13 -11
- package/dist/middleware/sanitise-fields.js.map +1 -1
- package/dist/middleware/serve-first-waypoint.d.ts +3 -3
- package/dist/middleware/serve-first-waypoint.js +8 -6
- package/dist/middleware/serve-first-waypoint.js.map +1 -1
- package/dist/middleware/session.js +14 -11
- package/dist/middleware/session.js.map +1 -1
- package/dist/middleware/skip-waypoint.d.ts +1 -1
- package/dist/middleware/skip-waypoint.js +3 -3
- package/dist/middleware/skip-waypoint.js.map +1 -1
- package/dist/middleware/steer-journey.d.ts +1 -1
- package/dist/middleware/steer-journey.js +16 -14
- package/dist/middleware/steer-journey.js.map +1 -1
- package/dist/middleware/strip-proxy-path.d.ts +1 -1
- package/dist/middleware/strip-proxy-path.js +3 -3
- package/dist/middleware/strip-proxy-path.js.map +1 -1
- package/dist/middleware/validate-fields.d.ts +1 -1
- package/dist/middleware/validate-fields.js +2 -5
- package/dist/middleware/validate-fields.js.map +1 -1
- package/dist/routes/ancillary.d.ts +3 -3
- package/dist/routes/ancillary.js +4 -4
- package/dist/routes/ancillary.js.map +1 -1
- package/dist/routes/journey.d.ts +2 -2
- package/dist/routes/journey.js +91 -39
- package/dist/routes/journey.js.map +1 -1
- package/dist/routes/static.d.ts +7 -5
- package/dist/routes/static.js +20 -20
- package/dist/routes/static.js.map +1 -1
- package/package.json +17 -16
- package/src/casa.js +134 -102
- package/src/lib/CasaTemplateLoader.js +24 -19
- package/src/lib/JourneyContext.js +147 -107
- package/src/lib/MutableRouter.js +72 -74
- package/src/lib/Plan.js +145 -97
- package/src/lib/ValidationError.js +25 -21
- package/src/lib/ValidatorFactory.js +17 -13
- package/src/lib/configuration-ingestor.js +147 -110
- package/src/lib/configure.js +34 -32
- package/src/lib/constants.js +9 -11
- package/src/lib/context-id-generators.js +40 -43
- package/src/lib/end-session.js +6 -6
- package/src/lib/field.js +74 -78
- package/src/lib/index.js +12 -12
- package/src/lib/logger.js +9 -9
- package/src/lib/mount.js +70 -80
- package/src/lib/nunjucks-filters.js +56 -59
- package/src/lib/nunjucks.js +23 -18
- package/src/lib/utils.js +78 -57
- package/src/lib/validators/dateObject.js +71 -60
- package/src/lib/validators/email.js +8 -8
- package/src/lib/validators/inArray.js +10 -11
- package/src/lib/validators/index.js +12 -14
- package/src/lib/validators/nino.js +29 -15
- package/src/lib/validators/postalAddressObject.js +87 -63
- package/src/lib/validators/range.js +14 -12
- package/src/lib/validators/regex.js +8 -8
- package/src/lib/validators/required.js +16 -16
- package/src/lib/validators/strlen.js +16 -14
- package/src/lib/validators/wordCount.js +22 -14
- package/src/lib/waypoint-url.js +64 -46
- package/src/middleware/body-parser.js +10 -10
- package/src/middleware/csrf.js +1 -1
- package/src/middleware/data.js +28 -24
- package/src/middleware/gather-fields.js +10 -9
- package/src/middleware/i18n.js +35 -37
- package/src/middleware/post.js +41 -21
- package/src/middleware/pre.js +62 -40
- package/src/middleware/progress-journey.js +32 -18
- package/src/middleware/sanitise-fields.js +43 -20
- package/src/middleware/serve-first-waypoint.js +14 -12
- package/src/middleware/session.js +74 -61
- package/src/middleware/skip-waypoint.js +7 -9
- package/src/middleware/steer-journey.js +40 -28
- package/src/middleware/strip-proxy-path.js +8 -7
- package/src/middleware/validate-fields.js +5 -12
- package/src/routes/ancillary.js +5 -7
- package/src/routes/journey.js +159 -85
- package/src/routes/static.js +62 -30
- package/views/casa/components/character-count/README.md +2 -2
- package/views/casa/components/checkboxes/README.md +6 -6
- package/views/casa/components/date-input/README.md +7 -7
- package/views/casa/components/input/README.md +2 -2
- package/views/casa/components/journey-form/README.md +33 -14
- package/views/casa/components/postal-address-object/README.md +4 -4
- package/views/casa/components/radios/README.md +6 -6
- package/views/casa/components/select/README.md +6 -6
- package/views/casa/components/textarea/README.md +2 -2
- package/views/casa/partials/scripts.njk +5 -3
- package/views/casa/partials/styles.njk +1 -4
- package/dist/assets/css/casa-ie8.css +0 -1
package/src/lib/Plan.js
CHANGED
|
@@ -1,91 +1,103 @@
|
|
|
1
|
-
import { Graph } from
|
|
2
|
-
import JourneyContext from
|
|
3
|
-
import logger from
|
|
1
|
+
import { Graph } from "@dagrejs/graphlib";
|
|
2
|
+
import JourneyContext from "./JourneyContext.js";
|
|
3
|
+
import logger from "./logger.js";
|
|
4
4
|
|
|
5
|
-
const log = logger(
|
|
5
|
+
const log = logger("lib:plan");
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
+
* @typedef {import("../casa").PlanRoute} PlanRoute
|
|
8
9
|
* @access private
|
|
9
|
-
* @typedef {import('../casa').PlanRoute} PlanRoute
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
+
* @typedef {import("../casa").PlanRouteCondition} PlanRouteCondition
|
|
13
14
|
* @access private
|
|
14
|
-
* @typedef {import('../casa').PlanRouteCondition} PlanRouteCondition
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
+
* @typedef {import("../casa").PlanTraverseOptions} PlanTraverseOptions
|
|
18
19
|
* @access private
|
|
19
|
-
* @typedef {import('../casa').PlanTraverseOptions} PlanTraverseOptions
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
+
* @typedef {import("../casa").PlanArbiter} PlanArbiter
|
|
23
24
|
* @access private
|
|
24
|
-
* @typedef {import('../casa').PlanArbiter} PlanArbiter
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* @typedef {object} PlanConstructorOptions
|
|
29
|
-
* @property {boolean} [validateBeforeRouteCondition=true] Check page validity
|
|
30
|
-
*
|
|
29
|
+
* @property {boolean} [validateBeforeRouteCondition=true] Check page validity
|
|
30
|
+
* before conditions. Default is `true`
|
|
31
|
+
* @property {string | PlanArbiter} [arbiter=undefined] Arbitration mechanism.
|
|
32
|
+
* Default is `undefined`
|
|
31
33
|
*/
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Will check if the source waypoint has specifically passed validation, i.e
|
|
35
37
|
* there is a "null" validation entry for the route source.
|
|
36
38
|
*
|
|
37
|
-
* @access private
|
|
38
39
|
* @type {PlanRouteCondition}
|
|
40
|
+
* @access private
|
|
39
41
|
*/
|
|
40
42
|
function defaultNextFollow(r, context) {
|
|
41
43
|
const { validation: v = {} } = context.toObject();
|
|
42
|
-
return
|
|
44
|
+
return (
|
|
45
|
+
Object.prototype.hasOwnProperty.call(v, r.source) && v[r.source] === null
|
|
46
|
+
);
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
/**
|
|
46
50
|
* Will check if the target waypoint (the one we're moving back to) has
|
|
47
51
|
* specifically passed validation.
|
|
48
52
|
*
|
|
49
|
-
* @access private
|
|
50
53
|
* @type {PlanRouteCondition}
|
|
54
|
+
* @access private
|
|
51
55
|
*/
|
|
52
56
|
function defaultPrevFollow(r, context) {
|
|
53
57
|
const { validation: v = {} } = context.toObject();
|
|
54
|
-
return
|
|
58
|
+
return (
|
|
59
|
+
Object.prototype.hasOwnProperty.call(v, r.target) && v[r.target] === null
|
|
60
|
+
);
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
/**
|
|
58
64
|
* Validate a given waypoint ID.
|
|
59
65
|
*
|
|
60
|
-
* @access private
|
|
61
66
|
* @param {string} val Waypoint ID
|
|
62
67
|
* @returns {void}
|
|
63
68
|
* @throws {TypeError} If waypoint ID is not a string
|
|
64
69
|
* @throws {SyntaxError} If waypoint ID is incorrectly formatted
|
|
70
|
+
* @access private
|
|
65
71
|
*/
|
|
66
72
|
function validateWaypointId(val) {
|
|
67
|
-
if (typeof val !==
|
|
68
|
-
throw new TypeError(
|
|
73
|
+
if (typeof val !== "string") {
|
|
74
|
+
throw new TypeError(
|
|
75
|
+
`Expected waypoint id to be a string, got ${typeof val}`,
|
|
76
|
+
);
|
|
69
77
|
}
|
|
70
|
-
if (val.substr(0, 6) ===
|
|
71
|
-
throw new SyntaxError(
|
|
78
|
+
if (val.substr(0, 6) === "url://" && !val.endsWith("/")) {
|
|
79
|
+
throw new SyntaxError("url:// waypoints must include a trailing /");
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
|
|
75
83
|
/**
|
|
76
84
|
* Validate a given route name.
|
|
77
85
|
*
|
|
78
|
-
* @access private
|
|
79
86
|
* @param {string} val Route name
|
|
80
87
|
* @returns {string} The route name
|
|
81
88
|
* @throws {TypeError} If route name is not a string
|
|
82
89
|
* @throws {ReferenceError} If route name is neither "next" or "prev"
|
|
90
|
+
* @access private
|
|
83
91
|
*/
|
|
84
92
|
function validateRouteName(val) {
|
|
85
|
-
if (typeof val !==
|
|
86
|
-
throw new TypeError(
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
if (typeof val !== "string") {
|
|
94
|
+
throw new TypeError(
|
|
95
|
+
`Expected route name to be a string, got ${typeof val}`,
|
|
96
|
+
);
|
|
97
|
+
} else if (!["next", "prev"].includes(val)) {
|
|
98
|
+
throw new ReferenceError(
|
|
99
|
+
`Expected route name to be one of next or prev. Got ${val}`,
|
|
100
|
+
);
|
|
89
101
|
}
|
|
90
102
|
return val;
|
|
91
103
|
}
|
|
@@ -93,26 +105,28 @@ function validateRouteName(val) {
|
|
|
93
105
|
/**
|
|
94
106
|
* Validate a given route condition.
|
|
95
107
|
*
|
|
96
|
-
* @access private
|
|
97
108
|
* @param {PlanRouteCondition} val The condition function
|
|
98
109
|
* @returns {void}
|
|
99
110
|
* @throws {TypeError} If condition is not a string
|
|
111
|
+
* @access private
|
|
100
112
|
*/
|
|
101
113
|
function validateRouteCondition(val) {
|
|
102
114
|
if (!(val instanceof Function)) {
|
|
103
|
-
throw new TypeError(
|
|
115
|
+
throw new TypeError(
|
|
116
|
+
`Expected route condition to be a function, got ${typeof val}`,
|
|
117
|
+
);
|
|
104
118
|
}
|
|
105
119
|
}
|
|
106
120
|
|
|
107
121
|
/**
|
|
108
|
-
* Creates a user friendly route structure from a given graph edge which will
|
|
109
|
-
*
|
|
122
|
+
* Creates a user friendly route structure from a given graph edge which will be
|
|
123
|
+
* used in userland. This is the object that will be passed into follow
|
|
110
124
|
* functions too as the "route" parameter.
|
|
111
125
|
*
|
|
112
|
-
* @access private
|
|
113
126
|
* @param {object} dgraph Directed graph instance.
|
|
114
127
|
* @param {object} edge Graph edge object.
|
|
115
128
|
* @returns {PlanRoute} Route.
|
|
129
|
+
* @access private
|
|
116
130
|
*/
|
|
117
131
|
const makeRouteObject = (dgraph, edge) => {
|
|
118
132
|
const label = dgraph.edge(edge) || {};
|
|
@@ -140,13 +154,9 @@ const reExitNodeProtocol = /^[a-z]+:\/\//i;
|
|
|
140
154
|
*/
|
|
141
155
|
const priv = new WeakMap();
|
|
142
156
|
|
|
143
|
-
/**
|
|
144
|
-
* @memberof module:@dwp/govuk-casa
|
|
145
|
-
*/
|
|
157
|
+
/** @memberof module:@dwp/govuk-casa */
|
|
146
158
|
export default class Plan {
|
|
147
|
-
/**
|
|
148
|
-
* @type {string[]} These waypoints can be skipped
|
|
149
|
-
*/
|
|
159
|
+
/** @type {string[]} These Waypoints can be skipped */
|
|
150
160
|
#skippableWaypoints;
|
|
151
161
|
|
|
152
162
|
/**
|
|
@@ -174,14 +184,18 @@ export default class Plan {
|
|
|
174
184
|
});
|
|
175
185
|
|
|
176
186
|
// Gather options
|
|
177
|
-
const options = Object.assign(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
const options = Object.assign(
|
|
188
|
+
Object.create(null),
|
|
189
|
+
{
|
|
190
|
+
// When true, the validation state of the source node must be `null` (i.e.
|
|
191
|
+
// no validation errors) before any custom route conditions are evaluated.
|
|
192
|
+
validateBeforeRouteCondition: true,
|
|
193
|
+
|
|
194
|
+
// Traversal arbitration
|
|
195
|
+
arbiter: undefined,
|
|
196
|
+
},
|
|
197
|
+
opts,
|
|
198
|
+
);
|
|
185
199
|
Object.freeze(options);
|
|
186
200
|
|
|
187
201
|
priv.set(this, {
|
|
@@ -217,7 +231,7 @@ export default class Plan {
|
|
|
217
231
|
/**
|
|
218
232
|
* Add one or more skippable waypoints.
|
|
219
233
|
*
|
|
220
|
-
* @param
|
|
234
|
+
* @param {...string} waypoints Waypoints
|
|
221
235
|
* @returns {Plan} Chain
|
|
222
236
|
*/
|
|
223
237
|
addSkippables(...waypoints) {
|
|
@@ -261,7 +275,9 @@ export default class Plan {
|
|
|
261
275
|
*/
|
|
262
276
|
getRoutes() {
|
|
263
277
|
const self = priv.get(this);
|
|
264
|
-
return self.dgraph
|
|
278
|
+
return self.dgraph
|
|
279
|
+
.edges()
|
|
280
|
+
.map((edge) => makeRouteObject(self.dgraph, edge));
|
|
265
281
|
}
|
|
266
282
|
|
|
267
283
|
/**
|
|
@@ -286,7 +302,9 @@ export default class Plan {
|
|
|
286
302
|
*/
|
|
287
303
|
getOutwardRoutes(src, tgt = null) {
|
|
288
304
|
const self = priv.get(this);
|
|
289
|
-
return self.dgraph
|
|
305
|
+
return self.dgraph
|
|
306
|
+
.outEdges(src, tgt)
|
|
307
|
+
.map((e) => makeRouteObject(self.dgraph, e));
|
|
290
308
|
}
|
|
291
309
|
|
|
292
310
|
/**
|
|
@@ -298,14 +316,14 @@ export default class Plan {
|
|
|
298
316
|
* @returns {PlanRoute[]} Route objects found.
|
|
299
317
|
*/
|
|
300
318
|
getPrevOutwardRoutes(src, tgt = null) {
|
|
301
|
-
return this.getOutwardRoutes(src, tgt).filter((r) => r.name ===
|
|
319
|
+
return this.getOutwardRoutes(src, tgt).filter((r) => r.name === "prev");
|
|
302
320
|
}
|
|
303
321
|
|
|
304
322
|
/**
|
|
305
323
|
* Add a sequence of waypoints that will follow on from each other, with no
|
|
306
324
|
* routing logic between them.
|
|
307
325
|
*
|
|
308
|
-
* @param
|
|
326
|
+
* @param {...string} waypoints Waypoints to add
|
|
309
327
|
* @returns {void}
|
|
310
328
|
*/
|
|
311
329
|
addSequence(...waypoints) {
|
|
@@ -326,7 +344,7 @@ export default class Plan {
|
|
|
326
344
|
* @returns {Plan} Chain
|
|
327
345
|
*/
|
|
328
346
|
setNextRoute(src, tgt, follow) {
|
|
329
|
-
return this.setNamedRoute(src, tgt,
|
|
347
|
+
return this.setNamedRoute(src, tgt, "next", follow);
|
|
330
348
|
}
|
|
331
349
|
|
|
332
350
|
/**
|
|
@@ -338,7 +356,7 @@ export default class Plan {
|
|
|
338
356
|
* @returns {Plan} Chain
|
|
339
357
|
*/
|
|
340
358
|
setPrevRoute(src, tgt, follow) {
|
|
341
|
-
return this.setNamedRoute(src, tgt,
|
|
359
|
+
return this.setNamedRoute(src, tgt, "prev", follow);
|
|
342
360
|
}
|
|
343
361
|
|
|
344
362
|
/**
|
|
@@ -349,8 +367,8 @@ export default class Plan {
|
|
|
349
367
|
* condition must be true, and so to reverse the direction we also need that
|
|
350
368
|
* same condition to be true.
|
|
351
369
|
*
|
|
352
|
-
* However, if the condition function uses the `source`/`target` property
|
|
353
|
-
*
|
|
370
|
+
* However, if the condition function uses the `source`/`target` property of
|
|
371
|
+
* the route in some way, then we must reverse these before passing to the
|
|
354
372
|
* condition on the "prev" route because `source` in the condition will almost
|
|
355
373
|
* certainly be referring to the source of the "next" route.
|
|
356
374
|
*
|
|
@@ -364,21 +382,24 @@ export default class Plan {
|
|
|
364
382
|
* @returns {Plan} Chain
|
|
365
383
|
*/
|
|
366
384
|
setRoute(src, tgt, followNext = undefined, followPrev = undefined) {
|
|
367
|
-
this.setNamedRoute(src, tgt,
|
|
385
|
+
this.setNamedRoute(src, tgt, "next", followNext);
|
|
368
386
|
|
|
369
387
|
let followPrevious = followPrev;
|
|
370
388
|
if (followPrevious === undefined) {
|
|
371
|
-
followPrevious =
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
389
|
+
followPrevious =
|
|
390
|
+
followNext === undefined
|
|
391
|
+
? undefined
|
|
392
|
+
: (r, c) => {
|
|
393
|
+
const invertedRoute = {
|
|
394
|
+
...r,
|
|
395
|
+
source: r.target,
|
|
396
|
+
target: r.source,
|
|
397
|
+
};
|
|
398
|
+
return followNext(invertedRoute, c);
|
|
399
|
+
};
|
|
379
400
|
}
|
|
380
401
|
|
|
381
|
-
this.setNamedRoute(tgt, src,
|
|
402
|
+
this.setNamedRoute(tgt, src, "prev", followPrevious);
|
|
382
403
|
|
|
383
404
|
return this;
|
|
384
405
|
}
|
|
@@ -386,20 +407,22 @@ export default class Plan {
|
|
|
386
407
|
/**
|
|
387
408
|
* Create a named route between two waypoints, and give that route a function
|
|
388
409
|
* that determine whether it should be followed during traversal operations.
|
|
389
|
-
* Note that the source waypoint must be in a successful validation state
|
|
390
|
-
*
|
|
410
|
+
* Note that the source waypoint must be in a successful validation state to
|
|
411
|
+
* be considered for traversal, regardless of what the custom function
|
|
391
412
|
* determines.
|
|
392
413
|
*
|
|
393
414
|
* You may also define routes that take the user to any generic URL within the
|
|
394
|
-
* same domain by using the `url://` protocol. These are considered
|
|
395
|
-
*
|
|
415
|
+
* same domain by using the `url://` protocol. These are considered "exit
|
|
416
|
+
* nodes".
|
|
396
417
|
*
|
|
397
|
-
*
|
|
418
|
+
* SetNamedRoute("my-waypoint", "url:///some/absolute/url");
|
|
398
419
|
*
|
|
399
420
|
* @param {string} src Source waypoint.
|
|
400
421
|
* @param {string} tgt Target waypoint.
|
|
401
|
-
* @param {string} name Name of the route (must be unique for this waypoint
|
|
402
|
-
*
|
|
422
|
+
* @param {string} name Name of the route (must be unique for this waypoint
|
|
423
|
+
* pairing).
|
|
424
|
+
* @param {PlanRouteCondition} follow Test function to determine if route can
|
|
425
|
+
* be followed.
|
|
403
426
|
* @returns {Plan} Chain
|
|
404
427
|
* @throws {Error} If attempting to create a "next" route from an exit node
|
|
405
428
|
*/
|
|
@@ -419,7 +442,12 @@ export default class Plan {
|
|
|
419
442
|
|
|
420
443
|
// Warn if we're overwriting an existing edge on the same name
|
|
421
444
|
if (self.dgraph.hasEdge(src, tgt, name)) {
|
|
422
|
-
log.warn(
|
|
445
|
+
log.warn(
|
|
446
|
+
"Setting a route that already exists (%s, %s, %s). Will be overridden",
|
|
447
|
+
src,
|
|
448
|
+
tgt,
|
|
449
|
+
name,
|
|
450
|
+
);
|
|
423
451
|
}
|
|
424
452
|
self.dgraph.setEdge(src, tgt, { conditionName }, name);
|
|
425
453
|
|
|
@@ -428,18 +456,18 @@ export default class Plan {
|
|
|
428
456
|
if (follow) {
|
|
429
457
|
if (!self.options.validateBeforeRouteCondition) {
|
|
430
458
|
followFunc = follow;
|
|
431
|
-
} else if (name ===
|
|
459
|
+
} else if (name === "next") {
|
|
432
460
|
// Retain the original function name of route condition
|
|
433
461
|
followFunc = {
|
|
434
|
-
[follow.name]: (r, c) =>
|
|
462
|
+
[follow.name]: (r, c) => defaultNextFollow(r, c) && follow(r, c),
|
|
435
463
|
}[follow.name];
|
|
436
464
|
} else {
|
|
437
465
|
// Retain the original function name of route condition
|
|
438
466
|
followFunc = {
|
|
439
|
-
[follow.name]: (r, c) =>
|
|
467
|
+
[follow.name]: (r, c) => defaultPrevFollow(r, c) && follow(r, c),
|
|
440
468
|
}[follow.name];
|
|
441
469
|
}
|
|
442
|
-
} else if (name ===
|
|
470
|
+
} else if (name === "next") {
|
|
443
471
|
followFunc = defaultNextFollow;
|
|
444
472
|
} else {
|
|
445
473
|
followFunc = defaultPrevFollow;
|
|
@@ -453,8 +481,8 @@ export default class Plan {
|
|
|
453
481
|
}
|
|
454
482
|
|
|
455
483
|
/**
|
|
456
|
-
* This is a convenience method for traversing all "next" routes, and
|
|
457
|
-
* the IDs of all waypoints visited along the way.
|
|
484
|
+
* This is a convenience method for traversing all "next" routes, and
|
|
485
|
+
* returning the IDs of all waypoints visited along the way.
|
|
458
486
|
*
|
|
459
487
|
* @param {JourneyContext} context Journey Context
|
|
460
488
|
* @param {PlanTraverseOptions} options Options
|
|
@@ -473,7 +501,7 @@ export default class Plan {
|
|
|
473
501
|
* @returns {PlanRoute[]} List of traversed waypoints
|
|
474
502
|
*/
|
|
475
503
|
traverseNextRoutes(context, options = {}) {
|
|
476
|
-
return this.traverseRoutes(context, { ...options, routeName:
|
|
504
|
+
return this.traverseRoutes(context, { ...options, routeName: "next" });
|
|
477
505
|
}
|
|
478
506
|
|
|
479
507
|
/**
|
|
@@ -485,15 +513,16 @@ export default class Plan {
|
|
|
485
513
|
* @returns {PlanRoute[]} List of traversed waypoints
|
|
486
514
|
*/
|
|
487
515
|
traversePrevRoutes(context, options = {}) {
|
|
488
|
-
return this.traverseRoutes(context, { ...options, routeName:
|
|
516
|
+
return this.traverseRoutes(context, { ...options, routeName: "prev" });
|
|
489
517
|
}
|
|
490
518
|
|
|
491
519
|
/**
|
|
492
520
|
* Traverse through the plan from a particular starting waypoint. This is a
|
|
493
521
|
* non-exhaustive Graph Exploration.
|
|
494
522
|
*
|
|
495
|
-
* The last route in the list will contain the source of the last waypoint
|
|
496
|
-
* can be reached, i.e. The waypoint that has no further satisfiable
|
|
523
|
+
* The last route in the list will contain the source of the last waypoint
|
|
524
|
+
* that can be reached, i.e. The waypoint that has no further satisfiable
|
|
525
|
+
* out-edges.
|
|
497
526
|
*
|
|
498
527
|
* If a cyclical set of routes are encountered, traversal will stop after
|
|
499
528
|
* reaching the first repeated waypoint.
|
|
@@ -505,7 +534,9 @@ export default class Plan {
|
|
|
505
534
|
*/
|
|
506
535
|
traverseRoutes(context, options = {}) {
|
|
507
536
|
if (!(context instanceof JourneyContext)) {
|
|
508
|
-
throw new TypeError(
|
|
537
|
+
throw new TypeError(
|
|
538
|
+
`Expected context to be an instance of JourneyContext, got ${typeof context}`,
|
|
539
|
+
);
|
|
509
540
|
}
|
|
510
541
|
|
|
511
542
|
const self = priv.get(this);
|
|
@@ -513,13 +544,15 @@ export default class Plan {
|
|
|
513
544
|
/** @type {PlanTraverseOptions} */
|
|
514
545
|
const {
|
|
515
546
|
startWaypoint = this.getWaypoints()[0],
|
|
516
|
-
stopCondition = () =>
|
|
547
|
+
stopCondition = () => false,
|
|
517
548
|
arbiter = self.options.arbiter,
|
|
518
549
|
routeName,
|
|
519
550
|
} = options;
|
|
520
551
|
|
|
521
552
|
if (!self.dgraph.hasNode(startWaypoint)) {
|
|
522
|
-
throw new ReferenceError(
|
|
553
|
+
throw new ReferenceError(
|
|
554
|
+
`Plan does not contain waypoint '${startWaypoint}'`,
|
|
555
|
+
);
|
|
523
556
|
}
|
|
524
557
|
|
|
525
558
|
validateRouteName(routeName);
|
|
@@ -537,7 +570,11 @@ export default class Plan {
|
|
|
537
570
|
/* eslint-disable-next-line security/detect-object-injection */
|
|
538
571
|
return self.follows[routeName][`${e.v}/${e.w}`](route, context);
|
|
539
572
|
} catch (ex) {
|
|
540
|
-
log.warn(
|
|
573
|
+
log.warn(
|
|
574
|
+
'Route follow function threw an exception, "%s" (%s)',
|
|
575
|
+
ex.message,
|
|
576
|
+
`${e.v} -> ${e.w}`,
|
|
577
|
+
);
|
|
541
578
|
return false;
|
|
542
579
|
}
|
|
543
580
|
});
|
|
@@ -545,10 +582,12 @@ export default class Plan {
|
|
|
545
582
|
// When there's more than one candidate route to take, we need help to choose
|
|
546
583
|
if (target.length > 1) {
|
|
547
584
|
const satisfied = target.map((t) => `${t.v} -> ${t.w}`);
|
|
548
|
-
log.debug(
|
|
585
|
+
log.debug(
|
|
586
|
+
`Multiple routes were satisfied for "${routeName}" from "${startWP}" (${satisfied.join(" / ")}). Deciding how to resolve ...`,
|
|
587
|
+
);
|
|
549
588
|
|
|
550
|
-
if (arbiter ===
|
|
551
|
-
log.debug(
|
|
589
|
+
if (arbiter === "auto") {
|
|
590
|
+
log.debug("Using automatic arbitration process");
|
|
552
591
|
const targetNames = target.map(({ w }) => w);
|
|
553
592
|
const forwardTraversal = this.traverseNextRoutes(context, {
|
|
554
593
|
stopCondition: ({ source }) => targetNames.includes(source),
|
|
@@ -556,7 +595,7 @@ export default class Plan {
|
|
|
556
595
|
const resolved = forwardTraversal.pop();
|
|
557
596
|
target = target.filter((t) => t.w === resolved.source);
|
|
558
597
|
} else if (arbiter instanceof Function) {
|
|
559
|
-
log.debug(
|
|
598
|
+
log.debug("Using custom arbitration process");
|
|
560
599
|
// Convert to routeObject and back to edge object so that only the
|
|
561
600
|
// routeObject is used in the public API
|
|
562
601
|
target = arbiter({
|
|
@@ -564,9 +603,13 @@ export default class Plan {
|
|
|
564
603
|
journeyContext: context,
|
|
565
604
|
travereOptions: options,
|
|
566
605
|
});
|
|
567
|
-
target = target.map((r) => ({
|
|
606
|
+
target = target.map((r) => ({
|
|
607
|
+
v: r.source,
|
|
608
|
+
w: r.target,
|
|
609
|
+
name: r.name,
|
|
610
|
+
}));
|
|
568
611
|
} else {
|
|
569
|
-
log.warn(
|
|
612
|
+
log.warn("Unable to arbitrate");
|
|
570
613
|
target = [];
|
|
571
614
|
}
|
|
572
615
|
}
|
|
@@ -593,15 +636,20 @@ export default class Plan {
|
|
|
593
636
|
|
|
594
637
|
return results;
|
|
595
638
|
}
|
|
596
|
-
log.debug(
|
|
639
|
+
log.debug(
|
|
640
|
+
"Encountered loop (%s). Stopping traversal.",
|
|
641
|
+
`${route.source} -> ${route.target}`,
|
|
642
|
+
);
|
|
597
643
|
}
|
|
598
644
|
|
|
599
|
-
return [
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
645
|
+
return [
|
|
646
|
+
makeRouteObject(self.dgraph, {
|
|
647
|
+
v: startWP,
|
|
648
|
+
w: null,
|
|
649
|
+
name: routeName,
|
|
650
|
+
label: {},
|
|
651
|
+
}),
|
|
652
|
+
];
|
|
605
653
|
};
|
|
606
654
|
|
|
607
655
|
return traverse(startWaypoint);
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import lodash from
|
|
1
|
+
import lodash from "lodash";
|
|
2
2
|
|
|
3
3
|
const { isPlainObject } = lodash; // CommonJS
|
|
4
4
|
|
|
5
5
|
const params = new WeakMap();
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
+
* @typedef {import("../casa").ValidateContext} ValidateContext
|
|
8
9
|
* @access private
|
|
9
|
-
* @typedef {import('../casa').ValidateContext} ValidateContext
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
+
* @typedef {import("../casa").ErrorMessageConfig} ErrorMessageConfig
|
|
13
14
|
* @access private
|
|
14
|
-
* @typedef {import('../casa').ErrorMessageConfig} ErrorMessageConfig
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
+
* @typedef {import("../casa").ErrorMessageConfigObject} ErrorMessageConfigObject
|
|
18
19
|
* @access private
|
|
19
|
-
* @typedef {import('../casa').ErrorMessageConfigObject} ErrorMessageConfigObject
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* @class
|
|
24
23
|
* @memberof module:@dwp/govuk-casa
|
|
24
|
+
* @class
|
|
25
25
|
*/
|
|
26
26
|
export default class ValidationError {
|
|
27
27
|
/**
|
|
@@ -30,24 +30,25 @@ export default class ValidationError {
|
|
|
30
30
|
* <br/><br/>
|
|
31
31
|
*
|
|
32
32
|
* In the case of `errorMsg` being a function, this will be called at runtime,
|
|
33
|
-
* at the point that errors are generated within the `validate()`,
|
|
34
|
-
*
|
|
35
|
-
* <br/><br/>
|
|
33
|
+
* at the point that errors are generated within the `validate()`, methods,
|
|
34
|
+
* and will be passed the `dataContext`. <br/><br/>
|
|
36
35
|
*
|
|
37
36
|
* `dataContext` is an object containing the same data passed to all
|
|
38
|
-
* validators' `validate()` methods. In the case of `errorMsg` being
|
|
39
|
-
*
|
|
37
|
+
* validators' `validate()` methods. In the case of `errorMsg` being a
|
|
38
|
+
* function, this data is passed to that function in order to help resolve to
|
|
40
39
|
* an error message config object.
|
|
41
40
|
*
|
|
42
41
|
* @param {object} args Arguments
|
|
43
|
-
* @param {ErrorMessageConfig} args.errorMsg Error message config to seed
|
|
44
|
-
*
|
|
42
|
+
* @param {ErrorMessageConfig} args.errorMsg Error message config to seed
|
|
43
|
+
* ValidationError
|
|
44
|
+
* @param {ValidateContext} [args.dataContext={}] Data for error msg function.
|
|
45
|
+
* Default is `{}`
|
|
45
46
|
* @returns {ValidationError} Error instance
|
|
46
47
|
* @throws {TypeError} If errorMsg is not in a valid type
|
|
47
48
|
*/
|
|
48
49
|
static make({ errorMsg, dataContext = {} }) {
|
|
49
50
|
// Convert strings to the most basic object primitive
|
|
50
|
-
if (typeof errorMsg ===
|
|
51
|
+
if (typeof errorMsg === "string") {
|
|
51
52
|
return new ValidationError({
|
|
52
53
|
summary: errorMsg,
|
|
53
54
|
inline: errorMsg,
|
|
@@ -63,7 +64,7 @@ export default class ValidationError {
|
|
|
63
64
|
|
|
64
65
|
// Use the user-defined function to generate an error primitive for the
|
|
65
66
|
// given context
|
|
66
|
-
if (typeof errorMsg ===
|
|
67
|
+
if (typeof errorMsg === "function") {
|
|
67
68
|
return new ValidationError(errorMsg.call(null, { ...dataContext }));
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -77,19 +78,22 @@ export default class ValidationError {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
// Unsupported
|
|
80
|
-
throw new TypeError(
|
|
81
|
+
throw new TypeError(
|
|
82
|
+
"errorMsg must be a string, Error, primitive object or function that generates a primitive object",
|
|
83
|
+
);
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
/**
|
|
84
87
|
* Create a ValidationError.
|
|
85
88
|
*
|
|
86
|
-
* @param {string|ErrorMessageConfigObject} errorParam Error configuration
|
|
89
|
+
* @param {string | ErrorMessageConfigObject} errorParam Error configuration
|
|
87
90
|
*/
|
|
88
91
|
constructor(errorParam = {}) {
|
|
89
|
-
if (!isPlainObject(errorParam) && typeof errorParam !==
|
|
90
|
-
throw new TypeError(
|
|
92
|
+
if (!isPlainObject(errorParam) && typeof errorParam !== "string") {
|
|
93
|
+
throw new TypeError("Constructor requires a string or object");
|
|
91
94
|
}
|
|
92
|
-
const error =
|
|
95
|
+
const error =
|
|
96
|
+
typeof errorParam === "string" ? { summary: errorParam } : errorParam;
|
|
93
97
|
|
|
94
98
|
// Store parameters for later use in applying contexts
|
|
95
99
|
const originals = {
|
|
@@ -133,7 +137,7 @@ export default class ValidationError {
|
|
|
133
137
|
const originals = params.get(this);
|
|
134
138
|
|
|
135
139
|
// Expand variables
|
|
136
|
-
if (typeof originals.variables ===
|
|
140
|
+
if (typeof originals.variables === "function") {
|
|
137
141
|
this.variables = originals.variables.call(this, context);
|
|
138
142
|
}
|
|
139
143
|
|
|
@@ -150,7 +154,7 @@ export default class ValidationError {
|
|
|
150
154
|
fieldHref += focusSuffix[0];
|
|
151
155
|
}
|
|
152
156
|
|
|
153
|
-
this.field = context.fieldName + (originals.fieldKeySuffix ||
|
|
157
|
+
this.field = context.fieldName + (originals.fieldKeySuffix || "");
|
|
154
158
|
this.fieldHref = fieldHref;
|
|
155
159
|
this.focusSuffix = focusSuffix || [];
|
|
156
160
|
}
|