From d3f4dda8af2f067bca112539b8af1e922c466495 Mon Sep 17 00:00:00 2001 From: robbond Date: Thu, 9 Apr 2026 15:16:01 +0100 Subject: [PATCH] refactor(breadcrumbs): extract representation route-state renderer group --- components/breadcrumbs.js | 47 ++++++----------- lib/routing/breadcrumbRendererFactories.js | 50 +++++++++++++++++++ lib/routing/breadcrumbRouteMaps.js | 2 + memory-bank/change-log.md | 49 ++++++++++++++++++ .../breadcrumb-route-maps-helper.test.cjs | 22 ++++++-- .../breadcrumbs-route-map-structure.test.cjs | 28 +++++++++-- 6 files changed, 160 insertions(+), 38 deletions(-) diff --git a/components/breadcrumbs.js b/components/breadcrumbs.js index 01c045be..90f078c5 100644 --- a/components/breadcrumbs.js +++ b/components/breadcrumbs.js @@ -216,6 +216,7 @@ const Breadcrumbs = (props) => { newAppealRouteRenderersByPath, callbackRouteRenderersByPath, stepBackRouteRenderersByPath, + representationRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath } = createMappedRouteRendererGroups({ @@ -242,6 +243,20 @@ const Breadcrumbs = (props) => { onStepBackWithInlineClass: () => { setCurrentSection(appealType.currentSection - 1); }, + currentView, + hasRepDetails, + repDetails, + showQuestionnaireSection, + onRepresentationSubmitBackFromConfirmation: () => { + setRepresentationSubmit(qcount); + }, + onRepresentationSubmitBackToDetails: () => { + setRepresentationSubmit(false); + }, + onRepresentationQuestionnaireBack: () => { + setShowQuestionnaireSection(showQuestionnaireSection - 1); + setRepresentationSubmit(false); + }, renderAnchorCrumb, renderLinkCrumb, renderTextCrumb, @@ -259,6 +274,7 @@ const Breadcrumbs = (props) => { newAppealRouteRenderersByPath, callbackRouteRenderersByPath, stepBackRouteRenderersByPath, + representationRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath }); @@ -284,37 +300,6 @@ const Breadcrumbs = (props) => { {isPath("/newappeal") && <>} - {isPath("/myportal/representation") && ( - <> - {currentView.representationSubmit === true && - currentView.representationSubmitConfirmation !== - true && - renderBackCrumb(() => { - setRepresentationSubmit(qcount); - })} - - {currentView.representationSubmit !== true && - hasRepDetails && - repDetails?.representationType !== - "Questionnaire" && - renderBackCrumb(() => { - setRepresentationSubmit(false); - })} - - {currentView.representationSubmit !== true && - hasRepDetails && - repDetails?.representationType === - "Questionnaire" && - showQuestionnaireSection > 1 && - renderBackCrumb(() => { - setShowQuestionnaireSection( - showQuestionnaireSection - 1 - ); - setRepresentationSubmit(false); - })} - - )} - {staticTextCrumbLabel && renderTextCrumb(staticTextCrumbLabel)} diff --git a/lib/routing/breadcrumbRendererFactories.js b/lib/routing/breadcrumbRendererFactories.js index ae8d6c40..122de2ac 100644 --- a/lib/routing/breadcrumbRendererFactories.js +++ b/lib/routing/breadcrumbRendererFactories.js @@ -203,6 +203,36 @@ export const createStepBackRouteRenderers = ({ } }); +export const createRepresentationRouteRenderers = ({ + currentView, + hasRepDetails, + repDetails, + showQuestionnaireSection, + onRepresentationSubmitBackFromConfirmation, + onRepresentationSubmitBackToDetails, + onRepresentationQuestionnaireBack, + renderBackCrumb +}) => ({ + "/myportal/representation": () => ( + <> + {currentView.representationSubmit === true && + currentView.representationSubmitConfirmation !== true && + renderBackCrumb(onRepresentationSubmitBackFromConfirmation)} + + {currentView.representationSubmit !== true && + hasRepDetails && + repDetails?.representationType !== "Questionnaire" && + renderBackCrumb(onRepresentationSubmitBackToDetails)} + + {currentView.representationSubmit !== true && + hasRepDetails && + repDetails?.representationType === "Questionnaire" && + showQuestionnaireSection > 1 && + renderBackCrumb(onRepresentationQuestionnaireBack)} + + ) +}); + export const createCaseDetailRouteRenderers = ({ t, router, @@ -457,6 +487,13 @@ export const createMappedRouteRendererGroups = ({ currentSection, onStepBack, onStepBackWithInlineClass, + currentView, + hasRepDetails, + repDetails, + showQuestionnaireSection, + onRepresentationSubmitBackFromConfirmation, + onRepresentationSubmitBackToDetails, + onRepresentationQuestionnaireBack, renderAnchorCrumb, renderLinkCrumb, renderTextCrumb, @@ -525,6 +562,18 @@ export const createMappedRouteRendererGroups = ({ renderBackCrumb }); + const representationRouteRenderersByPath = + createRepresentationRouteRenderers({ + currentView, + hasRepDetails, + repDetails, + showQuestionnaireSection, + onRepresentationSubmitBackFromConfirmation, + onRepresentationSubmitBackToDetails, + onRepresentationQuestionnaireBack, + renderBackCrumb + }); + const caseDetailRouteRenderersByPath = createCaseDetailRouteRenderers({ t, router, @@ -562,6 +611,7 @@ export const createMappedRouteRendererGroups = ({ newAppealRouteRenderersByPath, callbackRouteRenderersByPath, stepBackRouteRenderersByPath, + representationRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath }; diff --git a/lib/routing/breadcrumbRouteMaps.js b/lib/routing/breadcrumbRouteMaps.js index 5666de00..81c11364 100644 --- a/lib/routing/breadcrumbRouteMaps.js +++ b/lib/routing/breadcrumbRouteMaps.js @@ -25,6 +25,7 @@ const buildBreadcrumbRendererMaps = ({ newAppealRouteRenderersByPath, callbackRouteRenderersByPath, stepBackRouteRenderersByPath, + representationRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath }) => [ @@ -34,6 +35,7 @@ const buildBreadcrumbRendererMaps = ({ newAppealRouteRenderersByPath, callbackRouteRenderersByPath, stepBackRouteRenderersByPath, + representationRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath ]; diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index f303f84e..8d7a2f8a 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -784,6 +784,55 @@ Follow-ups: - Next larger slice candidate remains `/myportal/representation` callback/state branch extraction only if side-effects can be injected/parity-preserved cleanly. +### CL-22541-Y: breadcrumbs larger slice — representation route-group extraction (`/myportal/representation`) + +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 queued larger follow-up slice by extracting `/myportal/representation` callback/state breadcrumb branches into a dedicated mapped route group with explicit callback injection while preserving questionnaire/submit side-effects. +impact: Structural refactor with preserved representation back-link state behavior; no intended route/auth/session/API/EN-CY/a11y behavior change. +status: completed + +Summary: + +- Added `createRepresentationRouteRenderers(...)` in `lib/routing/breadcrumbRendererFactories.js` mapping: + - `/myportal/representation` +- Preserved existing representation-state semantics in mapped handler: + - submit/back from confirmation (`setRepresentationSubmit(qcount)`) + - non-questionnaire back path (`setRepresentationSubmit(false)`) + - questionnaire step-back path (`setShowQuestionnaireSection(showQuestionnaireSection - 1)` + `setRepresentationSubmit(false)`) +- Introduced explicit injected state/callback dependencies into grouped composition: + - `currentView`, `hasRepDetails`, `repDetails`, `showQuestionnaireSection` + - `onRepresentationSubmitBackFromConfirmation` + - `onRepresentationSubmitBackToDetails` + - `onRepresentationQuestionnaireBack` +- Extended `buildBreadcrumbRendererMaps(...)` in `lib/routing/breadcrumbRouteMaps.js` with `representationRouteRenderersByPath` and updated precedence order: + - simple -> link-text -> myportal -> new-appeal -> callback -> step-back -> representation -> case detail -> detail/account. +- Updated `components/breadcrumbs.js`: + - pass representation state/callback injections into `createMappedRouteRendererGroups(...)` + - include `representationRouteRenderersByPath` in map builder call + - remove inline `isPath("/myportal/representation")` branch block. +- Expanded tests: + - `tests/phase22/breadcrumb-route-maps-helper.test.cjs` + - updated map-count/order assertions to include representation group (9 total) + - updated map build fixtures to include `representationRouteRenderersByPath` + - `tests/phase22/breadcrumbs-route-map-structure.test.cjs` + - assert representation factory export + mapped route presence + - assert grouped destructuring includes representation map + - assert inline representation branch removal + - assert updated map-order invariant includes representation group. + +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 smaller slice candidate: helper-level assertion for missing/undefined representation dependency values yielding no renderer output regressions. +- Next larger slice candidate: evaluate whether remaining `/newappeal` edge branch can be absorbed into mapped grouping without reducing readability or explicitness. + ### 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 30c627bf..864840b7 100644 --- a/tests/phase22/breadcrumb-route-maps-helper.test.cjs +++ b/tests/phase22/breadcrumb-route-maps-helper.test.cjs @@ -66,6 +66,9 @@ test("buildBreadcrumbRendererMaps returns grouped maps in explicit precedence or const stepBackRouteRenderersByPath = { "/iota": () => "iota" }; + const representationRouteRenderersByPath = { + "/kappa": () => "kappa" + }; const caseDetailRouteRenderersByPath = { "/delta": () => "delta" }; @@ -80,12 +83,13 @@ test("buildBreadcrumbRendererMaps returns grouped maps in explicit precedence or newAppealRouteRenderersByPath, callbackRouteRenderersByPath, stepBackRouteRenderersByPath, + representationRouteRenderersByPath, caseDetailRouteRenderersByPath, detailAndAccountRouteRenderersByPath }); assert.ok(Array.isArray(result), "Expected grouped maps array"); - assert.strictEqual(result.length, 8, "Expected eight grouped route maps"); + assert.strictEqual(result.length, 9, "Expected nine grouped route maps"); assert.strictEqual( result[0], simpleRouteRenderersByPath, @@ -118,13 +122,18 @@ test("buildBreadcrumbRendererMaps returns grouped maps in explicit precedence or ); assert.strictEqual( result[6], - caseDetailRouteRenderersByPath, - "Expected case detail route renderers seventh" + representationRouteRenderersByPath, + "Expected representation route renderers seventh" ); assert.strictEqual( result[7], + caseDetailRouteRenderersByPath, + "Expected case detail route renderers eighth" + ); + assert.strictEqual( + result[8], detailAndAccountRouteRenderersByPath, - "Expected detail and account route renderers eighth" + "Expected detail and account route renderers ninth" ); }); @@ -146,6 +155,7 @@ test("resolveMappedRouteRenderer returns renderer from first matching grouped ma newAppealRouteRenderersByPath: {}, callbackRouteRenderersByPath: {}, stepBackRouteRenderersByPath: {}, + representationRouteRenderersByPath: {}, caseDetailRouteRenderersByPath: {}, detailAndAccountRouteRenderersByPath: {} }); @@ -175,6 +185,7 @@ test("resolveMappedRouteRenderer returns renderer from later grouped map when ea caseDetailRouteRenderersByPath: {}, callbackRouteRenderersByPath: {}, stepBackRouteRenderersByPath: {}, + representationRouteRenderersByPath: {}, detailAndAccountRouteRenderersByPath: { "/account/personaldetails": detailRenderer } @@ -202,6 +213,7 @@ test("resolveMappedRouteRenderer returns null for unmapped path", () => { newAppealRouteRenderersByPath: {}, callbackRouteRenderersByPath: {}, stepBackRouteRenderersByPath: {}, + representationRouteRenderersByPath: {}, caseDetailRouteRenderersByPath: {}, detailAndAccountRouteRenderersByPath: {} }); @@ -230,6 +242,7 @@ test("resolveMappedRouteRenderer returns null for empty, whitespace, and non-exa newAppealRouteRenderersByPath: {}, callbackRouteRenderersByPath: {}, stepBackRouteRenderersByPath: {}, + representationRouteRenderersByPath: {}, caseDetailRouteRenderersByPath: {}, detailAndAccountRouteRenderersByPath: {} }); @@ -282,6 +295,7 @@ test("resolveMappedRouteRenderer skips invalid map entries and non-function rend newAppealRouteRenderersByPath: undefined, callbackRouteRenderersByPath: {}, stepBackRouteRenderersByPath: {}, + representationRouteRenderersByPath: {}, caseDetailRouteRenderersByPath: {}, detailAndAccountRouteRenderersByPath: {} }); diff --git a/tests/phase22/breadcrumbs-route-map-structure.test.cjs b/tests/phase22/breadcrumbs-route-map-structure.test.cjs index 429a01e4..6066c10c 100644 --- a/tests/phase22/breadcrumbs-route-map-structure.test.cjs +++ b/tests/phase22/breadcrumbs-route-map-structure.test.cjs @@ -54,6 +54,12 @@ test("breadcrumbs/factory module includes expected mapped route definitions", as "Expected step-back route renderer factory export" ); + assert.strictEqual( + source.includes("export const createRepresentationRouteRenderers ="), + true, + "Expected representation route renderer factory export" + ); + assert.strictEqual( source.includes("export const createCaseDetailRouteRenderers ="), true, @@ -132,6 +138,12 @@ test("breadcrumbs/factory module includes expected mapped route definitions", as "Expected /myportal/[appealtypes] to be mapped in createStepBackRouteRenderers" ); + assert.strictEqual( + source.includes('"/myportal/representation": () => ('), + true, + "Expected /myportal/representation to be mapped in createRepresentationRouteRenderers" + ); + assert.strictEqual( source.includes('"/case/[ticketnumber]": () =>'), true, @@ -221,7 +233,7 @@ test("breadcrumbs/component composes mapped routes via imported factories and sh ); assert.strictEqual( - /const\s+\{[\s\S]*simpleRouteRenderersByPath[\s\S]*newAppealRouteRenderersByPath[\s\S]*callbackRouteRenderersByPath[\s\S]*stepBackRouteRenderersByPath[\s\S]*detailAndAccountRouteRenderersByPath[\s\S]*\}\s*=\s*createMappedRouteRendererGroups\s*\(/.test( + /const\s+\{[\s\S]*simpleRouteRenderersByPath[\s\S]*newAppealRouteRenderersByPath[\s\S]*callbackRouteRenderersByPath[\s\S]*stepBackRouteRenderersByPath[\s\S]*representationRouteRenderersByPath[\s\S]*detailAndAccountRouteRenderersByPath[\s\S]*\}\s*=\s*createMappedRouteRendererGroups\s*\(/.test( source ), true, @@ -320,6 +332,12 @@ test("breadcrumbs/component composes mapped routes via imported factories and sh "Expected /myportal/[appealtypes] explicit branch to be removed after step-back mapping" ); + assert.strictEqual( + source.includes('isPath("/myportal/representation")'), + false, + "Expected /myportal/representation explicit branch to be removed after representation mapping" + ); + assert.strictEqual( source.includes('isPath("/myportal/case/[ticketnumber]")'), false, @@ -405,6 +423,9 @@ test("breadcrumbs/map builder preserves route map ordering", async () => { const stepBackOrderIndex = mapBuilderSlice.indexOf( "stepBackRouteRenderersByPath" ); + const representationOrderIndex = mapBuilderSlice.indexOf( + "representationRouteRenderersByPath" + ); const caseDetailOrderIndex = mapBuilderSlice.indexOf( "caseDetailRouteRenderersByPath" ); @@ -419,10 +440,11 @@ test("breadcrumbs/map builder preserves route map ordering", async () => { newAppealOrderIndex > simpleMyPortalOrderIndex && callbackOrderIndex > newAppealOrderIndex && stepBackOrderIndex > callbackOrderIndex && - caseDetailOrderIndex > stepBackOrderIndex && + representationOrderIndex > stepBackOrderIndex && + caseDetailOrderIndex > representationOrderIndex && detailAndAccountOrderIndex > caseDetailOrderIndex, true, - "Expected map builder call to preserve explicit route-map ordering (simple -> link-text -> myportal -> new-appeal -> callback -> step-back -> case detail -> detail/account)" + "Expected map builder call to preserve explicit route-map ordering (simple -> link-text -> myportal -> new-appeal -> callback -> step-back -> representation -> case detail -> detail/account)" ); });