angular/packages/router/test
Andrew Scott 0960592d3d fix(router): pass outlet context to split to fix empty path named outlets
The `split` helper function in `packages/router/src/utils/config_matching.ts` was blind to the current outlet being processed. When encountering an empty path named outlet in the config, it would assume it needed to pull it in as a synthetic empty group, even if we were already in the process of resolving that very outlet!

When navigating to `/(secondary:component-copy)` with this config:

```typescript
{
  path: '',
  component: MainLayout,
  children: [
    { path: '', outlet: 'secondary', component: SecondaryComponent, children: [{path: 'component-copy'}] }
  ]
}
```

The router uses `MainLayout` as a pass-through and calls `split` on its children with segments `['component-copy']`.
`split` uses the `containsEmptyPathMatchesWithNamedOutlets` helper to determine if there are any candidate empty path named outlets to pull in. Because of this, it sees `{ path: '', outlet: 'secondary' }` and says: "Ah, an empty path named outlet! I must pull it in!"
Rather than falling through to standard segment matching, it returns `UrlSegmentGroup(segments: [], children: {secondary: emptyGroup})`.
The router then tries to process `primary` (with `[]` segments) and fails because the config only has `secondary`. It also tries to process `secondary` with the `emptyGroup`. While `{ path: '', outlet: 'secondary' }` matches the empty group, its child `{ path: 'component-copy' }` fails to match because the `emptyGroup` has no segments! So both branches fail, resulting in a `NoMatch` error for the entire navigation!

Pulling in empty path named outlets IS desired when they act as siblings to segments we are matching. This has worked before and continues to work!

```typescript
{
  path: 'a',
  children: [
    { path: 'b', component: ComponentB },
    { path: '', component: ComponentC, outlet: 'aux' }
  ]
}
```

When navigating to `a/b`, `split` sees segments `['b']` and the `aux` empty path. It pulls in `aux` so it gets instantiated alongside `b`. This is correct!

If we have a named outlet with a non-empty path under an empty path parent:

```typescript
{
  path: '',
  component: MainLayout,
  children: [
    { path: 'component-copy', outlet: 'secondary', component: ComponentE }
  ]
}
```

When we navigate to `/(secondary:component-copy)`:
- `split` uses `containsEmptyPathMatchesWithNamedOutlets` to see if there are any empty path named outlets. Since it only sees `path: 'component-copy'`, it returns `false`.
- It falls through to standard segment matching, which finds `component-copy` in the segments array and activates it flawlessly!

This worked perfectly before the fix because it didn't use `containsEmptyPathMatchesWithNamedOutlets`.

The fix passes the **current active outlet context** into `split`. If `split` finds an empty path named outlet that matches the outlet we are already processing, it ignores it as a pull-in candidate.

When evaluating `MainLayout` children for `secondary`:
- URL Segments left to process: `['component-copy']`
- Current Outlet: `secondary`
- `childConfig`: `[{ path: '', outlet: 'secondary' }]`

Previously, `split` saw the empty path and pulled it in as a synthetic empty group, breaking matching. Now, since `getOutlet(r) === outlet` (both are `secondary`), the fix ignores it. Instead of returning empty segments, it **falls through to standard segment matching**, which successfully find the `component-copy` segment!

When evaluating `ComponentA` children for `primary`:
- URL Segments left to process: `['b']`
- Current Outlet: `primary`
- `childConfig`: `[{ path: 'b' }, { path: '', outlet: 'aux' }]`

Since `getOutlet(aux) !== primary`, the fix **does not ignore it**. `split` pulls in `aux: emptyGroup` as a sibling, instantiating `ComponentC` alongside `ComponentB`. This preserves correct behavior for auxiliary outlets!

fixes #67708

