Visitar URL original
fix(router): reject duplicate outlets in production builds · angular/angular@6489ca5 · GitHub
Skip to content

Commit 6489ca5

Browse files
SkyZeroZxatscott
authored andcommitted
fix(router): reject duplicate outlets in production builds
A URL group that consumes no segments is matched against the same config as its parent, so a route can match once for every group the URL nests. Every match lands in one sibling list, which the router cannot activate because only one of them can own the outlet. The check that rejects this ran only in development, and never covered the siblings `mergeEmptyPathMatches` produces. Run it on every list the merge returns instead. Fixes #70997
1 parent 4bc11a5 commit 6489ca5

3 files changed

Lines changed: 43 additions & 7 deletions

File tree

‎packages/core/test/bundling/router/bundle.golden_symbols.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@
452452
"captureError",
453453
"catchError",
454454
"checkGuards",
455+
"checkOutletNameUniqueness",
455456
"checkSecurityContext",
456457
"checkStable",
457458
"classIndexOf",

‎packages/router/src/recognize.ts‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,6 @@ export class Recognizer {
228228
// multiple activated results for the same outlet. We should merge the children of
229229
// these results so the final return value is only one `TreeNode` per outlet.
230230
const mergedChildren = mergeEmptyPathMatches(children);
231-
if (typeof ngDevMode === 'undefined' || ngDevMode) {
232-
// This should really never happen - we are only taking the first match for each
233-
// outlet and merge the empty path matches.
234-
checkOutletNameUniqueness(mergedChildren);
235-
}
236231
sortActivatedRouteSnapshots(mergedChildren);
237232
return mergedChildren;
238233
}
@@ -585,11 +580,13 @@ function mergeEmptyPathMatches(
585580
const mergedChildren = mergeEmptyPathMatches(mergedNode.children);
586581
result.push(new TreeNode(mergedNode.value, mergedChildren));
587582
}
588-
return result.filter((n) => !mergedNodes.has(n));
583+
const merged = result.filter((n) => !mergedNodes.has(n));
584+
checkOutletNameUniqueness(merged);
585+
return merged;
589586
}
590587

591588
function checkOutletNameUniqueness(nodes: TreeNode<ActivatedRouteSnapshot>[]): void {
592-
const names: {[k: string]: ActivatedRouteSnapshot} = {};
589+
const names: {[k: string]: ActivatedRouteSnapshot} = Object.create(null);
593590
nodes.forEach((n) => {
594591
const routeWithSameOutletName = names[n.value.outlet];
595592
if (routeWithSameOutletName) {

‎packages/router/test/recognize.spec.ts‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,44 @@ describe('recognize', () => {
11791179
}
11801180
});
11811181
});
1182+
1183+
describe('outlet name uniqueness', () => {
1184+
// The two `detail` matches merge into one node, so the duplicate is among its children.
1185+
it('rejects duplicates that appear only after empty path matches are merged', async () => {
1186+
await expectAsync(
1187+
recognize(
1188+
[
1189+
{
1190+
path: 'a',
1191+
children: [
1192+
{path: '', outlet: 'detail', children: [{path: ':id', component: ComponentA}]},
1193+
],
1194+
},
1195+
],
1196+
'a/(detail:1//x:/(detail:2))',
1197+
),
1198+
).toBeRejectedWithError(/Two segments cannot have the same outlet name: '1' and '2'/);
1199+
});
1200+
1201+
it('does not treat an outlet named after an Object member as a duplicate', async () => {
1202+
const s = await recognize(
1203+
[
1204+
{
1205+
path: 'a',
1206+
children: [
1207+
{path: 'b', outlet: 'constructor', component: ComponentB},
1208+
{path: 'c', component: ComponentC},
1209+
],
1210+
},
1211+
],
1212+
'a/(c//constructor:b)',
1213+
);
1214+
const children = s.root.firstChild!.children;
1215+
expect(children.length).toBe(2);
1216+
checkActivatedRoute(children[0], 'c', {}, ComponentC);
1217+
checkActivatedRoute(children[1], 'b', {}, ComponentB, 'constructor');
1218+
});
1219+
});
11821220
});
11831221

11841222
function collectSnapshots(

0 commit comments

Comments
 (0)