core-app-api: beta and compat tests for route collection

Co-authored-by: blam <ben@blam.sh>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-07-13 14:24:06 +02:00
parent 937b09d135
commit e09aef53b5
2 changed files with 38 additions and 603 deletions
@@ -109,7 +109,7 @@ function routeObj(
routeRefs: new Set(refs),
children: [
{
path: '/*',
path: '*',
caseSensitive: false,
element: 'match-all',
routeRefs: new Set(),
@@ -18,7 +18,6 @@ import React from 'react';
import type { PropsWithChildren } from 'react';
import {
RouteRef,
BackstagePlugin,
createPlugin,
createRouteRef,
createRoutableExtension,
@@ -124,9 +123,8 @@ describe.each(['beta', 'stable'])('react-router %s', rrVersion => {
refs: RouteRef[],
children: any[] = [],
type: 'mounted' | 'gathered' = 'mounted',
backstagePlugin?: BackstagePlugin,
) {
return {
return expect.objectContaining({
path: path,
caseSensitive: false,
element: type,
@@ -140,8 +138,7 @@ describe.each(['beta', 'stable'])('react-router %s', rrVersion => {
},
...children,
],
plugin: backstagePlugin,
};
});
}
describe(`routing with ${rrVersion}`, () => {
@@ -158,10 +155,7 @@ describe.each(['beta', 'stable'])('react-router %s', rrVersion => {
} = requireDeps();
traversalOptions = {
discoverers:
rrVersion === 'beta'
? [childDiscoverer, routeElementDiscoverer]
: [childDiscoverer],
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing:
rrVersion === 'beta' ? routingV1Collector : routingV2Collector,
@@ -176,33 +170,34 @@ describe.each(['beta', 'stable'])('react-router %s', rrVersion => {
<div key={0} />,
<div key={1} />,
<div key={3}>
<Extension5 path="/blop" />
<Route path="blop" element={<Extension5 />} />
</div>,
];
const root = (
<MemoryRouter>
<Routes>
<Extension1 path="/foo">
<Route path="nothing" element={<div />} />
<Route path="foo" element={<Extension1 />}>
<div>
<Extension2 path="/bar/:id">
<Route path="bar/:id" element={<Extension2 />}>
<div>
<div />
Some text here shouldn't be a problem
<div />
{null}
<div />
<Extension3 path="/baz" />
<Route path="baz" element={<Extension3 />} />
</div>
</Extension2>
</Route>
{false}
{list}
{true}
{0}
</div>
</Extension1>
</Route>
<div>
<Route path="/divsoup" element={<Extension4 />} />
<Route path="divsoup" element={<Extension4 />} />
</div>
</Routes>
</MemoryRouter>
@@ -214,11 +209,11 @@ describe.each(['beta', 'stable'])('react-router %s', rrVersion => {
});
expect(sortedEntries(routing.paths)).toEqual([
[ref1, '/foo'],
[ref2, '/bar/:id'],
[ref3, '/baz'],
[ref4, '/divsoup'],
[ref5, '/blop'],
[ref1, 'foo'],
[ref2, 'bar/:id'],
[ref3, 'baz'],
[ref4, 'divsoup'],
[ref5, 'blop'],
]);
expect(sortedEntries(routing.parents)).toEqual([
[ref1, undefined],
@@ -229,97 +224,52 @@ describe.each(['beta', 'stable'])('react-router %s', rrVersion => {
]);
expect(routing.objects).toEqual([
routeObj(
'/foo',
'foo',
[ref1],
[
routeObj('/bar/:id', [ref2], [routeObj('/baz', [ref3])]),
routeObj('/blop', [ref5]),
routeObj('bar/:id', [ref2], [routeObj('baz', [ref3])]),
routeObj('blop', [ref5]),
],
),
routeObj('/divsoup', [ref4], undefined, undefined, plugin),
routeObj('divsoup', [ref4]),
]);
});
it('should handle all react router Route patterns', () => {
it('should collect routes with aggregators', () => {
const { MemoryRouter, Routes, Route, traverseElementTree } =
requireDeps();
const root = (
<MemoryRouter>
<Routes>
<Route
path="/foo"
element={
<Extension1>
<Routes>
<Extension2 path="/bar/:id" />
</Routes>
</Extension1>
}
/>
<Route path="/baz" element={<Extension3 path="/not-used" />}>
<Route path="/divsoup" element={<Extension4 />} />
<Extension5 path="/blop" />
</Route>
</Routes>
</MemoryRouter>
);
const { routing } = traverseElementTree({
root,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV1Collector,
},
});
expect(sortedEntries(routing.paths)).toEqual([
[ref1, '/foo'],
[ref2, '/bar/:id'],
[ref3, '/baz'],
[ref4, '/divsoup'],
[ref5, '/blop'],
]);
expect(sortedEntries(routing.parents)).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 root = (
<MemoryRouter>
<Routes>
<AggregationComponent path="/foo">
<AggregationComponent path="foo">
<Extension1 />
<div>
<Extension2 />
</div>
HELLO
</AggregationComponent>
<Extension3 path="/bar">
<AggregationComponent path="/baz">
<Route path="bar" element={<Extension3 />}>
<AggregationComponent path="baz">
<Extension4>
<Extension5 />
</Extension4>
</AggregationComponent>
</Extension3>
</Route>
</Routes>
</MemoryRouter>
);
const { routing } = traverseElementTree({
root,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV1Collector,
},
...traversalOptions,
});
expect(sortedEntries(routing.paths)).toEqual([
[ref1, '/foo'],
[ref2, '/foo'],
[ref3, '/bar'],
[ref4, '/baz'],
[ref5, '/baz'],
[ref1, 'foo'],
[ref2, 'foo'],
[ref3, 'bar'],
[ref4, 'baz'],
[ref5, 'baz'],
]);
expect(sortedEntries(routing.parents)).toEqual([
[ref1, undefined],
@@ -329,526 +279,11 @@ describe.each(['beta', 'stable'])('react-router %s', rrVersion => {
[ref5, ref3],
]);
expect(routing.objects).toEqual([
routeObj('/foo', [ref1, ref2], [], 'gathered'),
routeObj('foo', [ref1, ref2], [], 'gathered'),
routeObj(
'/bar',
'bar',
[ref3],
[routeObj('/baz', [ref4, ref5], [], 'gathered')],
),
]);
});
it('should use the route aggregator but stop when encountering explicit path', () => {
const root = (
<MemoryRouter>
<Routes>
<Extension1 path="/foo">
<AggregationComponent path="/bar">
<Extension2>
<Extension3 path="/baz">
<Extension4 path="/blop" />
</Extension3>
<Extension5 />
</Extension2>
</AggregationComponent>
</Extension1>
</Routes>
</MemoryRouter>
);
const { routing } = traverseElementTree({
root,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV1Collector,
},
});
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, ref1],
[ref4, ref3],
[ref5, ref1],
]);
expect(routing.objects).toEqual([
routeObj(
'/foo',
[ref1],
[
routeObj(
'/bar',
[ref2, ref5],
[routeObj('/baz', [ref3], [routeObj('/blop', [ref4])])],
'gathered',
),
],
),
]);
});
it('should stop gathering mount points after encountering explicit path', () => {
const root = (
<MemoryRouter>
<Routes>
<Extension1 path="/foo">
<AggregationComponent path="/bar">
<Extension2 path="/baz">
<Extension3 />
</Extension2>
</AggregationComponent>
</Extension1>
</Routes>
</MemoryRouter>
);
expect(() => {
traverseElementTree({
root,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV1Collector,
},
});
}).toThrow('Mounted routable extension must have a path');
});
});
describe('routingV2Collector', () => {
function routeRoot(element: JSX.Element) {
return (
<MemoryRouter>
<Routes>{element}</Routes>
</MemoryRouter>
);
}
it('should associate path with extension in element prop', () => {
const { routing } = traverseElementTree({
root: routeRoot(<Route path="/foo" element={<Extension1 />} />),
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 not allow multiple extensions within the same element prop', () => {
expect(() =>
traverseElementTree({
root: routeRoot(
<Route
path="/foo"
element={
<>
<Extension1 />
<Extension2 />
</>
}
/>,
),
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
}),
).toThrow('Route element may not contain multiple routable extensions');
});
it('should not support inline path', () => {
expect(() =>
traverseElementTree({
root: routeRoot(<Extension1 path="/foo" />),
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
}),
).toThrow(
'Path property may not be set directly on a routable extension',
);
});
it('should associate the path with extensions deep in the element prop', () => {
const { routing } = traverseElementTree({
root: routeRoot(
<Route
path="/foo"
element={
<>
<div>
<span />
{[
undefined,
null,
<main>
<Extension1 />
</main>,
]}
</div>
</>
}
/>,
),
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 not associate path with extension in children prop', () => {
expect(() =>
traverseElementTree({
root: routeRoot(
<Route path="/foo">
<Extension1 />
</Route>,
),
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
}),
).toThrow('Routable extension must be assigned a path');
});
it('should assign parent for extension in element prop', () => {
const { routing } = traverseElementTree({
root: routeRoot(
<Route path="/foo" element={<Extension1 />}>
<Route path="/bar" element={<Extension2 />} />
</Route>,
),
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
});
expect(sortedEntries(routing.paths)).toEqual([
[ref1, '/foo'],
[ref2, '/bar'],
]);
expect(sortedEntries(routing.parents)).toEqual([
[ref1, undefined],
[ref2, ref1],
]);
expect(routing.objects).toEqual([
routeObj(
'/foo',
[ref1],
[routeObj('/bar', [ref2], [], undefined, plugin)],
undefined,
plugin,
),
]);
});
it('should not allow paths within element props', () => {
expect(() => {
traverseElementTree({
root: routeRoot(
<Route
path="."
element={<Route path="/foo" element={<Extension1 />} />}
/>,
),
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
});
}).toThrow('Elements within the element prop tree may not contain paths');
});
it('should not allow extensions within the element prop to have path props either', () => {
expect(() => {
traverseElementTree({
root: routeRoot(
<Route path="." element={<Extension1 path="/foo" />} />,
),
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
});
}).toThrow('Elements within the element prop tree may not contain paths');
});
it('should gather extension', () => {
const { routing } = traverseElementTree({
root: routeRoot(
<AggregationComponent path="/foo">
<Extension1 />
</AggregationComponent>,
),
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], [], 'gathered'),
]);
});
it('should reset gathering if path prop is encountered', () => {
const { routing } = traverseElementTree({
root: routeRoot(
<AggregationComponent path="/foo">
<Extension1>
<Route path="/bar" element={<Extension2 />} />
</Extension1>
</AggregationComponent>,
),
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
});
expect(sortedEntries(routing.paths)).toEqual([
[ref1, '/foo'],
[ref2, '/bar'],
]);
expect(sortedEntries(routing.parents)).toEqual([
[ref1, undefined],
[ref2, ref1],
]);
expect(routing.objects).toEqual([
routeObj(
'/foo',
[ref1],
[routeObj('/bar', [ref2], [], undefined, plugin)],
'gathered',
),
]);
});
it('should collect routes defined with different patterns', () => {
const list = [
<div key={0} />,
<div key={1} />,
<div key={3}>
<Route path="/blop" element={<Extension5 />} />
</div>,
];
const { routing } = traverseElementTree({
root: routeRoot(
<>
<Route path="/foo" element={<Extension1 />}>
<div>
<Route path="/bar/:id" element={<Extension2 />}>
<div>
<div />
Some text here shouldn't be a problem
<div />
{null}
<div />
<Route path="/baz" element={<Extension3 />} />
</div>
</Route>
{false}
{list}
{true}
{0}
</div>
</Route>
<div>
<Route path="/divsoup" element={<Extension4 />} />
</div>
</>,
),
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
});
expect(sortedEntries(routing.paths)).toEqual([
[ref1, '/foo'],
[ref2, '/bar/:id'],
[ref3, '/baz'],
[ref4, '/divsoup'],
[ref5, '/blop'],
]);
expect(sortedEntries(routing.parents)).toEqual([
[ref1, undefined],
[ref2, ref1],
[ref3, ref2],
[ref4, undefined],
[ref5, ref1],
]);
expect(routing.objects).toEqual([
routeObj(
'/foo',
[ref1],
[
routeObj(
'/bar/:id',
[ref2],
[routeObj('/baz', [ref3], [], undefined, plugin)],
undefined,
plugin,
),
routeObj('/blop', [ref5], [], undefined, plugin),
],
undefined,
plugin,
),
routeObj('/divsoup', [ref4], undefined, undefined, plugin),
]);
});
it('should handle a subset of react router Route patterns', () => {
const { routing } = traverseElementTree({
root: routeRoot(
<>
<Route path="/foo" element={<Extension1 />} />
<Route
path="/baz"
element={
<div>
<Extension2 />
</div>
}
>
<Route path="/child" element={<Extension3 />} />
<AggregationComponent path="/collected">
<Extension4 />
</AggregationComponent>
</Route>
</>,
),
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
});
expect(sortedEntries(routing.paths)).toEqual([
[ref1, '/foo'],
[ref2, '/baz'],
[ref3, '/child'],
[ref4, '/collected'],
]);
expect(sortedEntries(routing.parents)).toEqual([
[ref1, undefined],
[ref2, undefined],
[ref3, ref2],
[ref4, ref2],
]);
});
it('should bind child routes to the same path with a gatherer flag', () => {
const { routing } = traverseElementTree({
root: routeRoot(
<>
<AggregationComponent path="/foo">
<Extension1 />
<div>
<Extension2 />
</div>
HELLO
</AggregationComponent>
</>,
),
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routing: routingV2Collector,
},
});
expect(sortedEntries(routing.paths)).toEqual([
[ref1, '/foo'],
[ref2, '/foo'],
]);
expect(sortedEntries(routing.parents)).toEqual([
[ref1, undefined],
[ref2, undefined],
]);
expect(routing.objects).toEqual([
routeObj('/foo', [ref1, ref2], [], 'gathered'),
]);
});
it('should gather routes but stop when encountering explicit path', () => {
const { routing } = traverseElementTree({
root: routeRoot(
<Route path="/foo" element={<Extension1 />}>
<AggregationComponent path="/bar">
<Extension2>
<Route path="/baz" element={<Extension3 />}>
<AggregationComponent path="/blop">
<Extension4 />
</AggregationComponent>
</Route>
<Extension5 />
</Extension2>
</AggregationComponent>
</Route>,
),
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], [], 'gathered')],
undefined,
plugin,
),
],
'gathered',
),
],
undefined,
plugin,
[routeObj('baz', [ref4, ref5], [], 'gathered')],
),
]);
});