(cherry picked from commit daa9b2a9d6)
2026-04-01 11:48:47 +02:00
..
directives test(router): move timeout and autoTick helpers to shared testing utilities 2026-02-10 07:45:00 -08:00
integration test(router): move timeout and autoTick helpers to shared testing utilities 2026-02-10 07:45:00 -08:00
operators test(router): move timeout and autoTick helpers to shared testing utilities 2026-02-10 07:45:00 -08:00
utils refactor: update license text to point to angular.dev (#57901) 2024-09-24 15:33:00 +02:00
apply_redirects.spec.ts test(router): move timeout and autoTick helpers to shared testing utilities 2026-02-10 07:45:00 -08:00
bootstrap.spec.ts test(router): move timeout and autoTick helpers to shared testing utilities 2026-02-10 07:45:00 -08:00
BUILD.bazel refactor(router): Intercept navigate events (unless it's a rollback) 2025-11-06 15:00:08 -08:00
computed_state_restoration.spec.ts refactor(router): Permit deferring commit of traversal navigations 2026-03-03 22:09:15 +00:00
config.spec.ts build: update Jasmine to 6.0.0 2026-02-09 12:15:57 -08:00
create_router_state.spec.ts refactor(router): Store route injector on ActivatedRoute instance 2025-11-20 17:05:10 -05:00
create_url_tree.spec.ts test(router): move timeout and autoTick helpers to shared testing utilities 2026-02-10 07:45:00 -08:00
default_export_component.ts refactor(router): remove standalone true (#58950) 2024-12-05 16:02:37 -08:00
default_export_routes.ts refactor(router): switching to relative imports within the router package (#60557) 2025-04-01 14:54:05 +00:00
helpers.ts test(router): move timeout and autoTick helpers to shared testing utilities 2026-02-10 07:45:00 -08:00
page_title_strategy_spec.ts ci: reformat files 2025-12-16 14:44:19 -08:00
recognize.spec.ts fix(router): pass outlet context to split to fix empty path named outlets 2026-04-01 11:48:47 +02:00
regression_integration.spec.ts test(router): move timeout and autoTick helpers to shared testing utilities 2026-02-10 07:45:00 -08:00
route_injector_cleanup.spec.ts feat(router): add controls for route cleanup 2026-01-05 14:43:56 -05:00
router.spec.ts refactor(router): Store route injector on ActivatedRoute instance 2025-11-20 17:05:10 -05:00
router_devtools.spec.ts feat(devtools): clean up router tree for stable release (#63081) 2025-09-02 20:59:15 -07:00
router_link_active.spec.ts feat(router): Update IsActiveMatchOptions APIs to accept a Partial 2026-01-29 12:10:40 -08:00
router_link_spec.ts test(router): remove provider zoneless from tests 2026-02-02 15:00:18 -08:00
router_navigation_extras.spec.ts refactor(router): switching to relative imports within the router package (#60557) 2025-04-01 14:54:05 +00:00
router_preloader.spec.ts test(router): move timeout and autoTick helpers to shared testing utilities 2026-02-10 07:45:00 -08:00
router_scroller.spec.ts test(router): move timeout and autoTick helpers to shared testing utilities 2026-02-10 07:45:00 -08:00
router_state.spec.ts refactor: update license text to point to angular.dev (#57901) 2024-09-24 15:33:00 +02:00
shared.spec.ts refactor: update license text to point to angular.dev (#57901) 2024-09-24 15:33:00 +02:00
standalone.spec.ts test(router): move timeout and autoTick helpers to shared testing utilities 2026-02-10 07:45:00 -08:00
trailing_slash_integration.spec.ts feat(common): Add Location strategies to manage trailing slash on write 2026-01-23 20:09:23 +00:00
tsconfig-test.json refactor(router): Intercept navigate events (unless it's a rollback) 2025-11-06 15:00:08 -08:00
url_serializer.spec.ts fix(router): limit UrlParser recursion depth to prevent stack overflow 2026-01-29 12:15:21 -08:00
url_tree.spec.ts build: update Jasmine to 6.0.0 2026-02-09 12:15:57 -08:00
view_transitions.spec.ts test(router): remove provider zoneless from tests 2026-02-02 15:00:18 -08:00
with_platform_navigation.spec.ts build: update rules_browsers digest to ceb5275 2026-02-19 16:01:02 -08:00