Skip to content

Commit 3d931bd

Browse files
bpamiriclaude
andcommitted
fix: Adobe CF compatibility for mapperModernSpec tests
Replace arrow functions with function() syntax for Adobe CF 2025 compatibility. Inline $appKey() logic in $applyConstraintToLastRoute() and use intermediate variables in typed constraint tests to avoid Adobe CF 2018/2021/2023 runtime errors with deep chained access on application-scoped arrays. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ce445f0 commit 3d931bd

File tree

2 files changed

+87
-66
lines changed

2 files changed

+87
-66
lines changed

vendor/wheels/mapper/matching.cfc

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,16 @@ component {
528528
* Supports comma-delimited variable names to constrain multiple variables at once.
529529
*/
530530
public struct function $applyConstraintToLastRoute(required string variableName, required string pattern) {
531-
local.routeCount = ArrayLen(application[$appKey()].routes);
531+
// Cache the application key and routes array reference to avoid repeated
532+
// dynamic key resolution via $appKey(). On Adobe CF, chained expressions
533+
// like application[$appKey()].routes[i] can cause unexpected runtime errors
534+
// when accessing array elements from application-scoped structs.
535+
local.appKey = "wheels";
536+
if (StructKeyExists(application, "$wheels")) {
537+
local.appKey = "$wheels";
538+
}
539+
local.appRoutes = application[local.appKey].routes;
540+
local.routeCount = ArrayLen(local.appRoutes);
532541
if (local.routeCount == 0) {
533542
Throw(
534543
type = "Wheels.NoRouteToConstrain",
@@ -539,7 +548,7 @@ component {
539548
// Apply constraint to the last route (and its optional-segment variants).
540549
// When optional segments are used, $match adds multiple routes. We apply the
541550
// constraint to all routes that share the same name as the last one.
542-
local.lastRoute = application[$appKey()].routes[local.routeCount];
551+
local.lastRoute = local.appRoutes[local.routeCount];
543552
local.lastRouteName = StructKeyExists(local.lastRoute, "name") ? local.lastRoute.name : "";
544553

545554
local.variables = ListToArray(arguments.variableName);
@@ -548,7 +557,7 @@ component {
548557

549558
// Walk backward through routes to find all variants of the same named route.
550559
for (local.i = local.routeCount; local.i >= 1; local.i--) {
551-
local.route = application[$appKey()].routes[local.i];
560+
local.route = local.appRoutes[local.i];
552561
local.routeName = StructKeyExists(local.route, "name") ? local.route.name : "";
553562

554563
// Stop if we've gone past the related routes.
@@ -566,10 +575,12 @@ component {
566575
// Recompile the regex with the new constraint.
567576
local.route.regex = $patternToRegex(local.route.pattern, local.route.constraints);
568577

569-
// Explicit write-back: on Adobe CF, modifying a local copy of an
570-
// array element does not always propagate back to the array. Write
571-
// the modified struct back to guarantee cross-engine safety.
572-
application[$appKey()].routes[local.i] = local.route;
578+
// Explicit write-back: on Adobe CF, array elements from the
579+
// application scope are returned by value. Write the modified
580+
// struct back directly to the application-scoped array to
581+
// ensure changes persist (not via a local reference which may
582+
// be a copy on some engines).
583+
application[local.appKey].routes[local.i] = local.route;
573584
}
574585

575586
// If no name, only update the very last route.

vendor/wheels/tests_testbox/specs/mapperModernSpec.cfc

Lines changed: 69 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ component extends="wheels.Testbox" {
1717
// -----------------------------------------------------------------------
1818
// group() tests
1919
// -----------------------------------------------------------------------
20-
describe("Tests that group()", () => {
20+
describe("Tests that group()", function() {
2121

22-
beforeEach(() => {
22+
beforeEach(function() {
2323
$clearRoutes()
2424
})
2525

26-
it("groups routes with a path prefix", () => {
26+
it("groups routes with a path prefix", function() {
2727
$mapper()
2828
.$draw()
2929
.group(path="admin", callback=function(map) {
@@ -35,7 +35,7 @@ component extends="wheels.Testbox" {
3535
expect(application.wheels.routes[1].pattern).toBe("/admin/dashboard")
3636
})
3737

38-
it("groups routes with a name prefix", () => {
38+
it("groups routes with a name prefix", function() {
3939
$mapper()
4040
.$draw()
4141
.group(name="admin", path="admin", callback=function(map) {
@@ -47,7 +47,7 @@ component extends="wheels.Testbox" {
4747
expect(application.wheels.routes[1].name).toBe("adminDashboard")
4848
})
4949

50-
it("groups routes with shared constraints", () => {
50+
it("groups routes with shared constraints", function() {
5151
$mapper()
5252
.$draw()
5353
.group(path="users", constraints={key: "\d+"}, callback=function(map) {
@@ -61,7 +61,7 @@ component extends="wheels.Testbox" {
6161
expect(application.wheels.routes[1].constraints.key).toBe("\d+")
6262
})
6363

64-
it("does not add package to controller (unlike namespace)", () => {
64+
it("does not add package to controller (unlike namespace)", function() {
6565
$mapper()
6666
.$draw()
6767
.group(path="admin", callback=function(map) {
@@ -73,7 +73,7 @@ component extends="wheels.Testbox" {
7373
expect(application.wheels.routes[1].controller).toBe("users")
7474
})
7575

76-
it("supports nesting groups", () => {
76+
it("supports nesting groups", function() {
7777
$mapper()
7878
.$draw()
7979
.group(path="api", callback=function(map) {
@@ -86,7 +86,7 @@ component extends="wheels.Testbox" {
8686
expect(application.wheels.routes[1].pattern).toBe("/api/v1/users")
8787
})
8888

89-
it("works with manual end() when no callback", () => {
89+
it("works with manual end() when no callback", function() {
9090
$mapper()
9191
.$draw()
9292
.group(path="admin")
@@ -101,13 +101,13 @@ component extends="wheels.Testbox" {
101101
// -----------------------------------------------------------------------
102102
// api() and version() tests
103103
// -----------------------------------------------------------------------
104-
describe("Tests that api() and version()", () => {
104+
describe("Tests that api() and version()", function() {
105105

106-
beforeEach(() => {
106+
beforeEach(function() {
107107
$clearRoutes()
108108
})
109109

110-
it("creates API-scoped routes with default path", () => {
110+
it("creates API-scoped routes with default path", function() {
111111
$mapper()
112112
.$draw()
113113
.api(callback=function(api) {
@@ -118,7 +118,7 @@ component extends="wheels.Testbox" {
118118
expect(application.wheels.routes[1].pattern).toBe("/api/users")
119119
})
120120

121-
it("creates API-scoped routes with custom path", () => {
121+
it("creates API-scoped routes with custom path", function() {
122122
$mapper()
123123
.$draw()
124124
.api(path="services", callback=function(api) {
@@ -129,7 +129,7 @@ component extends="wheels.Testbox" {
129129
expect(application.wheels.routes[1].pattern).toBe("/services/status")
130130
})
131131

132-
it("creates versioned API routes", () => {
132+
it("creates versioned API routes", function() {
133133
$mapper()
134134
.$draw()
135135
.api(callback=function(api) {
@@ -147,7 +147,7 @@ component extends="wheels.Testbox" {
147147
expect(application.wheels.routes[2].pattern).toBe("/api/v2/users")
148148
})
149149

150-
it("generates correct named routes for versioned APIs", () => {
150+
it("generates correct named routes for versioned APIs", function() {
151151
$mapper()
152152
.$draw()
153153
.api(callback=function(api) {
@@ -161,7 +161,7 @@ component extends="wheels.Testbox" {
161161
expect(application.wheels.routes[1].name).toBe("apiV1Users")
162162
})
163163

164-
it("version() works with resources", () => {
164+
it("version() works with resources", function() {
165165
$mapper()
166166
.$draw()
167167
.api(callback=function(api) {
@@ -186,109 +186,119 @@ component extends="wheels.Testbox" {
186186
// -----------------------------------------------------------------------
187187
// Typed constraint helper tests
188188
// -----------------------------------------------------------------------
189-
describe("Tests that typed constraint helpers", () => {
189+
describe("Tests that typed constraint helpers", function() {
190190

191-
beforeEach(() => {
191+
beforeEach(function() {
192192
$clearRoutes()
193193
})
194194

195-
it("whereNumber constrains to digits", () => {
195+
it("whereNumber constrains to digits", function() {
196196
$mapper()
197197
.$draw()
198198
.get(name="user", pattern="users/[id]", to="users##show")
199199
.whereNumber("id")
200200
.end()
201201

202-
expect(application.wheels.routes[1].constraints.id).toBe("\d+")
202+
// Use intermediate variable for Adobe CF compatibility — deep chained
203+
// access on application-scoped arrays can fail on Adobe CF engines.
204+
local.route = application.wheels.routes[1]
205+
expect(local.route.constraints.id).toBe("\d+")
203206
// Should match digits
204-
expect("users/123").toMatch(application.wheels.routes[1].regex)
207+
expect("users/123").toMatch(local.route.regex)
205208
// Should NOT match alphabetic
206-
expect("users/abc").notToMatch(application.wheels.routes[1].regex)
209+
expect("users/abc").notToMatch(local.route.regex)
207210
})
208211

209-
it("whereAlpha constrains to letters", () => {
212+
it("whereAlpha constrains to letters", function() {
210213
$mapper()
211214
.$draw()
212215
.get(name="category", pattern="categories/[slug]", to="categories##show")
213216
.whereAlpha("slug")
214217
.end()
215218

216-
expect(application.wheels.routes[1].constraints.slug).toBe("[a-zA-Z]+")
217-
expect("categories/electronics").toMatch(application.wheels.routes[1].regex)
218-
expect("categories/123").notToMatch(application.wheels.routes[1].regex)
219+
local.route = application.wheels.routes[1]
220+
expect(local.route.constraints.slug).toBe("[a-zA-Z]+")
221+
expect("categories/electronics").toMatch(local.route.regex)
222+
expect("categories/123").notToMatch(local.route.regex)
219223
})
220224

221-
it("whereAlphaNumeric constrains to alphanumeric", () => {
225+
it("whereAlphaNumeric constrains to alphanumeric", function() {
222226
$mapper()
223227
.$draw()
224228
.get(name="product", pattern="products/[code]", to="products##show")
225229
.whereAlphaNumeric("code")
226230
.end()
227231

228-
expect(application.wheels.routes[1].constraints.code).toBe("[a-zA-Z0-9]+")
229-
expect("products/abc123").toMatch(application.wheels.routes[1].regex)
232+
local.route = application.wheels.routes[1]
233+
expect(local.route.constraints.code).toBe("[a-zA-Z0-9]+")
234+
expect("products/abc123").toMatch(local.route.regex)
230235
})
231236

232-
it("whereUuid constrains to UUID format", () => {
237+
it("whereUuid constrains to UUID format", function() {
233238
$mapper()
234239
.$draw()
235240
.get(name="item", pattern="items/[guid]", to="items##show")
236241
.whereUuid("guid")
237242
.end()
238243

239-
expect(application.wheels.routes[1].constraints.guid).toInclude("[0-9a-fA-F]")
240-
expect("items/550e8400-e29b-41d4-a716-446655440000").toMatch(application.wheels.routes[1].regex)
241-
expect("items/not-a-uuid").notToMatch(application.wheels.routes[1].regex)
244+
local.route = application.wheels.routes[1]
245+
expect(local.route.constraints.guid).toInclude("[0-9a-fA-F]")
246+
expect("items/550e8400-e29b-41d4-a716-446655440000").toMatch(local.route.regex)
247+
expect("items/not-a-uuid").notToMatch(local.route.regex)
242248
})
243249

244-
it("whereSlug constrains to URL-friendly slugs", () => {
250+
it("whereSlug constrains to URL-friendly slugs", function() {
245251
$mapper()
246252
.$draw()
247253
.get(name="post", pattern="posts/[slug]", to="posts##show")
248254
.whereSlug("slug")
249255
.end()
250256

251-
expect("posts/my-great-post").toMatch(application.wheels.routes[1].regex)
252-
expect("posts/hello").toMatch(application.wheels.routes[1].regex)
257+
local.route = application.wheels.routes[1]
258+
expect("posts/my-great-post").toMatch(local.route.regex)
259+
expect("posts/hello").toMatch(local.route.regex)
253260
})
254261

255-
it("whereIn constrains to a set of values", () => {
262+
it("whereIn constrains to a set of values", function() {
256263
$mapper()
257264
.$draw()
258265
.get(name="userByStatus", pattern="users/status/[status]", to="users##byStatus")
259266
.whereIn("status", "active,inactive,pending")
260267
.end()
261268

262-
expect("users/status/active").toMatch(application.wheels.routes[1].regex)
263-
expect("users/status/inactive").toMatch(application.wheels.routes[1].regex)
264-
expect("users/status/pending").toMatch(application.wheels.routes[1].regex)
265-
expect("users/status/deleted").notToMatch(application.wheels.routes[1].regex)
269+
local.route = application.wheels.routes[1]
270+
expect("users/status/active").toMatch(local.route.regex)
271+
expect("users/status/inactive").toMatch(local.route.regex)
272+
expect("users/status/pending").toMatch(local.route.regex)
273+
expect("users/status/deleted").notToMatch(local.route.regex)
266274
})
267275

268-
it("whereMatch applies a custom regex", () => {
276+
it("whereMatch applies a custom regex", function() {
269277
$mapper()
270278
.$draw()
271279
.get(name="dated", pattern="archive/[year]", to="archive##show")
272280
.whereMatch("year", "20\d{2}")
273281
.end()
274282

275-
expect("archive/2024").toMatch(application.wheels.routes[1].regex)
276-
expect("archive/2099").toMatch(application.wheels.routes[1].regex)
277-
expect("archive/1999").notToMatch(application.wheels.routes[1].regex)
283+
local.route = application.wheels.routes[1]
284+
expect("archive/2024").toMatch(local.route.regex)
285+
expect("archive/2099").toMatch(local.route.regex)
286+
expect("archive/1999").notToMatch(local.route.regex)
278287
})
279288

280-
it("supports comma-delimited variable names", () => {
289+
it("supports comma-delimited variable names", function() {
281290
$mapper()
282291
.$draw()
283292
.get(name="userPost", pattern="users/[userId]/posts/[postId]", to="posts##show")
284293
.whereNumber("userId,postId")
285294
.end()
286295

287-
expect(application.wheels.routes[1].constraints.userId).toBe("\d+")
288-
expect(application.wheels.routes[1].constraints.postId).toBe("\d+")
296+
local.route = application.wheels.routes[1]
297+
expect(local.route.constraints.userId).toBe("\d+")
298+
expect(local.route.constraints.postId).toBe("\d+")
289299
})
290300

291-
it("throws error when no routes exist", () => {
301+
it("throws error when no routes exist", function() {
292302
mapper = $mapper().$draw()
293303

294304
expect(function() {
@@ -300,13 +310,13 @@ component extends="wheels.Testbox" {
300310
// -----------------------------------------------------------------------
301311
// health() tests
302312
// -----------------------------------------------------------------------
303-
describe("Tests that health()", () => {
313+
describe("Tests that health()", function() {
304314

305-
beforeEach(() => {
315+
beforeEach(function() {
306316
$clearRoutes()
307317
})
308318

309-
it("creates a health check route with defaults", () => {
319+
it("creates a health check route with defaults", function() {
310320
$mapper()
311321
.$draw()
312322
.health()
@@ -317,7 +327,7 @@ component extends="wheels.Testbox" {
317327
expect(application.wheels.routes[1].name).toBe("health")
318328
})
319329

320-
it("creates a health check route with custom handler", () => {
330+
it("creates a health check route with custom handler", function() {
321331
$mapper()
322332
.$draw()
323333
.health(to="monitoring##check")
@@ -327,7 +337,7 @@ component extends="wheels.Testbox" {
327337
expect(application.wheels.routes[1].action).toBe("check")
328338
})
329339

330-
it("creates a health check route with custom path", () => {
340+
it("creates a health check route with custom path", function() {
331341
$mapper()
332342
.$draw()
333343
.health(path="status")
@@ -340,13 +350,13 @@ component extends="wheels.Testbox" {
340350
// -----------------------------------------------------------------------
341351
// Performance index tests
342352
// -----------------------------------------------------------------------
343-
describe("Tests that performance indexes", () => {
353+
describe("Tests that performance indexes", function() {
344354

345-
beforeEach(() => {
355+
beforeEach(function() {
346356
$clearRoutes()
347357
})
348358

349-
it("indexes static routes for O(1) lookup", () => {
359+
it("indexes static routes for O(1) lookup", function() {
350360
$mapper()
351361
.$draw()
352362
.get(name="login", pattern="login", to="sessions##new")
@@ -358,7 +368,7 @@ component extends="wheels.Testbox" {
358368
expect(application.wheels.staticRoutes).toHaveKey("GET:/about")
359369
})
360370

361-
it("does not index dynamic routes as static", () => {
371+
it("does not index dynamic routes as static", function() {
362372
$mapper()
363373
.$draw()
364374
.get(name="user", pattern="users/[id]", to="users##show")
@@ -372,7 +382,7 @@ component extends="wheels.Testbox" {
372382
}
373383
})
374384

375-
it("marks routes with isStatic flag", () => {
385+
it("marks routes with isStatic flag", function() {
376386
$mapper()
377387
.$draw()
378388
.get(name="about", pattern="about", to="pages##about")
@@ -385,7 +395,7 @@ component extends="wheels.Testbox" {
385395
expect(application.wheels.routes[2].isStatic).toBeFalse()
386396
})
387397

388-
it("stores pre-compiled regex string on routes", () => {
398+
it("stores pre-compiled regex string on routes", function() {
389399
$mapper()
390400
.$draw()
391401
.get(name="test", pattern="test/[id]", to="test##show")

0 commit comments

Comments
 (0)