diff --git a/components/breadcrumbs.js b/components/breadcrumbs.js index 23e17a77..17985d09 100644 --- a/components/breadcrumbs.js +++ b/components/breadcrumbs.js @@ -213,6 +213,7 @@ const Breadcrumbs = (props) => { simpleRouteRenderersByPath, simpleLinkTextPairRenderersByPath, simpleMyPortalRouteRenderersByPath, + newAppealRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath } = createMappedRouteRendererGroups({ @@ -242,6 +243,7 @@ const Breadcrumbs = (props) => { simpleRouteRenderersByPath, simpleLinkTextPairRenderersByPath, simpleMyPortalRouteRenderersByPath, + newAppealRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath }); @@ -306,22 +308,6 @@ const Breadcrumbs = (props) => { )} - {isPath("/newappeal/selectappeal") && ( - <> - {renderLinkCrumb( - myPortalHref, - t("newappeal:parent-page-title") - )} - {renderLinkCrumb( - myPortalHref, - t("newappeal:page-title") - )} -
  • - Select Appeal -
  • - - )} - {isPath("/myportal/case/id/[incident]") && ( <> {renderMyPortalCrumb()} diff --git a/lib/routing/breadcrumbRendererFactories.js b/lib/routing/breadcrumbRendererFactories.js index 21c3024b..ad9efc92 100644 --- a/lib/routing/breadcrumbRendererFactories.js +++ b/lib/routing/breadcrumbRendererFactories.js @@ -107,6 +107,21 @@ export const createSimpleMyPortalRouteRenderers = ({ ) }); +export const createNewAppealRouteRenderers = ({ + t, + myPortalHref, + renderLinkCrumb, + renderTextCrumb +}) => ({ + "/newappeal/selectappeal": () => ( + <> + {renderLinkCrumb(myPortalHref, t("newappeal:parent-page-title"))} + {renderLinkCrumb(myPortalHref, t("newappeal:page-title"))} + {renderTextCrumb("Select Appeal")} + + ) +}); + export const createCaseDetailRouteRenderers = ({ t, router, @@ -396,6 +411,13 @@ export const createMappedRouteRendererGroups = ({ renderMyPortalSectionCrumbs }); + const newAppealRouteRenderersByPath = createNewAppealRouteRenderers({ + t, + myPortalHref, + renderLinkCrumb, + renderTextCrumb + }); + const caseDetailRouteRenderersByPath = createCaseDetailRouteRenderers({ t, router, @@ -430,6 +452,7 @@ export const createMappedRouteRendererGroups = ({ simpleRouteRenderersByPath, simpleLinkTextPairRenderersByPath, simpleMyPortalRouteRenderersByPath, + newAppealRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath }; diff --git a/lib/routing/breadcrumbRouteMaps.js b/lib/routing/breadcrumbRouteMaps.js index 3d505eb1..c3cbde18 100644 --- a/lib/routing/breadcrumbRouteMaps.js +++ b/lib/routing/breadcrumbRouteMaps.js @@ -16,12 +16,14 @@ const buildBreadcrumbRendererMaps = ({ simpleRouteRenderersByPath, simpleLinkTextPairRenderersByPath, simpleMyPortalRouteRenderersByPath, + newAppealRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath }) => [ simpleRouteRenderersByPath, simpleLinkTextPairRenderersByPath, simpleMyPortalRouteRenderersByPath, + newAppealRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath ]; diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index bf772e1f..1a07eb6c 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -602,6 +602,42 @@ Follow-ups: - Next slice option A (larger): evaluate whether any remaining deterministic inline branches can be extracted without violating callback/state constraints. - Next slice option B (smaller): add a focused helper-level precedence test where earlier map contains falsy/non-function values and later maps contain valid renderers. +### CL-22541-T: breadcrumbs larger slice — new-appeal mapped group extraction (`/newappeal/selectappeal`) + +date: 2026-04-09 +author: Cline +scope: `lib/routing/{breadcrumbRendererFactories,breadcrumbRouteMaps}.js`, `components/breadcrumbs.js`, `tests/phase22/{breadcrumb-route-maps-helper,breadcrumbs-route-map-structure}.test.cjs` +type: change +rationale: Execute the next larger deterministic refactor slice by extracting callback-free new-appeal breadcrumb branching into a dedicated mapped renderer group and extending map-order guardrails. +impact: Refactor-only structural extraction plus precedence-test hardening; no intended route/auth/session/API/EN-CY/a11y behavior change. +status: completed + +Summary: + +- Added `createNewAppealRouteRenderers(...)` in `lib/routing/breadcrumbRendererFactories.js`. +- Mapped `/newappeal/selectappeal` into the new grouped renderer factory. +- Extended grouped composition return from `createMappedRouteRendererGroups(...)` with `newAppealRouteRenderersByPath`. +- Updated `lib/routing/breadcrumbRouteMaps.js` to include `newAppealRouteRenderersByPath` in explicit map precedence ordering. +- Updated `components/breadcrumbs.js` to consume/pass `newAppealRouteRenderersByPath` into map builder. +- Removed now-redundant inline `isPath("/newappeal/selectappeal")` branch from component JSX. +- Expanded tests: + - `tests/phase22/breadcrumbs-route-map-structure.test.cjs` + - assert new factory export and mapped route presence + - assert inline branch removal + - assert updated map-order invariant (simple -> link-text -> myportal -> new-appeal -> case detail -> detail/account) + - `tests/phase22/breadcrumb-route-maps-helper.test.cjs` + - assert six-map ordering contract and updated grouped precedence wiring. + +Validation: + +- `npx eslint lib/routing/breadcrumbRendererFactories.js lib/routing/breadcrumbRouteMaps.js components/breadcrumbs.js tests/phase22/breadcrumb-route-maps-helper.test.cjs tests/phase22/breadcrumbs-route-map-structure.test.cjs tests/phase22/index.test.cjs` -> pass. +- `node tests/phase22/index.test.cjs` -> pass (combined suite). + +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-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 60ab8661..23c40e38 100644 --- a/tests/phase22/breadcrumb-route-maps-helper.test.cjs +++ b/tests/phase22/breadcrumb-route-maps-helper.test.cjs @@ -57,6 +57,9 @@ test("buildBreadcrumbRendererMaps returns grouped maps in explicit precedence or const simpleMyPortalRouteRenderersByPath = { "/gamma": () => "gamma" }; + const newAppealRouteRenderersByPath = { + "/eta": () => "eta" + }; const caseDetailRouteRenderersByPath = { "/delta": () => "delta" }; @@ -68,12 +71,13 @@ test("buildBreadcrumbRendererMaps returns grouped maps in explicit precedence or simpleRouteRenderersByPath, simpleLinkTextPairRenderersByPath, simpleMyPortalRouteRenderersByPath, + newAppealRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath }); assert.ok(Array.isArray(result), "Expected grouped maps array"); - assert.strictEqual(result.length, 5, "Expected five grouped route maps"); + assert.strictEqual(result.length, 6, "Expected six grouped route maps"); assert.strictEqual( result[0], simpleRouteRenderersByPath, @@ -91,13 +95,18 @@ test("buildBreadcrumbRendererMaps returns grouped maps in explicit precedence or ); assert.strictEqual( result[3], - caseDetailRouteRenderersByPath, - "Expected case detail route renderers fourth" + newAppealRouteRenderersByPath, + "Expected new-appeal route renderers fourth" ); assert.strictEqual( result[4], + caseDetailRouteRenderersByPath, + "Expected case detail route renderers fifth" + ); + assert.strictEqual( + result[5], detailAndAccountRouteRenderersByPath, - "Expected detail and account route renderers fifth" + "Expected detail and account route renderers sixth" ); }); @@ -116,6 +125,7 @@ test("resolveMappedRouteRenderer returns renderer from first matching grouped ma "/known": secondRenderer }, simpleMyPortalRouteRenderersByPath: {}, + newAppealRouteRenderersByPath: {}, caseDetailRouteRenderersByPath: {}, detailAndAccountRouteRenderersByPath: {} }); @@ -138,6 +148,9 @@ test("resolveMappedRouteRenderer returns renderer from later grouped map when ea const maps = buildBreadcrumbRendererMaps({ simpleRouteRenderersByPath: {}, simpleLinkTextPairRenderersByPath: {}, + newAppealRouteRenderersByPath: { + "/newappeal/selectappeal": () => "newappeal" + }, simpleMyPortalRouteRenderersByPath: {}, caseDetailRouteRenderersByPath: {}, detailAndAccountRouteRenderersByPath: { @@ -164,6 +177,7 @@ test("resolveMappedRouteRenderer returns null for unmapped path", () => { }, simpleLinkTextPairRenderersByPath: {}, simpleMyPortalRouteRenderersByPath: {}, + newAppealRouteRenderersByPath: {}, caseDetailRouteRenderersByPath: {}, detailAndAccountRouteRenderersByPath: {} }); diff --git a/tests/phase22/breadcrumbs-route-map-structure.test.cjs b/tests/phase22/breadcrumbs-route-map-structure.test.cjs index 60cb5a27..5cf9d452 100644 --- a/tests/phase22/breadcrumbs-route-map-structure.test.cjs +++ b/tests/phase22/breadcrumbs-route-map-structure.test.cjs @@ -36,6 +36,12 @@ test("breadcrumbs/factory module includes expected mapped route definitions", as "Expected my-portal route renderer factory export" ); + assert.strictEqual( + source.includes("export const createNewAppealRouteRenderers ="), + true, + "Expected new-appeal route renderer factory export" + ); + assert.strictEqual( source.includes("export const createCaseDetailRouteRenderers ="), true, @@ -84,6 +90,12 @@ test("breadcrumbs/factory module includes expected mapped route definitions", as "Expected /myportal/addresssearchresults to be mapped in createSimpleMyPortalRouteRenderers" ); + assert.strictEqual( + source.includes('"/newappeal/selectappeal": () => ('), + true, + "Expected /newappeal/selectappeal to be mapped in createNewAppealRouteRenderers" + ); + assert.strictEqual( source.includes('"/case/[ticketnumber]": () =>'), true, @@ -173,7 +185,7 @@ test("breadcrumbs/component composes mapped routes via imported factories and sh ); assert.strictEqual( - /const\s+\{[\s\S]*simpleRouteRenderersByPath[\s\S]*detailAndAccountRouteRenderersByPath[\s\S]*\}\s*=\s*createMappedRouteRendererGroups\s*\(/.test( + /const\s+\{[\s\S]*simpleRouteRenderersByPath[\s\S]*newAppealRouteRenderersByPath[\s\S]*detailAndAccountRouteRenderersByPath[\s\S]*\}\s*=\s*createMappedRouteRendererGroups\s*\(/.test( source ), true, @@ -254,6 +266,12 @@ test("breadcrumbs/component composes mapped routes via imported factories and sh "Expected /case/id/[incident] explicit branch to be removed after mapping" ); + assert.strictEqual( + source.includes('isPath("/newappeal/selectappeal")'), + false, + "Expected /newappeal/selectappeal explicit branch to be removed after mapping" + ); + assert.strictEqual( source.includes('isPath("/myportal/case/[ticketnumber]")'), false, @@ -330,6 +348,9 @@ test("breadcrumbs/map builder preserves route map ordering", async () => { const simpleMyPortalOrderIndex = mapBuilderSlice.indexOf( "simpleMyPortalRouteRenderersByPath" ); + const newAppealOrderIndex = mapBuilderSlice.indexOf( + "newAppealRouteRenderersByPath" + ); const caseDetailOrderIndex = mapBuilderSlice.indexOf( "caseDetailRouteRenderersByPath" ); @@ -341,10 +362,11 @@ test("breadcrumbs/map builder preserves route map ordering", async () => { simpleRouteOrderIndex >= 0 && simpleLinkTextPairOrderIndex > simpleRouteOrderIndex && simpleMyPortalOrderIndex > simpleLinkTextPairOrderIndex && - caseDetailOrderIndex > simpleMyPortalOrderIndex && + newAppealOrderIndex > simpleMyPortalOrderIndex && + caseDetailOrderIndex > newAppealOrderIndex && detailAndAccountOrderIndex > caseDetailOrderIndex, true, - "Expected map builder call to preserve explicit route-map ordering (simple -> link-text -> myportal -> case detail -> detail/account)" + "Expected map builder call to preserve explicit route-map ordering (simple -> link-text -> myportal -> new-appeal -> case detail -> detail/account)" ); });