core-app-api: remedy wrong relative route reference resolution

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-11-29 13:53:10 +01:00
parent 4add90deb3
commit 0e7f256034
3 changed files with 74 additions and 12 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-app-api': patch
---
Fixed a bug where `useRouteRef` would fail in situations where relative navigation was needed and the app was is mounted on a sub-path. This would typically show up as a failure to navigate to a tab on an entity page.
@@ -100,23 +100,55 @@ describe('RouteResolver', () => {
expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined);
});
it('should resolve an absolute route and an app base path', () => {
it('should resolve an absolute route and sub route with an app base path', () => {
const r = new RouteResolver(
new Map([[ref1, '/my-route']]),
new Map(),
[{ routeRefs: new Set([ref1]), path: '/my-route', ...rest }],
new Map<RouteRef, string>([
[ref2, '/my-parent/:x'],
[ref1, '/my-route'],
]),
new Map<RouteRef, RouteRef>([[ref1, ref2]]),
[
{
routeRefs: new Set([ref2]),
path: '/my-parent/:x',
...rest,
children: [
MATCH_ALL_ROUTE,
{ routeRefs: new Set([ref1]), path: '/my-route', ...rest },
],
},
],
new Map(),
'/base',
);
expect(r.resolve(ref1, '/')?.()).toBe('/base/my-route');
expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined);
expect(r.resolve(subRef1, '/')?.()).toBe('/base/my-route/foo');
expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe(
'/base/my-route/foo/2a',
expect(r.resolve(ref1, '/my-parent/1x')?.()).toBe(
'/base/my-parent/1x/my-route',
);
expect(r.resolve(ref1, '/base/my-parent/1x')?.()).toBe(
'/base/my-parent/1x/my-route',
);
expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe('/base/my-parent/1x');
expect(r.resolve(ref2, '/base')?.({ x: '1x' })).toBe('/base/my-parent/1x');
expect(r.resolve(ref3, '/')?.({ y: '1y' })).toBe(undefined);
expect(r.resolve(subRef1, '/my-parent/2x')?.()).toBe(
'/base/my-parent/2x/my-route/foo',
);
expect(r.resolve(subRef1, '/base/my-parent/2x')?.()).toBe(
'/base/my-parent/2x/my-route/foo',
);
expect(r.resolve(subRef2, '/my-parent/3x')?.({ a: '2a' })).toBe(
'/base/my-parent/3x/my-route/foo/2a',
);
expect(r.resolve(subRef2, '/base/my-parent/3x')?.({ a: '2a' })).toBe(
'/base/my-parent/3x/my-route/foo/2a',
);
expect(r.resolve(subRef3, '/')?.({ x: '5x' })).toBe(
'/base/my-parent/5x/bar',
);
expect(r.resolve(subRef4, '/')?.({ x: '6x', a: '4a' })).toBe(
'/base/my-parent/6x/bar/4a',
);
expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined);
expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined);
expect(r.resolve(externalRef1, '/')?.()).toBe(undefined);
expect(r.resolve(externalRef2, '/')?.()).toBe(undefined);
expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined);
@@ -207,6 +207,20 @@ export class RouteResolver {
return undefined;
}
// The location that we get passed in uses the full path, so start by trimming off
// the app base path prefix in case we're running the app on a sub-path.
let relativeSourceLocation: Parameters<typeof matchRoutes>[1];
if (typeof sourceLocation === 'string') {
relativeSourceLocation = this.trimPath(sourceLocation);
} else if (sourceLocation.pathname) {
relativeSourceLocation = {
...sourceLocation,
pathname: this.trimPath(sourceLocation.pathname),
};
} else {
relativeSourceLocation = sourceLocation;
}
// Next we figure out the base path, which is the combination of the common parent path
// between our current location and our target location, as well as the additional path
// that is the difference between the parent path and the base of our target location.
@@ -214,7 +228,7 @@ export class RouteResolver {
this.appBasePath +
resolveBasePath(
targetRef,
sourceLocation,
relativeSourceLocation,
this.routePaths,
this.routeParents,
this.routeObjects,
@@ -225,4 +239,15 @@ export class RouteResolver {
};
return routeFunc;
}
private trimPath(targetPath: string) {
if (!targetPath) {
return targetPath;
}
if (targetPath.startsWith(this.appBasePath)) {
return targetPath.slice(this.appBasePath.length);
}
return targetPath;
}
}