refactor(router): Update pre/post wildcard matching to retain 0 or more segment match meaning

Prior to the change to support defining required segments before and after the wildcard, the meaning of the wildcard
path was '0 or more' so path: foo with a child of ** will still match the ** route when the URL is only 'foo'.
If developers want to _require_ something, they can still use path: foo/:anyRequired/**/bar
This commit is contained in:
Andrew Scott
2025-11-04 08:09:24 -08:00
committed by Andrew Scott
parent 058377ed55
commit 7f16902a72
2 changed files with 83 additions and 12 deletions
-10
View File
@@ -193,16 +193,6 @@ export function defaultUrlMatcher(
// The actual URL is shorter than the config, no match
return null;
}
if (
// If the wildcard is not at the end of the path, it must match at least one segment.
// e.g. `foo/**/bar` does not match `foo/bar`.
wildcardIndex > -1 &&
pre.length > 0 &&
post.length > 0 &&
pre.length + post.length === segments.length
) {
return null;
}
if (route.pathMatch === 'full' && segmentGroup.hasChildren() && route.path !== '**') {
// The config is longer than the actual URL but we are looking for a full match, return null
+83 -2
View File
@@ -650,14 +650,49 @@ describe('recognize', () => {
const s = await recognizer('foo/a/b/c/bar');
checkActivatedRoute(s.root.firstChild!, 'foo/a/b/c/bar', {}, ComponentA);
});
it('does not match a url with no segments for the wildcard', async () => {
await expectAsync(recognizer('foo/bar')).toBeRejected();
it('matches a url with no segments for the wildcard', async () => {
const s = await recognizer('foo/bar');
checkActivatedRoute(s.root.firstChild!, 'foo/bar', {}, ComponentA);
});
it('does not match a url with a wrong suffix', async () => {
await expectAsync(recognizer('foo/a/b/baz')).toBeRejected();
});
});
describe('with prefix', () => {
const recognizer = (url: string) => {
const config = [{path: 'foo/**', component: ComponentA}];
return recognize(config, url);
};
it('matches a url with segments after the prefix', async () => {
const s = await recognizer('foo/a/b');
checkActivatedRoute(s.root.firstChild!, 'foo/a/b', {}, ComponentA);
});
it('matches a url with no segments after the prefix', async () => {
const s = await recognizer('foo');
checkActivatedRoute(s.root.firstChild!, 'foo', {}, ComponentA);
});
});
describe('with suffix', () => {
const recognizer = (url: string) => {
const config = [{path: '**/bar', component: ComponentA}];
return recognize(config, url);
};
it('matches a url with segments before the suffix', async () => {
const s = await recognizer('a/b/bar');
checkActivatedRoute(s.root.firstChild!, 'a/b/bar', {}, ComponentA);
});
it('matches a url with no segments before the suffix', async () => {
const s = await recognizer('bar');
checkActivatedRoute(s.root.firstChild!, 'bar', {}, ComponentA);
});
});
});
describe('componentless routes', () => {
@@ -877,6 +912,52 @@ describe('recognize', () => {
expect(s.root.firstChild!.data['id']).toEqual('b');
});
});
describe('with required param before wildcard', () => {
const recognizer = (url: string) => {
const config = [
{
path: 'foo/:anyRequired/**/bar',
component: ComponentA,
},
];
return recognize(config, url);
};
it('matches with one segment for wildcard', async () => {
const s = await recognizer('foo/required/a/bar');
checkActivatedRoute(
s.root.firstChild!,
'foo/required/a/bar',
{anyRequired: 'required'},
ComponentA,
);
});
it('matches with multiple segments for wildcard', async () => {
const s = await recognizer('foo/required/a/b/c/bar');
checkActivatedRoute(
s.root.firstChild!,
'foo/required/a/b/c/bar',
{anyRequired: 'required'},
ComponentA,
);
});
it('matches with no segments for wildcard', async () => {
const s = await recognizer('foo/required/bar');
checkActivatedRoute(
s.root.firstChild!,
'foo/required/bar',
{anyRequired: 'required'},
ComponentA,
);
});
it('does not match without the required segment', async () => {
await expectAsync(recognizer('foo/bar')).toBeRejected();
});
});
});
async function recognize(