From 0cd78e6f4639d16ee704574d26101208eb06654e Mon Sep 17 00:00:00 2001 From: robbond Date: Thu, 9 Apr 2026 14:45:12 +0100 Subject: [PATCH] 22541 harden breadcrumb mapped route resolver against invalid entries --- lib/routing/breadcrumbRouteMaps.js | 10 +++++-- memory-bank/change-log.md | 30 +++++++++++++++++++ .../breadcrumb-route-maps-helper.test.cjs | 28 +++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/lib/routing/breadcrumbRouteMaps.js b/lib/routing/breadcrumbRouteMaps.js index c3cbde18..ef46b5f5 100644 --- a/lib/routing/breadcrumbRouteMaps.js +++ b/lib/routing/breadcrumbRouteMaps.js @@ -1,5 +1,11 @@ -const getMappedRendererByPath = (path, routeRendererMap) => - routeRendererMap[path] || null; +const getMappedRendererByPath = (path, routeRendererMap) => { + if (!routeRendererMap || typeof routeRendererMap !== "object") { + return null; + } + + const routeRenderer = routeRendererMap[path]; + return typeof routeRenderer === "function" ? routeRenderer : null; +}; const resolveMappedRouteRenderer = (path, routeRendererMaps = []) => { for (const routeRendererMap of routeRendererMaps) { diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index 1a07eb6c..89b9bfc6 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -638,6 +638,36 @@ Follow-ups: - Next larger slice candidate: evaluate extraction viability for `/case` branch only if callback/`router.back()` behavior can remain explicit and parity-safe. - Next smaller slice candidate: add helper-level test coverage for unexpected non-function map values and explicit skip behavior (defensive precedence guard). +### CL-22541-U: breadcrumbs smaller slice — defensive mapped-route resolver hardening + +date: 2026-04-09 +author: Cline +scope: `lib/routing/breadcrumbRouteMaps.js`, `tests/phase22/breadcrumb-route-maps-helper.test.cjs` +type: change +rationale: Execute the queued smaller slice by hardening mapped-route lookup against malformed map entries and non-function values while keeping precedence behavior unchanged. +impact: Refactor-hardening only; no intended breadcrumb route behavior change for valid mapped renderers. +status: completed + +Summary: + +- Updated `getMappedRendererByPath(...)` to safely handle invalid route-map entries: + - returns `null` when map is missing/non-object + - returns `null` when mapped value is not a function +- Preserved existing resolver precedence semantics for valid function renderers. +- Added focused helper test asserting resolver skips: + - invalid map entries (`null`/`undefined`) + - non-function mapped values + and still resolves the first later valid renderer. + +Validation: + +- `npx eslint lib/routing/breadcrumbRouteMaps.js tests/phase22/breadcrumb-route-maps-helper.test.cjs tests/phase22/index.test.cjs` -> pass. +- `node tests/phase22/index.test.cjs` -> pass (combined suite; breadcrumb-route-maps-helper 5/5). + +Follow-ups: + +- Next larger slice: extract remaining callback-bearing deterministic branches into mapped route groups with callback injection, then update structure guards accordingly. + ### CL-00X: 22500 `components/elements/index.js` Phase 1 helper extraction date: 2026-04-07 diff --git a/tests/phase22/breadcrumb-route-maps-helper.test.cjs b/tests/phase22/breadcrumb-route-maps-helper.test.cjs index 23c40e38..8f04ee21 100644 --- a/tests/phase22/breadcrumb-route-maps-helper.test.cjs +++ b/tests/phase22/breadcrumb-route-maps-helper.test.cjs @@ -191,6 +191,34 @@ test("resolveMappedRouteRenderer returns null for unmapped path", () => { ); }); +test("resolveMappedRouteRenderer skips invalid map entries and non-function renderer values", () => { + const { buildBreadcrumbRendererMaps, resolveMappedRouteRenderer } = + loadHelpers(); + + const validRenderer = () => "valid"; + + const maps = buildBreadcrumbRendererMaps({ + simpleRouteRenderersByPath: { + "/known": "not-a-function" + }, + simpleLinkTextPairRenderersByPath: null, + simpleMyPortalRouteRenderersByPath: { + "/known": validRenderer + }, + newAppealRouteRenderersByPath: undefined, + caseDetailRouteRenderersByPath: {}, + detailAndAccountRouteRenderersByPath: {} + }); + + const result = resolveMappedRouteRenderer("/known", maps); + + assert.strictEqual( + result, + validRenderer, + "Expected resolver to skip invalid/non-function entries and return first valid renderer" + ); +}); + const run = async () => { let passed = 0;