angular/packages/router/test/operators/resolve_data.spec.ts
Dmitrij Kuba c9679760b2 refactor(router): take only the first emitted value of every resolver to make it consistent with guards (#44573)
The router used to wait for the resolvers to complete and take the last
value. The changes here take only the first
emitted value of every resolver and proceed the navigation. This matches
how other guards work in the `Router` code.

Resolves https://github.com/angular/angular/issues/44643

BREAKING CHANGE: Previously, resolvers were waiting to be completed
before proceeding with the navigation and the Router would take the last
value emitted from the resolver.
The router now takes only the first emitted value by the resolvers
and then proceeds with navigation. This is now consistent with `Observables`
returned by other guards: only the first value is used.

PR Close #44573
2022-03-01 17:12:37 +00:00

117 lines
4.1 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 {Injector} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {EMPTY, interval, of} from 'rxjs';
import {TestScheduler} from 'rxjs/testing';
import {resolveData} from '../../src/operators/resolve_data';
describe('resolveData operator', () => {
let testScheduler: TestScheduler;
let injector: Injector;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{provide: 'resolveTwo', useValue: (a: any, b: any) => of(2)},
{provide: 'resolveFour', useValue: (a: any, b: any) => 4},
{provide: 'resolveEmpty', useValue: (a: any, b: any) => EMPTY},
{provide: 'resolveInterval', useValue: (a: any, b: any) => interval()},
]
});
});
beforeEach(() => {
testScheduler = new TestScheduler(assertDeepEquals);
});
beforeEach(() => {
injector = TestBed.inject<Injector>(Injector);
});
it('should re-emit updated value from source after all resolvers emit and complete', () => {
testScheduler.run(({hot, cold, expectObservable}) => {
const transition: any = createTransition({e1: 'resolveTwo'}, {e2: 'resolveFour'});
const source = cold('-(t|)', {t: deepClone(transition)});
const expected = '-(t|)';
const outputTransition = deepClone(transition);
outputTransition.guards.canActivateChecks[0].route._resolvedData = {e1: 2};
outputTransition.guards.canActivateChecks[1].route._resolvedData = {e2: 4};
expectObservable(source.pipe(resolveData('emptyOnly', injector))).toBe(expected, {
t: outputTransition
});
});
});
it('should take only the first emitted value of every resolver', () => {
testScheduler.run(({cold, expectObservable}) => {
const transition: any = createTransition({e1: 'resolveInterval'});
const source = cold('-(t|)', {t: deepClone(transition)});
const expected = '-(t|)';
const outputTransition = deepClone(transition);
outputTransition.guards.canActivateChecks[0].route._resolvedData = {e1: 0};
expectObservable(source.pipe(resolveData('emptyOnly', injector))).toBe(expected, {
t: outputTransition
});
});
});
it('should re-emit value from source when there are no resolvers', () => {
testScheduler.run(({hot, cold, expectObservable}) => {
const transition: any = createTransition({});
const source = cold('-(t|)', {t: deepClone(transition)});
const expected = '-(t|)';
const outputTransition = deepClone(transition);
outputTransition.guards.canActivateChecks[0].route._resolvedData = {};
expectObservable(source.pipe(resolveData('emptyOnly', injector))).toBe(expected, {
t: outputTransition
});
});
});
it('should not emit when there\'s one resolver that doesn\'t emit', () => {
testScheduler.run(({hot, cold, expectObservable}) => {
const transition: any = createTransition({e2: 'resolveEmpty'});
const source = cold('-(t|)', {t: deepClone(transition)});
const expected = '-|';
expectObservable(source.pipe(resolveData('emptyOnly', injector))).toBe(expected);
});
});
it('should not emit if at least one resolver doesn\'t emit', () => {
testScheduler.run(({hot, cold, expectObservable}) => {
const transition: any = createTransition({e1: 'resolveTwo'}, {e2: 'resolveEmpty'});
const source = cold('-(t|)', {t: deepClone(transition)});
const expected = '-|';
expectObservable(source.pipe(resolveData('emptyOnly', injector))).toBe(expected);
});
});
});
function assertDeepEquals(a: any, b: any) {
return expect(a).toEqual(b);
}
function createTransition(...resolvers: {[key: string]: string}[]) {
return {
targetSnapshot: {},
guards: {
canActivateChecks:
resolvers.map(resolver => ({
route: {_resolve: resolver, pathFromRoot: [{url: '/'}], data: {}},
})),
},
};
}
function deepClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj)) as T;
}