Skip to content

Commit

Permalink
test(upgrade): update router upgrade tests to use fewer mocks (#43441)
Browse files Browse the repository at this point in the history
This updates the router upgrade tests to use less mocked behavior. The
test upgrade location module is copied from the one that's used in the
common package. This update to the tests verifies more real behavior of
the upgrade module.

PR Close #43441
  • Loading branch information
atscott authored and thePunderWoman committed Feb 2, 2022
1 parent 64adfc9 commit 123f42c
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 34 deletions.
4 changes: 4 additions & 0 deletions packages/router/upgrade/test/BUILD.bazel
Expand Up @@ -13,9 +13,13 @@ ts_library(
srcs = glob(["**/*.ts"]),
deps = [
"//packages/common",
"//packages/common/testing",
"//packages/common/upgrade",
"//packages/core",
"//packages/core/testing",
"//packages/private/testing",
"//packages/router",
"//packages/router/testing",
"//packages/router/upgrade",
"//packages/upgrade/static",
],
Expand Down
121 changes: 87 additions & 34 deletions packages/router/upgrade/test/upgrade.spec.ts
Expand Up @@ -7,31 +7,87 @@
*/

import {Location} from '@angular/common';
import {TestBed} from '@angular/core/testing';
import {$locationShim, UrlCodec} from '@angular/common/upgrade';
import {fakeAsync, flush, TestBed} from '@angular/core/testing';
import {Router} from '@angular/router';
import {RouterTestingModule} from '@angular/router/testing';
import {setUpLocationSync} from '@angular/router/upgrade';
import {UpgradeModule} from '@angular/upgrade/static';

import {LocationUpgradeTestModule} from './upgrade_location_test_module';

export function injectorFactory() {
const rootScopeMock = new $rootScopeMock();
const rootElementMock = {on: () => undefined};
return function $injectorGet(provider: string) {
if (provider === '$rootScope') {
return rootScopeMock;
} else if (provider === '$rootElement') {
return rootElementMock;
} else {
throw new Error(`Unsupported injectable mock: ${provider}`);
}
};
}

export class $rootScopeMock {
private watchers: any[] = [];
private events: {[k: string]: any[]} = {};

$watch(fn: any) {
this.watchers.push(fn);
}

$broadcast(evt: string, ...args: any[]) {
if (this.events[evt]) {
this.events[evt].forEach(fn => {
fn.apply(fn, [/** angular.IAngularEvent*/ {}, ...args]);
});
}
return {
defaultPrevented: false,
preventDefault() {
this.defaultPrevented = true;
}
};
}

$on(evt: string, fn: any) {
if (!this.events[evt]) {
this.events[evt] = [];
}
this.events[evt].push(fn);
}

$evalAsync(fn: any) {
fn();
}

$digest() {
this.watchers.forEach(fn => fn());
}
}

describe('setUpLocationSync', () => {
let upgradeModule: UpgradeModule;
let RouterMock: any;
let LocationMock: any;
let router: any;
let location: any;

beforeEach(() => {
RouterMock = jasmine.createSpyObj('Router', ['navigateByUrl']);
LocationMock = jasmine.createSpyObj('Location', ['normalize']);

TestBed.configureTestingModule({
providers: [
UpgradeModule, {provide: Router, useValue: RouterMock},
{provide: Location, useValue: LocationMock}
imports: [
RouterTestingModule.withRoutes([{path: '1', children: []}, {path: '2', children: []}]),
UpgradeModule,
LocationUpgradeTestModule.config(),
],
});

upgradeModule = TestBed.inject(UpgradeModule);
upgradeModule.$injector = {
get: jasmine.createSpy('$injector.get').and.returnValue({'$on': () => undefined})
};
router = TestBed.inject(Router);
location = TestBed.inject(Location);
spyOn(router, 'navigateByUrl').and.callThrough();
spyOn(location, 'normalize').and.callThrough();
upgradeModule.$injector = {get: injectorFactory()};
});

it('should throw an error if the UpgradeModule.bootstrap has not been called', () => {
Expand All @@ -45,10 +101,8 @@ describe('setUpLocationSync', () => {

it('should get the $rootScope from AngularJS and set an $on watch on $locationChangeStart',
() => {
const $rootScope = jasmine.createSpyObj('$rootScope', ['$on']);

upgradeModule.$injector.get.and.callFake(
(name: string) => (name === '$rootScope') && $rootScope);
const $rootScope = upgradeModule.$injector.get('$rootScope');
spyOn($rootScope, '$on');

setUpLocationSync(upgradeModule);

Expand All @@ -62,21 +116,21 @@ describe('setUpLocationSync', () => {
const normalizedPathname = 'foo';
const query = '?query=1&query2=3';
const hash = '#new/hash';
const $rootScope = jasmine.createSpyObj('$rootScope', ['$on']);
const $rootScope = upgradeModule.$injector.get('$rootScope');
spyOn($rootScope, '$on');

upgradeModule.$injector.get.and.returnValue($rootScope);
LocationMock.normalize.and.returnValue(normalizedPathname);
location.normalize.and.returnValue(normalizedPathname);

setUpLocationSync(upgradeModule);

const callback = $rootScope.$on.calls.argsFor(0)[1];
callback({}, url + pathname + query + hash, '');

expect(LocationMock.normalize).toHaveBeenCalledTimes(1);
expect(LocationMock.normalize).toHaveBeenCalledWith(pathname);
expect(location.normalize).toHaveBeenCalledTimes(1);
expect(location.normalize).toHaveBeenCalledWith(pathname);

expect(RouterMock.navigateByUrl).toHaveBeenCalledTimes(1);
expect(RouterMock.navigateByUrl).toHaveBeenCalledWith(normalizedPathname + query + hash);
expect(router.navigateByUrl).toHaveBeenCalledTimes(1);
expect(router.navigateByUrl).toHaveBeenCalledWith(normalizedPathname + query + hash);
});

it('should allow configuration to work with hash-based routing', () => {
Expand All @@ -86,21 +140,20 @@ describe('setUpLocationSync', () => {
const query = '?query=1&query2=3';
const hash = '#new/hash';
const combinedUrl = url + '#' + pathname + query + hash;
const $rootScope = jasmine.createSpyObj('$rootScope', ['$on']);

upgradeModule.$injector.get.and.returnValue($rootScope);
LocationMock.normalize.and.returnValue(normalizedPathname);
const $rootScope = upgradeModule.$injector.get('$rootScope');
spyOn($rootScope, '$on');
location.normalize.and.returnValue(normalizedPathname);

setUpLocationSync(upgradeModule, 'hash');

const callback = $rootScope.$on.calls.argsFor(0)[1];
callback({}, combinedUrl, '');

expect(LocationMock.normalize).toHaveBeenCalledTimes(1);
expect(LocationMock.normalize).toHaveBeenCalledWith(pathname);
expect(location.normalize).toHaveBeenCalledTimes(1);
expect(location.normalize).toHaveBeenCalledWith(pathname);

expect(RouterMock.navigateByUrl).toHaveBeenCalledTimes(1);
expect(RouterMock.navigateByUrl).toHaveBeenCalledWith(normalizedPathname + query + hash);
expect(router.navigateByUrl).toHaveBeenCalledTimes(1);
expect(router.navigateByUrl).toHaveBeenCalledWith(normalizedPathname + query + hash);
});

it('should work correctly on browsers that do not start pathname with `/`', () => {
Expand All @@ -109,15 +162,15 @@ describe('setUpLocationSync', () => {
Object.defineProperty(anchorProto, 'pathname', {get: () => 'foo/bar'});

try {
const $rootScope = jasmine.createSpyObj('$rootScope', ['$on']);
upgradeModule.$injector.get.and.returnValue($rootScope);
const $rootScope = upgradeModule.$injector.get('$rootScope');
spyOn($rootScope, '$on');

setUpLocationSync(upgradeModule);

const callback = $rootScope.$on.calls.argsFor(0)[1];
callback({}, '', '');

expect(LocationMock.normalize).toHaveBeenCalledWith('/foo/bar');
expect(location.normalize).toHaveBeenCalledWith('/foo/bar');
} finally {
Object.defineProperty(anchorProto, 'pathname', originalDescriptor!);
}
Expand Down
86 changes: 86 additions & 0 deletions packages/router/upgrade/test/upgrade_location_test_module.ts
@@ -0,0 +1,86 @@
/**
* @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 {APP_BASE_HREF, CommonModule, Location, LocationStrategy, PlatformLocation} from '@angular/common';
import {MockPlatformLocation} from '@angular/common/testing';
import {$locationShim, $locationShimProvider, LocationUpgradeModule, UrlCodec} from '@angular/common/upgrade';
import {Inject, InjectionToken, ModuleWithProviders, NgModule, Optional} from '@angular/core';
import {UpgradeModule} from '@angular/upgrade/static';

export interface LocationUpgradeTestingConfig {
useHash?: boolean;
hashPrefix?: string;
urlCodec?: typeof UrlCodec;
startUrl?: string;
appBaseHref?: string;
}

/**
* @description
*
* Is used in DI to configure the router.
*/
export const LOC_UPGRADE_TEST_CONFIG =
new InjectionToken<LocationUpgradeTestingConfig>('LOC_UPGRADE_TEST_CONFIG');


export const APP_BASE_HREF_RESOLVED = new InjectionToken<string>('APP_BASE_HREF_RESOLVED');

/**
* Module used for configuring Angular's LocationUpgradeService.
*/
@NgModule({imports: [CommonModule]})
export class LocationUpgradeTestModule {
static config(config?: LocationUpgradeTestingConfig):
ModuleWithProviders<LocationUpgradeTestModule> {
return {
ngModule: LocationUpgradeTestModule,
providers: [
{provide: LOC_UPGRADE_TEST_CONFIG, useValue: config || {}}, {
provide: PlatformLocation,
useFactory: (appBaseHref?: string) => {
if (config && config.appBaseHref != null) {
appBaseHref = config.appBaseHref;
} else if (appBaseHref == null) {
appBaseHref = '';
}
return new MockPlatformLocation(
{startUrl: config && config.startUrl, appBaseHref: appBaseHref});
},
deps: [[new Inject(APP_BASE_HREF), new Optional()]]
},
{
provide: $locationShim,
useFactory: provide$location,
deps: [
UpgradeModule, Location, PlatformLocation, UrlCodec, LocationStrategy,
LOC_UPGRADE_TEST_CONFIG
]
},
LocationUpgradeModule
.config({
appBaseHref: config && config.appBaseHref,
useHash: config && config.useHash || false
})
.providers!
],
};
}
}

export function provide$location(
ngUpgrade: UpgradeModule, location: Location, platformLocation: PlatformLocation,
urlCodec: UrlCodec, locationStrategy: LocationStrategy, config?: LocationUpgradeTestingConfig) {
const $locationProvider =
new $locationShimProvider(ngUpgrade, location, platformLocation, urlCodec, locationStrategy);

$locationProvider.hashPrefix(config && config.hashPrefix);
$locationProvider.html5Mode(config && !config.useHash);

return $locationProvider.$get();
}

0 comments on commit 123f42c

Please sign in to comment.