fix(router): popping route now accounts for route params (#24315)

This commit is contained in:
Sean Perkins
2021-12-06 17:25:40 -05:00
committed by GitHub
parent 8f188eaae7
commit 5e5054d369
2 changed files with 70 additions and 17 deletions

View File

@ -30,15 +30,24 @@ const CHAIN_3: RouteChain = [
describe('matchesIDs', () => { describe('matchesIDs', () => {
it('should match simple set of ids', () => { it('should match simple set of ids', () => {
const chain: RouteChain = CHAIN_1; const chain: RouteChain = CHAIN_1;
expect(matchesIDs(['2'], chain)).toBe(1); expect(matchesIDs([{ id: '2' }], chain)).toBe(1);
expect(matchesIDs(['2', '1'], chain)).toBe(2); expect(matchesIDs([{ id: '2' }, { id: '1' }], chain)).toBe(2);
expect(matchesIDs(['2', '1', '3'], chain)).toBe(3); expect(matchesIDs([{ id: '2' }, { id: '1' }, { id: '3' }], chain)).toBe(3);
expect(matchesIDs(['2', '1', '3', '4'], chain)).toBe(4); expect(matchesIDs([{ id: '2' }, { id: '1' }, { id: '3' }, { id: '4' }], chain)).toBe(4);
expect(matchesIDs(['2', '1', '3', '4', '5'], chain)).toBe(4); expect(matchesIDs([{ id: '2' }, { id: '1' }, { id: '3' }, { id: '4' }, { id: '5' }], chain)).toBe(4);
expect(matchesIDs([], chain)).toBe(0); expect(matchesIDs([], chain)).toBe(0);
expect(matchesIDs(['1'], chain)).toBe(0); expect(matchesIDs([{ id: '1' }], chain)).toBe(0);
}); });
it('should match path with params', () => {
const ids = [{ id: 'my-page', params: { s1: 'a', s2: 'b' } }];
expect(matchesIDs(ids, [{ id: 'my-page', path: [''], params: {} }])).toBe(1);
expect(matchesIDs(ids, [{ id: 'my-page', path: [':s1'], params: {} }])).toBe(1);
expect(matchesIDs(ids, [{ id: 'my-page', path: [':s1', ':s2'], params: {} }])).toBe(3);
expect(matchesIDs(ids, [{ id: 'my-page', path: [':s1', ':s2', ':s3'], params: {} }])).toBe(1);
})
}); });
describe('matchesPath', () => { describe('matchesPath', () => {
@ -227,7 +236,7 @@ describe('mergeParams', () => {
}); });
describe('RouterSegments', () => { describe('RouterSegments', () => {
it ('should initialize with empty array', () => { it('should initialize with empty array', () => {
const s = new RouterSegments([]); const s = new RouterSegments([]);
expect(s.next()).toEqual(''); expect(s.next()).toEqual('');
expect(s.next()).toEqual(''); expect(s.next()).toEqual('');
@ -236,7 +245,7 @@ describe('RouterSegments', () => {
expect(s.next()).toEqual(''); expect(s.next()).toEqual('');
}); });
it ('should initialize with array', () => { it('should initialize with array', () => {
const s = new RouterSegments(['', 'path', 'to', 'destination']); const s = new RouterSegments(['', 'path', 'to', 'destination']);
expect(s.next()).toEqual(''); expect(s.next()).toEqual('');
expect(s.next()).toEqual('path'); expect(s.next()).toEqual('path');

View File

@ -32,16 +32,60 @@ export const findRouteRedirect = (path: string[], redirects: RouteRedirect[]) =>
return redirects.find(redirect => matchesRedirect(path, redirect)); return redirects.find(redirect => matchesRedirect(path, redirect));
}; };
export const matchesIDs = (ids: string[], chain: RouteChain): number => { export const matchesIDs = (ids: Pick<RouteID, 'id' | 'params'>[], chain: RouteChain): number => {
const len = Math.min(ids.length, chain.length); const len = Math.min(ids.length, chain.length);
let i = 0;
for (; i < len; i++) { let score = 0;
if (ids[i].toLowerCase() !== chain[i].id) {
for (let i = 0; i < len; i++) {
const routeId = ids[i];
const routeChain = chain[i];
// Skip results where the route id does not match the chain at the same index
if (routeId.id.toLowerCase() !== routeChain.id) {
break; break;
} }
if (routeId.params) {
const routeIdParams = Object.keys(routeId.params);
/**
* Only compare routes with the chain that have the same number of parameters.
*/
if (routeIdParams.length === routeChain.path.length) {
/**
* Maps the route's params into a path based on the path variable names,
* to compare against the route chain format.
*
* Before:
* ```ts
* {
* params: {
* s1: 'a',
* s2: 'b'
* }
* }
* ```
*
* After:
* ```ts
* [':s1',':s2']
* ```
*/
const pathWithParams = routeIdParams.map(key => `:${key}`);
for (let j = 0; j < pathWithParams.length; j++) {
// Skip results where the path variable is not a match
if (pathWithParams[j].toLowerCase() !== routeChain.path[j]) {
break;
}
// Weight path matches for the same index higher.
score++;
}
}
}
// Weight id matches
score++;
} }
return i; return score;
}; }
export const matchesPath = (inputPath: string[], chain: RouteChain): RouteChain | null => { export const matchesPath = (inputPath: string[], chain: RouteChain): RouteChain | null => {
const segments = new RouterSegments(inputPath); const segments = new RouterSegments(inputPath);
@ -90,16 +134,16 @@ export const matchesPath = (inputPath: string[], chain: RouteChain): RouteChain
// Merges the route parameter objects. // Merges the route parameter objects.
// Returns undefined when both parameters are undefined. // Returns undefined when both parameters are undefined.
export const mergeParams = (a: {[key: string]: any} | undefined, b: {[key: string]: any} | undefined): {[key: string]: any} | undefined => { export const mergeParams = (a: { [key: string]: any } | undefined, b: { [key: string]: any } | undefined): { [key: string]: any } | undefined => {
return a || b ? { ...a, ...b } : undefined; return a || b ? { ...a, ...b } : undefined;
}; };
export const routerIDsToChain = (ids: RouteID[], chains: RouteChain[]): RouteChain | null => { export const routerIDsToChain = (ids: RouteID[], chains: RouteChain[]): RouteChain | null => {
let match: RouteChain | null = null; let match: RouteChain | null = null;
let maxMatches = 0; let maxMatches = 0;
const plainIDs = ids.map(i => i.id);
for (const chain of chains) { for (const chain of chains) {
const score = matchesIDs(plainIDs, chain); const score = matchesIDs(ids, chain);
if (score > maxMatches) { if (score > maxMatches) {
match = chain; match = chain;
maxMatches = score; maxMatches = score;