angular/packages/router/test/operators/resolve_data.spec.ts
Andrew Scott 7c12cb1df9 feat(router): Allow resolvers to read resolved data from ancestors (#59860)
This commit updates the resolver execution to ensure that resolvers in
children routes are able to read the resolved data from anything above
them in the route tree. Because resolvers on one level block execution
of those below, it seems more of an oversight in the initial
implementation than anything else that this wasn't already possible.

resolves #47287

PR Close #59860
2025-02-25 12:34:40 -05:00

248 lines
7.7 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.dev/license
*/
import {Component} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {ActivatedRouteSnapshot, 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 run resolvers in different parts of the tree', async () => {
let value = 0;
let bValue = 0;
TestBed.configureTestingModule({
providers: [
provideRouter([
{
path: 'a',
runGuardsAndResolvers: () => false,
children: [
{
path: '',
resolve: {d0: () => ++value},
runGuardsAndResolvers: 'always',
children: [],
},
],
},
{
path: 'b',
outlet: 'aux',
runGuardsAndResolvers: () => false,
children: [
{
path: '',
resolve: {d1: () => ++bValue},
runGuardsAndResolvers: 'always',
children: [],
},
],
},
]),
],
});
const router = TestBed.inject(Router);
const harness = await RouterTestingHarness.create('/a(aux:b)');
expect(router.routerState.root.children[0]?.firstChild?.snapshot.data).toEqual({d0: 1});
expect(router.routerState.root.children[1]?.firstChild?.snapshot.data).toEqual({d1: 1});
await harness.navigateByUrl('/a(aux:b)#new');
expect(router.routerState.root.children[0]?.firstChild?.snapshot.data).toEqual({d0: 2});
expect(router.routerState.root.children[1]?.firstChild?.snapshot.data).toEqual({d1: 2});
});
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: '',
standalone: false,
})
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: '',
standalone: false,
})
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('can used parent data in child resolver', async () => {
@Component({
template: '',
})
class Empty {}
TestBed.configureTestingModule({
providers: [
provideRouter([
{
path: 'a',
resolve: {
aResolve: () => new Promise<string>((resolve) => setTimeout(() => resolve('a'), 5)),
},
children: [
{
path: 'b',
resolve: {
bResolve: (route: ActivatedRouteSnapshot) => route.data['aResolve'] + '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: 'ab', aResolve: 'a'});
});
it('should inherit resolved data from parent of parent route', async () => {
@Component({
template: '',
standalone: false,
})
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'});
});
});