angular/packages/router/test/operators/resolve_data.spec.ts
Andrew Scott 3278966068 fix(router): Ensure newly resolved data is inherited by child routes (#52167)
The current way of computing a route's params and data recomputes
inherited data from the inheritance root every time. When the
inheritance strategy is "emptyOnly", this isn't necessarily the root of
the tree, but some point along the way (it stops once it reaches an
ancestor route with a component).

Instead, this commit updates parameter inheritance to only inherit data
directly from the parent route (again, instead of recomputing all
inherited data back to the inheritance root). The only requirement for
making this work is that the parent route data has already calculated
and updated its own inherited data. This was really already a
requirement -- parents need to be processed before children.

In addition, the update to the inheritance algorithm in this commit
requires more of an understanding that a resolver running higher up in
the tree has to propagate inherited data downwards. The previous
algorithm hid this knowledge because resolvers would recompute inherited
data from the root when run. However, routes that did not have resolvers
rerun or never had resolvers at all would not get the updated resolved data.

fixes #51934

PR Close #52167
2023-10-19 10:26:27 -07:00

138 lines
4.8 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {provideRouter, Router} from '@angular/router';
import {RouterTestingHarness} from '@angular/router/testing';
import {EMPTY, interval, NEVER, of} from 'rxjs';
describe('resolveData operator', () => {
it('should take only the first emitted value of every resolver', async () => {
TestBed.configureTestingModule({
providers: [provideRouter([{path: '**', children: [], resolve: {e1: () => interval()}}])]
});
await RouterTestingHarness.create('/');
expect(TestBed.inject(Router).routerState.root.firstChild?.snapshot.data).toEqual({e1: 0});
});
it('should cancel navigation if a resolver does not complete', async () => {
TestBed.configureTestingModule(
{providers: [provideRouter([{path: '**', children: [], resolve: {e1: () => EMPTY}}])]});
await RouterTestingHarness.create('/a');
expect(TestBed.inject(Router).url).toEqual('/');
});
it('should cancel navigation if 1 of 2 resolvers does not emit', async () => {
TestBed.configureTestingModule({
providers:
[provideRouter([{path: '**', children: [], resolve: {e0: () => of(0), e1: () => EMPTY}}])]
});
await RouterTestingHarness.create('/a');
expect(TestBed.inject(Router).url).toEqual('/');
});
it('should complete instantly if at least one resolver doesn\'t emit', async () => {
TestBed.configureTestingModule({
providers:
[provideRouter([{path: '**', children: [], resolve: {e0: () => EMPTY, e1: () => NEVER}}])]
});
await RouterTestingHarness.create('/a');
expect(TestBed.inject(Router).url).toEqual('/');
});
it('should update children inherited data when resolvers run', async () => {
let value = 0;
TestBed.configureTestingModule({
providers: [provideRouter([{
path: 'a',
children: [{path: 'b', children: []}],
resolve: {d0: () => ++value},
runGuardsAndResolvers: 'always',
}])]
});
const harness = await RouterTestingHarness.create('/a/b');
expect(TestBed.inject(Router).routerState.root.firstChild?.snapshot.data).toEqual({d0: 1});
expect(TestBed.inject(Router).routerState.root.firstChild?.firstChild?.snapshot.data).toEqual({
d0: 1
});
await harness.navigateByUrl('/a/b#new');
expect(TestBed.inject(Router).routerState.root.firstChild?.snapshot.data).toEqual({d0: 2});
expect(TestBed.inject(Router).routerState.root.firstChild?.firstChild?.snapshot.data).toEqual({
d0: 2
});
});
it('should have correct data when parent resolver runs but data is not inherited', async () => {
@Component({template: ''})
class Empty {
}
TestBed.configureTestingModule({
providers: [provideRouter([{
path: 'a',
component: Empty,
data: {parent: 'parent'},
resolve: {other: () => 'other'},
children: [{
path: 'b',
data: {child: 'child'},
component: Empty,
}]
}])]
});
await RouterTestingHarness.create('/a/b');
const rootSnapshot = TestBed.inject(Router).routerState.root.firstChild!.snapshot;
expect(rootSnapshot.data).toEqual({parent: 'parent', other: 'other'});
expect(rootSnapshot.firstChild!.data).toEqual({child: 'child'});
});
it('should have static title when there is a resolver', async () => {
@Component({template: ''})
class Empty {
}
TestBed.configureTestingModule({
providers: [provideRouter([{
path: 'a',
title: 'a title',
component: Empty,
resolve: {other: () => 'other'},
children: [{
path: 'b',
title: 'b title',
component: Empty,
resolve: {otherb: () => 'other b'},
}]
}])]
});
await RouterTestingHarness.create('/a/b');
const rootSnapshot = TestBed.inject(Router).routerState.root.firstChild!.snapshot;
expect(rootSnapshot.title).toBe('a title');
expect(rootSnapshot.firstChild!.title).toBe('b title');
});
it('should inherit resolved data from parent of parent route', async () => {
@Component({template: ''})
class Empty {
}
TestBed.configureTestingModule({
providers: [provideRouter([{
path: 'a',
resolve: {aResolve: () => 'a'},
children:
[{path: 'b', resolve: {bResolve: () => 'b'}, children: [{path: 'c', component: Empty}]}]
}])]
});
await RouterTestingHarness.create('/a/b/c');
const rootSnapshot = TestBed.inject(Router).routerState.root.firstChild!.snapshot;
expect(rootSnapshot.firstChild!.firstChild!.data).toEqual({bResolve: 'b', aResolve: 'a'});
});
});