diff --git a/packages/core-api/src/routing/collectors.test.tsx b/packages/core-api/src/routing/collectors.test.tsx index 7622cdc70a..44d53856b4 100644 --- a/packages/core-api/src/routing/collectors.test.tsx +++ b/packages/core-api/src/routing/collectors.test.tsx @@ -15,7 +15,11 @@ */ import React, { PropsWithChildren } from 'react'; -import { routePathCollector, routeParentCollector } from './collectors'; +import { + routePathCollector, + routeParentCollector, + routeObjectCollector, +} from './collectors'; import { traverseElementTree, @@ -86,6 +90,16 @@ function sortedEntries(map: Map): [RouteRef, T][] { ); } +function routeObj(path: string, refs: RouteRef[], children: any[] = []) { + return { + path: path, + caseSensitive: false, + element: null, + routeRefs: new Set(refs), + children: children, + }; +} + describe('discovery', () => { it('should collect routes', () => { const list = [ @@ -124,12 +138,13 @@ describe('discovery', () => { ); - const { routes, routeParents } = traverseElementTree({ + const { routes, routeParents, routeObjects } = traverseElementTree({ root, discoverers: [childDiscoverer, routeElementDiscoverer], collectors: { routes: routePathCollector, routeParents: routeParentCollector, + routeObjects: routeObjectCollector, }, }); expect(sortedEntries(routes)).toEqual([ @@ -146,6 +161,17 @@ describe('discovery', () => { [ref4, undefined], [ref5, ref1], ]); + expect(routeObjects).toEqual([ + routeObj( + '/foo', + [ref1], + [ + routeObj('/bar/:id', [ref2], [routeObj('/baz', [ref3])]), + routeObj('/blop', [ref5]), + ], + ), + routeObj('/divsoup', [ref4]), + ]); }); it('should handle all react router Route patterns', () => { @@ -216,12 +242,13 @@ describe('discovery', () => { ); - const { routes, routeParents } = traverseElementTree({ + const { routes, routeParents, routeObjects } = traverseElementTree({ root, discoverers: [childDiscoverer, routeElementDiscoverer], collectors: { routes: routePathCollector, routeParents: routeParentCollector, + routeObjects: routeObjectCollector, }, }); expect(sortedEntries(routes)).toEqual([ @@ -238,6 +265,10 @@ describe('discovery', () => { [ref4, ref3], [ref5, ref3], ]); + expect(routeObjects).toEqual([ + routeObj('/foo', [ref1, ref2]), + routeObj('/bar', [ref3], [routeObj('/baz', [ref4, ref5])]), + ]); }); it('should use the route aggregator but stop when encountering explicit path', () => { @@ -258,12 +289,13 @@ describe('discovery', () => { ); - const { routes, routeParents } = traverseElementTree({ + const { routes, routeParents, routeObjects } = traverseElementTree({ root, discoverers: [childDiscoverer, routeElementDiscoverer], collectors: { routes: routePathCollector, routeParents: routeParentCollector, + routeObjects: routeObjectCollector, }, }); expect(sortedEntries(routes)).toEqual([ @@ -280,6 +312,19 @@ describe('discovery', () => { [ref4, ref3], [ref5, ref1], ]); + expect(routeObjects).toEqual([ + routeObj( + '/foo', + [ref1], + [ + routeObj( + '/bar', + [ref2, ref5], + [routeObj('/baz', [ref3], [routeObj('/blop', [ref4])])], + ), + ], + ), + ]); }); it('should stop gathering mount points after encountering explicit path', () => { diff --git a/packages/core-api/src/routing/collectors.tsx b/packages/core-api/src/routing/collectors.tsx index 1bf9558d06..37362cf8d4 100644 --- a/packages/core-api/src/routing/collectors.tsx +++ b/packages/core-api/src/routing/collectors.tsx @@ -111,9 +111,10 @@ export const routeParentCollector = createCollector( export const routeObjectCollector = createCollector( () => Array(), - (acc, node, parent, parentChildArr: BackstageRouteObject[] = acc) => { + (acc, node, parent, parentObj: BackstageRouteObject | undefined) => { + const parentChildren = parentObj?.children ?? acc; if (parent?.props.element === node) { - return parentChildArr; + return parentObj; } const path: string | undefined = node.props?.path; @@ -121,20 +122,40 @@ export const routeObjectCollector = createCollector( const routeRef = getMountPoint(node); if (routeRef) { - const children: BackstageRouteObject[] = []; - if (!path) { - throw new Error(`No path found for mount point ${routeRef}`); + if (path) { + const newObject: BackstageRouteObject = { + caseSensitive, + path, + element: null, + routeRefs: new Set([routeRef]), + children: [], + }; + parentChildren.push(newObject); + return newObject; } - parentChildArr.push({ + + parentObj?.routeRefs.add(routeRef); + } + + const isGatherer = getComponentData( + node, + 'core.gatherMountPoints', + ); + if (isGatherer) { + if (!path) { + throw new Error('Mount point gatherer must have a path'); + } + const newObject: BackstageRouteObject = { caseSensitive, path, element: null, - routeRef, - children, - }); - return children; + routeRefs: new Set(), + children: [], + }; + parentChildren.push(newObject); + return newObject; } - return parentChildArr; + return parentObj; }, ); diff --git a/packages/core-api/src/routing/hooks.tsx b/packages/core-api/src/routing/hooks.tsx index 55fde89de2..3a18d8b0af 100644 --- a/packages/core-api/src/routing/hooks.tsx +++ b/packages/core-api/src/routing/hooks.tsx @@ -60,8 +60,8 @@ class RouteResolver { currentRouteRef; currentRouteRef = this.routeParents.get(currentRouteRef) ) { - matchIndex = match.findIndex( - m => (m.route as BackstageRouteObject).routeRef === currentRouteRef, + matchIndex = match.findIndex(m => + (m.route as BackstageRouteObject).routeRefs.has(currentRouteRef!), ); if (matchIndex !== -1) { break; diff --git a/packages/core-api/src/routing/types.ts b/packages/core-api/src/routing/types.ts index e91ca7ea53..c6d79fd991 100644 --- a/packages/core-api/src/routing/types.ts +++ b/packages/core-api/src/routing/types.ts @@ -62,5 +62,5 @@ export interface BackstageRouteObject { children?: BackstageRouteObject[]; element: React.ReactNode; path: string; - routeRef: AnyRouteRef; + routeRefs: Set; }