Files
angular__angular/packages/router/testing/test/router_testing_harness.spec.ts
Andrew Scott 50d7916278 feat(router): Add router configuration to resolve navigation promise on error (#48910)
With the deprecation of the configurable errorHandler in the Router, there is a missing
use-case to prevent the navigation promise from rejecting on an error. This rejection
results in unhandled promise rejections. This commit allows developers to instruct
the router to instead resolve the navigation promise with 'false', which matches
the behavior of other failed navigations.

Resolving the Promise would be the ideal default behavior. It is rare
that any code handles the navigation Promise at all and even more rare
that the Promise rejection is caught. Updating the default value for
this option should be considered for an upcoming major version.

fixes #48902

PR Close #48910
2023-12-04 21:49:35 -08:00

148 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 {AsyncPipe} from '@angular/common';
import {Component, inject} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {ActivatedRoute, provideRouter, Router} from '@angular/router';
import {RouterTestingHarness} from '@angular/router/testing';
import {of} from 'rxjs';
import {delay} from 'rxjs/operators';
import {withRouterConfig} from '../../src/provide_router';
describe('navigateForTest', () => {
it('gives null for the activatedComponent when no routes are configured', async () => {
TestBed.configureTestingModule({providers: [provideRouter([])]});
const harness = await RouterTestingHarness.create('/');
expect(harness.routeDebugElement).toBeNull();
});
it('navigates to routed component', async () => {
@Component({standalone: true, template: 'hello {{name}}'})
class TestCmp {
name = 'world';
}
TestBed.configureTestingModule({providers: [provideRouter([{path: '', component: TestCmp}])]});
const harness = await RouterTestingHarness.create();
const activatedComponent = await harness.navigateByUrl('/', TestCmp);
expect(activatedComponent).toBeInstanceOf(TestCmp);
expect(harness.routeNativeElement?.innerHTML).toContain('hello world');
});
it('executes guards on the path', async () => {
let guardCalled = false;
TestBed.configureTestingModule({
providers: [provideRouter([{
path: '',
canActivate: [() => {
guardCalled = true;
return true;
}],
children: []
}])]
});
await RouterTestingHarness.create('/');
expect(guardCalled).toBeTrue();
});
it('throws error if routing throws', async () => {
TestBed.configureTestingModule({
providers: [provideRouter(
[
{
path: 'e',
canActivate: [() => {
throw new Error('oh no');
}],
children: []
},
],
withRouterConfig({resolveNavigationPromiseOnError: true}))]
});
const harness = await RouterTestingHarness.create();
await expectAsync(harness.navigateByUrl('e')).toBeResolvedTo(null);
});
it('can observe param changes on routed component with second navigation', async () => {
@Component({standalone: true, template: '{{(route.params | async)?.id}}', imports: [AsyncPipe]})
class TestCmp {
constructor(readonly route: ActivatedRoute) {}
}
TestBed.configureTestingModule({
providers: [
provideRouter([{path: ':id', component: TestCmp}]),
]
});
const harness = await RouterTestingHarness.create();
const activatedComponent = await harness.navigateByUrl('/123', TestCmp);
expect(activatedComponent.route).toBeInstanceOf(ActivatedRoute);
expect(harness.routeNativeElement?.innerHTML).toContain('123');
await harness.navigateByUrl('/456');
expect(harness.routeNativeElement?.innerHTML).toContain('456');
});
it('throws an error if the routed component instance does not match the one required',
async () => {
@Component({standalone: true, template: ''})
class TestCmp {
}
@Component({standalone: true, template: ''})
class OtherCmp {
}
TestBed.configureTestingModule({
providers: [
provideRouter([{path: '**', component: TestCmp}]),
]
});
const harness = await RouterTestingHarness.create();
await expectAsync(harness.navigateByUrl('/123', OtherCmp)).toBeRejected();
});
it('throws an error if navigation fails but expected a component instance', async () => {
@Component({standalone: true, template: ''})
class TestCmp {
}
TestBed.configureTestingModule({
providers: [
provideRouter([{path: '**', canActivate: [() => false], component: TestCmp}]),
]
});
const harness = await RouterTestingHarness.create();
await expectAsync(harness.navigateByUrl('/123', TestCmp)).toBeRejected();
});
it('waits for redirects using router.navigate', async () => {
@Component({standalone: true, template: 'test'})
class TestCmp {
}
@Component({standalone: true, template: 'redirect'})
class OtherCmp {
}
TestBed.configureTestingModule({
providers: [
provideRouter([
{
path: 'test',
canActivate: [() => inject(Router).navigateByUrl('/redirect')],
component: TestCmp
},
{path: 'redirect', canActivate: [() => of(true).pipe(delay(100))], component: OtherCmp},
]),
]
});
await RouterTestingHarness.create('test');
expect(TestBed.inject(Router).url).toEqual('/redirect');
});
});