frontend-app-api: finish route info collection implementation and tests

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Co-authored-by: Vincenzo Scamporlino <vincenzos@spotify.com>
Co-authored-by: Camila Belo <camilaibs@gmail.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-10-03 17:39:45 +02:00
parent 7aad08e564
commit e92ad1c067
2 changed files with 233 additions and 209 deletions
@@ -233,53 +233,53 @@ describe('discovery', () => {
]);
});
// it('should handle absolute route paths', () => {
// const info = routeInfoFromExtensions([
// createTestExtension({
// id: 'page1',
// path: '/foo',
// routeRef: ref1,
// }),
// createTestExtension({
// id: 'page2',
// at: 'page1/children',
// path: '/bar/:id',
// routeRef: ref2,
// }),
// createTestExtension({
// id: 'page3',
// path: '/baz',
// routeRef: ref3,
// }),
// createTestExtension({
// id: 'page4',
// at: 'page3/children',
// path: '/divsoup',
// routeRef: ref4,
// }),
// createTestExtension({
// id: 'page5',
// at: 'page3/children',
// path: '/blop',
// routeRef: ref5,
// }),
// ]);
it('should strip leading slashes in route paths', () => {
const info = routeInfoFromExtensions([
createTestExtension({
id: 'page1',
path: '/foo',
routeRef: ref1,
}),
createTestExtension({
id: 'page2',
at: 'page1/children',
path: '/bar/:id',
routeRef: ref2,
}),
createTestExtension({
id: 'page3',
path: '/baz',
routeRef: ref3,
}),
createTestExtension({
id: 'page4',
at: 'page3/children',
path: '/divsoup',
routeRef: ref4,
}),
createTestExtension({
id: 'page5',
at: 'page3/children',
path: '/blop',
routeRef: ref5,
}),
]);
// expect(sortedEntries(info.routePaths)).toEqual([
// [ref1, 'foo'],
// [ref2, 'bar/:id'],
// [ref3, 'baz'],
// [ref4, 'divsoup'],
// [ref5, 'blop'],
// ]);
// expect(sortedEntries(info.routeParents)).toEqual([
// [ref1, undefined],
// [ref2, ref1],
// [ref3, undefined],
// [ref4, ref3],
// [ref5, ref3],
// ]);
// });
expect(sortedEntries(info.routePaths)).toEqual([
[ref1, 'foo'],
[ref2, 'bar/:id'],
[ref3, 'baz'],
[ref4, 'divsoup'],
[ref5, 'blop'],
]);
expect(sortedEntries(info.routeParents)).toEqual([
[ref1, undefined],
[ref2, ref1],
[ref3, undefined],
[ref4, ref3],
[ref5, ref3],
]);
});
it('should use the route aggregator key to bind child routes to the same path', () => {
const info = routeInfoFromExtensions([
@@ -338,7 +338,7 @@ describe('discovery', () => {
[ref2, undefined],
[ref3, undefined],
[ref4, ref3],
[ref5, ref4],
[ref5, ref3],
]);
expect(info.routeObjects).toEqual([
routeObj('foo', [ref1, ref2], [], 'mounted', expect.any(Object)),
@@ -352,166 +352,177 @@ describe('discovery', () => {
]);
});
// it('should use the route aggregator but stop when encountering explicit path', () => {
// const root = (
// <MemoryRouter>
// <Routes>
// <Route path="foo" element={<Extension1 />}>
// <AggregationComponent path="/bar">
// <Extension2>
// <Routes>
// <Route path="baz" element={<Extension3 />}>
// <Route path="/blop" element={<Extension4 />} />
// </Route>
// </Routes>
// <Extension5 />
// </Extension2>
// </AggregationComponent>
// </Route>
// </Routes>
// </MemoryRouter>
// );
it('should use the route aggregator but stop when encountering explicit path', () => {
const info = routeInfoFromExtensions([
createTestExtension({
id: 'page1',
path: 'foo',
routeRef: ref1,
}),
createTestExtension({
id: 'page1Child',
at: 'page1/children',
path: 'bar',
}),
createTestExtension({
id: 'page2',
at: 'page1Child/children',
routeRef: ref2,
}),
createTestExtension({
id: 'page3',
at: 'page2/children',
path: 'baz',
routeRef: ref3,
}),
createTestExtension({
id: 'page4',
at: 'page3/children',
path: '/blop',
routeRef: ref4,
}),
createTestExtension({
id: 'page5',
at: 'page2/children',
routeRef: ref5,
}),
]);
// const { routing } = traverseElementTree({
// root,
// discoverers: [childDiscoverer, routeElementDiscoverer],
// collectors: {
// routing: routingV2Collector,
// },
// });
// expect(sortedEntries(routing.paths)).toEqual([
// [ref1, 'foo'],
// [ref2, 'bar'],
// [ref3, 'baz'],
// [ref4, 'blop'],
// [ref5, 'bar'],
// ]);
// expect(sortedEntries(routing.parents)).toEqual([
// [ref1, undefined],
// [ref2, ref1],
// [ref3, ref2],
// [ref4, ref3],
// [ref5, ref1],
// ]);
// expect(routing.objects).toEqual([
// routeObj(
// 'foo',
// [ref1],
// [
// routeObj(
// 'bar',
// [ref2, ref5],
// [
// routeObj(
// 'baz',
// [ref3],
// [routeObj('blop', [ref4], undefined, undefined, plugin)],
// undefined,
// plugin,
// ),
// ],
// 'gathered',
// plugin,
// ),
// ],
// undefined,
// plugin,
// ),
// ]);
// });
expect(sortedEntries(info.routePaths)).toEqual([
[ref1, 'foo'],
[ref2, 'bar'],
[ref3, 'baz'],
[ref4, 'blop'],
[ref5, 'bar'],
]);
expect(sortedEntries(info.routeParents)).toEqual([
[ref1, undefined],
[ref2, ref1],
[ref3, ref2],
[ref4, ref3],
[ref5, ref1],
]);
expect(info.routeObjects).toEqual([
routeObj(
'foo',
[ref1],
[
routeObj(
'bar',
[ref2, ref5],
[
routeObj(
'baz',
[ref3],
[
routeObj(
'blop',
[ref4],
undefined,
undefined,
expect.any(Object),
),
],
undefined,
expect.any(Object),
),
],
'mounted',
expect.any(Object),
),
],
undefined,
expect.any(Object),
),
]);
});
// it('should throw when you provide path property on an extension', () => {
// expect(() => {
// traverseElementTree({
// root: <Extension1 path="/foo" />,
// discoverers: [childDiscoverer, routeElementDiscoverer],
// collectors: {
// routing: routingV2Collector,
// },
// });
// }).toThrow(
// 'Path property may not be set directly on a routable extension "Extension(Extension1)"',
// );
// });
it('should account for loose route paths', () => {
const info = routeInfoFromExtensions([
createTestExtension({
id: 'r',
path: 'r',
}),
createTestExtension({
id: 'page1',
at: 'r/children',
path: 'x',
routeRef: ref1,
}),
createTestExtension({
id: 'y',
path: 'y',
at: 'r/children',
}),
createTestExtension({
id: 'page2',
at: 'y/children',
path: '1',
routeRef: ref2,
}),
createTestExtension({
id: 'page3',
at: 'page2/children',
path: 'a',
routeRef: ref3,
}),
createTestExtension({
id: 'page4',
at: 'page2/children',
path: 'b',
routeRef: ref4,
}),
]);
// it('should throw when element prop is not a string', () => {
// const Div = 'div' as unknown as ComponentType<{ path: boolean }>;
// expect(() => {
// traverseElementTree({
// root: <Div path />,
// discoverers: [childDiscoverer, routeElementDiscoverer],
// collectors: {
// routing: routingV2Collector,
// },
// });
// }).toThrow('Element path must be a string at "div"');
// });
// it('should throw when the mount point gatherers have an element prop', () => {
// const AnyAggregationComponent = AggregationComponent as any;
// expect(() => {
// traverseElementTree({
// root: <AnyAggregationComponent path="test" element={<Extension3 />} />,
// discoverers: [childDiscoverer, routeElementDiscoverer],
// collectors: {
// routing: routingV2Collector,
// },
// });
// }).toThrow(
// 'Mount point gatherers may not have an element prop "AggregationComponent"',
// );
// });
// it('should ignore path props within route elements', () => {
// const { routing } = traverseElementTree({
// root: <Route path="foo" element={<Extension1 path="bar" />} />,
// discoverers: [childDiscoverer, routeElementDiscoverer],
// collectors: {
// routing: routingV2Collector,
// },
// });
// expect(sortedEntries(routing.paths)).toEqual([[ref1, 'foo']]);
// expect(sortedEntries(routing.parents)).toEqual([[ref1, undefined]]);
// expect(routing.objects).toEqual([
// routeObj('foo', [ref1], [], undefined, plugin),
// ]);
// });
// it('should throw when a routable extension does not have a path set', () => {
// expect(() => {
// traverseElementTree({
// root: <Extension3 />,
// discoverers: [childDiscoverer, routeElementDiscoverer],
// collectors: {
// routing: routingV2Collector,
// },
// });
// }).toThrow(
// 'Routable extension "Extension(Extension3)" with mount point "routeRef{type=absolute,id=ref3}" must be assigned a path',
// );
// });
// it('should throw when Route elements contain multiple routable extensions', () => {
// expect(() => {
// traverseElementTree({
// root: (
// <Route
// path="foo"
// element={
// <>
// <Extension1 />
// <Extension2 />
// </>
// }
// />
// ),
// discoverers: [childDiscoverer, routeElementDiscoverer],
// collectors: {
// routing: routingV2Collector,
// },
// });
// }).toThrow(
// 'Route element with path "foo" may not contain multiple routable extensions',
// );
// });
expect(sortedEntries(info.routePaths)).toEqual([
[ref1, 'r/x'],
[ref2, 'r/y/1'],
[ref3, 'a'],
[ref4, 'b'],
]);
expect(sortedEntries(info.routeParents)).toEqual([
[ref1, undefined],
[ref2, undefined],
[ref3, ref2],
[ref4, ref2],
]);
expect(info.routeObjects).toEqual([
routeObj(
'r',
[],
[
routeObj('x', [ref1], [], 'mounted', expect.any(Object)),
routeObj(
'y',
[],
[
routeObj(
'1',
[ref2],
[
routeObj(
'a',
[ref3],
undefined,
'mounted',
expect.any(Object),
),
routeObj(
'b',
[ref4],
undefined,
'mounted',
expect.any(Object),
),
],
'mounted',
expect.any(Object),
),
],
'mounted',
),
],
),
]);
});
});
@@ -56,14 +56,19 @@ export function extractRouteInfoFromInstanceTree(roots: ExtensionInstance[]): {
collectedPath?: string,
foundRefForCollectedPath: boolean = false,
parentRef?: RouteRef,
candidateParentRef?: RouteRef,
parentObj?: BackstageRouteObject,
) {
const routePath = current.getData(coreExtensionData.routePath);
const routePath = current
.getData(coreExtensionData.routePath)
?.replace(/^\//, '');
const routeRef = current.getData(coreExtensionData.routeRef);
const parentChildren = parentObj?.children ?? routeObjects;
let currentObj = parentObj;
let newCollectedPath = collectedPath;
let newFoundRefForCollectedPath = foundRefForCollectedPath;
let newParentRef = parentRef;
let newCandidateParentRef = candidateParentRef;
if (routePath !== undefined) {
currentObj = {
@@ -85,6 +90,9 @@ export function extractRouteInfoFromInstanceTree(roots: ExtensionInstance[]): {
? joinPaths(collectedPath, routePath)
: routePath;
}
newParentRef = candidateParentRef;
newCandidateParentRef = undefined;
}
if (routeRef) {
@@ -95,12 +103,16 @@ export function extractRouteInfoFromInstanceTree(roots: ExtensionInstance[]): {
);
}
if (!newCandidateParentRef) {
newCandidateParentRef = routeRef;
}
if (newCollectedPath !== undefined) {
routePaths.set(routeRef, newCollectedPath);
newFoundRefForCollectedPath = true;
}
routeParents.set(routeRef, parentRef);
routeParents.set(routeRef, newParentRef);
currentObj?.routeRefs.add(routeRef);
if (current.source) {
currentObj?.plugins.add(toLegacyPlugin(current.source));
@@ -113,7 +125,8 @@ export function extractRouteInfoFromInstanceTree(roots: ExtensionInstance[]): {
child,
newCollectedPath,
newFoundRefForCollectedPath,
routeRef ?? parentRef,
newParentRef,
newCandidateParentRef,
currentObj,
);
}