core-api: initial implementation of external route refs
Co-authored-by: blam <ben@blam.sh>
This commit is contained in:
@@ -53,3 +53,13 @@ export function createRouteRef<
|
||||
>(config: RouteRefConfig<Params>): RouteRef<Params> {
|
||||
return new AbsoluteRouteRef<Params>(config);
|
||||
}
|
||||
|
||||
export class ExternalRouteRef {
|
||||
toString() {
|
||||
return `externalRouteRef{}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function createExternalRouteRef(): ExternalRouteRef {
|
||||
return new ExternalRouteRef();
|
||||
}
|
||||
|
||||
@@ -35,7 +35,11 @@ import {
|
||||
validateRoutes,
|
||||
RouteFunc,
|
||||
} from './hooks';
|
||||
import { createRouteRef } from './RouteRef';
|
||||
import {
|
||||
createRouteRef,
|
||||
createExternalRouteRef,
|
||||
ExternalRouteRef,
|
||||
} from './RouteRef';
|
||||
import { RouteRef, RouteRefConfig } from './types';
|
||||
|
||||
const mockConfig = (extra?: Partial<RouteRefConfig<{}>>) => ({
|
||||
@@ -52,10 +56,13 @@ const ref2 = createRouteRef(mockConfig({ path: '/wat2' }));
|
||||
const ref3 = createRouteRef(mockConfig({ path: '/wat3' }));
|
||||
const ref4 = createRouteRef(mockConfig({ path: '/wat4' }));
|
||||
const ref5 = createRouteRef(mockConfig({ path: '/wat5' }));
|
||||
const eRefA = createExternalRouteRef();
|
||||
const eRefB = createExternalRouteRef();
|
||||
const eRefC = createExternalRouteRef();
|
||||
|
||||
const MockRouteSource = <T extends { [name in string]: string }>(props: {
|
||||
name: string;
|
||||
routeRef: RouteRef<T>;
|
||||
routeRef: RouteRef<T> | ExternalRouteRef;
|
||||
params?: T;
|
||||
}) => {
|
||||
try {
|
||||
@@ -90,7 +97,10 @@ const Extension5 = plugin.provide(
|
||||
createRoutableExtension({ component: MockComponent, mountPoint: ref5 }),
|
||||
);
|
||||
|
||||
function withRoutingProvider(root: ReactElement) {
|
||||
function withRoutingProvider(
|
||||
root: ReactElement,
|
||||
routeBindings: [ExternalRouteRef, RouteRef][] = [],
|
||||
) {
|
||||
const { routePaths, routeParents, routeObjects } = traverseElementTree({
|
||||
root,
|
||||
discoverers: [childDiscoverer, routeElementDiscoverer],
|
||||
@@ -106,6 +116,7 @@ function withRoutingProvider(root: ReactElement) {
|
||||
routePaths={routePaths}
|
||||
routeParents={routeParents}
|
||||
routeObjects={routeObjects}
|
||||
routeBindings={new Map(routeBindings)}
|
||||
>
|
||||
{root}
|
||||
</RoutingProvider>
|
||||
@@ -119,17 +130,35 @@ describe('discovery', () => {
|
||||
<Routes>
|
||||
<Extension1 path="/foo">
|
||||
<Extension2 path="/bar" name="inside" routeRef={ref2} />
|
||||
<MockRouteSource name="insideExternal" routeRef={eRefA} />
|
||||
</Extension1>
|
||||
<Extension3 path="/baz" />
|
||||
</Routes>
|
||||
<MockRouteSource name="outside" routeRef={ref2} />
|
||||
<MockRouteSource name="outsideExternal1" routeRef={eRefB} />
|
||||
<MockRouteSource name="outsideExternal2" routeRef={eRefC} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const rendered = render(withRoutingProvider(root));
|
||||
const rendered = render(
|
||||
withRoutingProvider(root, [
|
||||
[eRefA, ref3],
|
||||
[eRefB, ref1],
|
||||
[eRefC, ref2],
|
||||
]),
|
||||
);
|
||||
|
||||
expect(rendered.getByText('Path at inside: /foo/bar')).toBeInTheDocument();
|
||||
expect(
|
||||
rendered.getByText('Path at insideExternal: /baz'),
|
||||
).toBeInTheDocument();
|
||||
expect(rendered.getByText('Path at outside: /foo/bar')).toBeInTheDocument();
|
||||
expect(
|
||||
rendered.getByText('Path at outsideExternal1: /foo'),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
rendered.getByText('Path at outsideExternal2: /foo/bar'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle routeRefs with parameters', () => {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import React, { createContext, ReactNode, useContext, useMemo } from 'react';
|
||||
import { AnyRouteRef, BackstageRouteObject, RouteRef } from './types';
|
||||
import { generatePath, matchRoutes, useLocation } from 'react-router-dom';
|
||||
import { ExternalRouteRef } from './RouteRef';
|
||||
|
||||
// The extra TS magic here is to require a single params argument if the RouteRef
|
||||
// had at least one param defined, but require 0 arguments if there are no params defined.
|
||||
@@ -34,12 +35,17 @@ class RouteResolver {
|
||||
private readonly routePaths: Map<AnyRouteRef, string>,
|
||||
private readonly routeParents: Map<AnyRouteRef, AnyRouteRef | undefined>,
|
||||
private readonly routeObjects: BackstageRouteObject[],
|
||||
private readonly routeBindings: Map<ExternalRouteRef, RouteRef>,
|
||||
) {}
|
||||
|
||||
resolve<Params extends { [param in string]: string }>(
|
||||
routeRef: RouteRef<Params>,
|
||||
routeRefOrExternalRouteRef: RouteRef<Params> | ExternalRouteRef,
|
||||
sourceLocation: ReturnType<typeof useLocation>,
|
||||
): RouteFunc<Params> {
|
||||
const routeRef =
|
||||
this.routeBindings.get(routeRefOrExternalRouteRef) ??
|
||||
(routeRefOrExternalRouteRef as RouteRef<Params>);
|
||||
|
||||
const match = matchRoutes(this.routeObjects, sourceLocation) ?? [];
|
||||
|
||||
const lastPath = this.routePaths.get(routeRef);
|
||||
@@ -106,7 +112,7 @@ class RouteResolver {
|
||||
const RoutingContext = createContext<RouteResolver | undefined>(undefined);
|
||||
|
||||
export function useRouteRef<Params extends { [param in string]: string }>(
|
||||
routeRef: RouteRef<Params>,
|
||||
routeRef: RouteRef<Params> | ExternalRouteRef,
|
||||
): RouteFunc<Params> {
|
||||
const sourceLocation = useLocation();
|
||||
const resolver = useContext(RoutingContext);
|
||||
@@ -126,6 +132,7 @@ type ProviderProps = {
|
||||
routePaths: Map<AnyRouteRef, string>;
|
||||
routeParents: Map<AnyRouteRef, AnyRouteRef | undefined>;
|
||||
routeObjects: BackstageRouteObject[];
|
||||
routeBindings: Map<ExternalRouteRef, RouteRef>;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
@@ -133,9 +140,15 @@ export const RoutingProvider = ({
|
||||
routePaths,
|
||||
routeParents,
|
||||
routeObjects,
|
||||
routeBindings,
|
||||
children,
|
||||
}: ProviderProps) => {
|
||||
const resolver = new RouteResolver(routePaths, routeParents, routeObjects);
|
||||
const resolver = new RouteResolver(
|
||||
routePaths,
|
||||
routeParents,
|
||||
routeObjects,
|
||||
routeBindings,
|
||||
);
|
||||
return (
|
||||
<RoutingContext.Provider value={resolver}>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user