| /** | 
 |  * @license Angular v11.2.14 | 
 |  * (c) 2010-2021 Google LLC. https://angular.io/ | 
 |  * License: MIT | 
 |  */ | 
 |  | 
 | import { EventEmitter, Injectable, InjectionToken, Inject, Optional } from '@angular/core'; | 
 | import { LocationStrategy } from '@angular/common'; | 
 | import { Subject } from 'rxjs'; | 
 |  | 
 | /** | 
 |  * @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 | 
 |  */ | 
 | /** | 
 |  * A spy for {@link Location} that allows tests to fire simulated location events. | 
 |  * | 
 |  * @publicApi | 
 |  */ | 
 | class SpyLocation { | 
 |     constructor() { | 
 |         this.urlChanges = []; | 
 |         this._history = [new LocationState('', '', null)]; | 
 |         this._historyIndex = 0; | 
 |         /** @internal */ | 
 |         this._subject = new EventEmitter(); | 
 |         /** @internal */ | 
 |         this._baseHref = ''; | 
 |         /** @internal */ | 
 |         this._platformStrategy = null; | 
 |         /** @internal */ | 
 |         this._platformLocation = null; | 
 |         /** @internal */ | 
 |         this._urlChangeListeners = []; | 
 |     } | 
 |     setInitialPath(url) { | 
 |         this._history[this._historyIndex].path = url; | 
 |     } | 
 |     setBaseHref(url) { | 
 |         this._baseHref = url; | 
 |     } | 
 |     path() { | 
 |         return this._history[this._historyIndex].path; | 
 |     } | 
 |     getState() { | 
 |         return this._history[this._historyIndex].state; | 
 |     } | 
 |     isCurrentPathEqualTo(path, query = '') { | 
 |         const givenPath = path.endsWith('/') ? path.substring(0, path.length - 1) : path; | 
 |         const currPath = this.path().endsWith('/') ? this.path().substring(0, this.path().length - 1) : this.path(); | 
 |         return currPath == givenPath + (query.length > 0 ? ('?' + query) : ''); | 
 |     } | 
 |     simulateUrlPop(pathname) { | 
 |         this._subject.emit({ 'url': pathname, 'pop': true, 'type': 'popstate' }); | 
 |     } | 
 |     simulateHashChange(pathname) { | 
 |         // Because we don't prevent the native event, the browser will independently update the path | 
 |         this.setInitialPath(pathname); | 
 |         this.urlChanges.push('hash: ' + pathname); | 
 |         this._subject.emit({ 'url': pathname, 'pop': true, 'type': 'hashchange' }); | 
 |     } | 
 |     prepareExternalUrl(url) { | 
 |         if (url.length > 0 && !url.startsWith('/')) { | 
 |             url = '/' + url; | 
 |         } | 
 |         return this._baseHref + url; | 
 |     } | 
 |     go(path, query = '', state = null) { | 
 |         path = this.prepareExternalUrl(path); | 
 |         if (this._historyIndex > 0) { | 
 |             this._history.splice(this._historyIndex + 1); | 
 |         } | 
 |         this._history.push(new LocationState(path, query, state)); | 
 |         this._historyIndex = this._history.length - 1; | 
 |         const locationState = this._history[this._historyIndex - 1]; | 
 |         if (locationState.path == path && locationState.query == query) { | 
 |             return; | 
 |         } | 
 |         const url = path + (query.length > 0 ? ('?' + query) : ''); | 
 |         this.urlChanges.push(url); | 
 |         this._subject.emit({ 'url': url, 'pop': false }); | 
 |     } | 
 |     replaceState(path, query = '', state = null) { | 
 |         path = this.prepareExternalUrl(path); | 
 |         const history = this._history[this._historyIndex]; | 
 |         if (history.path == path && history.query == query) { | 
 |             return; | 
 |         } | 
 |         history.path = path; | 
 |         history.query = query; | 
 |         history.state = state; | 
 |         const url = path + (query.length > 0 ? ('?' + query) : ''); | 
 |         this.urlChanges.push('replace: ' + url); | 
 |     } | 
 |     forward() { | 
 |         if (this._historyIndex < (this._history.length - 1)) { | 
 |             this._historyIndex++; | 
 |             this._subject.emit({ 'url': this.path(), 'state': this.getState(), 'pop': true }); | 
 |         } | 
 |     } | 
 |     back() { | 
 |         if (this._historyIndex > 0) { | 
 |             this._historyIndex--; | 
 |             this._subject.emit({ 'url': this.path(), 'state': this.getState(), 'pop': true }); | 
 |         } | 
 |     } | 
 |     onUrlChange(fn) { | 
 |         this._urlChangeListeners.push(fn); | 
 |         if (!this._urlChangeSubscription) { | 
 |             this._urlChangeSubscription = this.subscribe(v => { | 
 |                 this._notifyUrlChangeListeners(v.url, v.state); | 
 |             }); | 
 |         } | 
 |     } | 
 |     /** @internal */ | 
 |     _notifyUrlChangeListeners(url = '', state) { | 
 |         this._urlChangeListeners.forEach(fn => fn(url, state)); | 
 |     } | 
 |     subscribe(onNext, onThrow, onReturn) { | 
 |         return this._subject.subscribe({ next: onNext, error: onThrow, complete: onReturn }); | 
 |     } | 
 |     normalize(url) { | 
 |         return null; | 
 |     } | 
 | } | 
 | SpyLocation.decorators = [ | 
 |     { type: Injectable } | 
 | ]; | 
 | class LocationState { | 
 |     constructor(path, query, state) { | 
 |         this.path = path; | 
 |         this.query = query; | 
 |         this.state = state; | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  * @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 | 
 |  */ | 
 | /** | 
 |  * A mock implementation of {@link LocationStrategy} that allows tests to fire simulated | 
 |  * location events. | 
 |  * | 
 |  * @publicApi | 
 |  */ | 
 | class MockLocationStrategy extends LocationStrategy { | 
 |     constructor() { | 
 |         super(); | 
 |         this.internalBaseHref = '/'; | 
 |         this.internalPath = '/'; | 
 |         this.internalTitle = ''; | 
 |         this.urlChanges = []; | 
 |         /** @internal */ | 
 |         this._subject = new EventEmitter(); | 
 |         this.stateChanges = []; | 
 |     } | 
 |     simulatePopState(url) { | 
 |         this.internalPath = url; | 
 |         this._subject.emit(new _MockPopStateEvent(this.path())); | 
 |     } | 
 |     path(includeHash = false) { | 
 |         return this.internalPath; | 
 |     } | 
 |     prepareExternalUrl(internal) { | 
 |         if (internal.startsWith('/') && this.internalBaseHref.endsWith('/')) { | 
 |             return this.internalBaseHref + internal.substring(1); | 
 |         } | 
 |         return this.internalBaseHref + internal; | 
 |     } | 
 |     pushState(ctx, title, path, query) { | 
 |         // Add state change to changes array | 
 |         this.stateChanges.push(ctx); | 
 |         this.internalTitle = title; | 
 |         const url = path + (query.length > 0 ? ('?' + query) : ''); | 
 |         this.internalPath = url; | 
 |         const externalUrl = this.prepareExternalUrl(url); | 
 |         this.urlChanges.push(externalUrl); | 
 |     } | 
 |     replaceState(ctx, title, path, query) { | 
 |         // Reset the last index of stateChanges to the ctx (state) object | 
 |         this.stateChanges[(this.stateChanges.length || 1) - 1] = ctx; | 
 |         this.internalTitle = title; | 
 |         const url = path + (query.length > 0 ? ('?' + query) : ''); | 
 |         this.internalPath = url; | 
 |         const externalUrl = this.prepareExternalUrl(url); | 
 |         this.urlChanges.push('replace: ' + externalUrl); | 
 |     } | 
 |     onPopState(fn) { | 
 |         this._subject.subscribe({ next: fn }); | 
 |     } | 
 |     getBaseHref() { | 
 |         return this.internalBaseHref; | 
 |     } | 
 |     back() { | 
 |         if (this.urlChanges.length > 0) { | 
 |             this.urlChanges.pop(); | 
 |             this.stateChanges.pop(); | 
 |             const nextUrl = this.urlChanges.length > 0 ? this.urlChanges[this.urlChanges.length - 1] : ''; | 
 |             this.simulatePopState(nextUrl); | 
 |         } | 
 |     } | 
 |     forward() { | 
 |         throw 'not implemented'; | 
 |     } | 
 |     getState() { | 
 |         return this.stateChanges[(this.stateChanges.length || 1) - 1]; | 
 |     } | 
 | } | 
 | MockLocationStrategy.decorators = [ | 
 |     { type: Injectable } | 
 | ]; | 
 | MockLocationStrategy.ctorParameters = () => []; | 
 | class _MockPopStateEvent { | 
 |     constructor(newUrl) { | 
 |         this.newUrl = newUrl; | 
 |         this.pop = true; | 
 |         this.type = 'popstate'; | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  * @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 | 
 |  */ | 
 | /** | 
 |  * Parser from https://tools.ietf.org/html/rfc3986#appendix-B | 
 |  * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? | 
 |  *  12            3  4          5       6  7        8 9 | 
 |  * | 
 |  * Example: http://www.ics.uci.edu/pub/ietf/uri/#Related | 
 |  * | 
 |  * Results in: | 
 |  * | 
 |  * $1 = http: | 
 |  * $2 = http | 
 |  * $3 = //www.ics.uci.edu | 
 |  * $4 = www.ics.uci.edu | 
 |  * $5 = /pub/ietf/uri/ | 
 |  * $6 = <undefined> | 
 |  * $7 = <undefined> | 
 |  * $8 = #Related | 
 |  * $9 = Related | 
 |  */ | 
 | const urlParse = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; | 
 | function parseUrl(urlStr, baseHref) { | 
 |     const verifyProtocol = /^((http[s]?|ftp):\/\/)/; | 
 |     let serverBase; | 
 |     // URL class requires full URL. If the URL string doesn't start with protocol, we need to add | 
 |     // an arbitrary base URL which can be removed afterward. | 
 |     if (!verifyProtocol.test(urlStr)) { | 
 |         serverBase = 'http://empty.com/'; | 
 |     } | 
 |     let parsedUrl; | 
 |     try { | 
 |         parsedUrl = new URL(urlStr, serverBase); | 
 |     } | 
 |     catch (e) { | 
 |         const result = urlParse.exec(serverBase || '' + urlStr); | 
 |         if (!result) { | 
 |             throw new Error(`Invalid URL: ${urlStr} with base: ${baseHref}`); | 
 |         } | 
 |         const hostSplit = result[4].split(':'); | 
 |         parsedUrl = { | 
 |             protocol: result[1], | 
 |             hostname: hostSplit[0], | 
 |             port: hostSplit[1] || '', | 
 |             pathname: result[5], | 
 |             search: result[6], | 
 |             hash: result[8], | 
 |         }; | 
 |     } | 
 |     if (parsedUrl.pathname && parsedUrl.pathname.indexOf(baseHref) === 0) { | 
 |         parsedUrl.pathname = parsedUrl.pathname.substring(baseHref.length); | 
 |     } | 
 |     return { | 
 |         hostname: !serverBase && parsedUrl.hostname || '', | 
 |         protocol: !serverBase && parsedUrl.protocol || '', | 
 |         port: !serverBase && parsedUrl.port || '', | 
 |         pathname: parsedUrl.pathname || '/', | 
 |         search: parsedUrl.search || '', | 
 |         hash: parsedUrl.hash || '', | 
 |     }; | 
 | } | 
 | /** | 
 |  * Provider for mock platform location config | 
 |  * | 
 |  * @publicApi | 
 |  */ | 
 | const MOCK_PLATFORM_LOCATION_CONFIG = new InjectionToken('MOCK_PLATFORM_LOCATION_CONFIG'); | 
 | /** | 
 |  * Mock implementation of URL state. | 
 |  * | 
 |  * @publicApi | 
 |  */ | 
 | class MockPlatformLocation { | 
 |     constructor(config) { | 
 |         this.baseHref = ''; | 
 |         this.hashUpdate = new Subject(); | 
 |         this.urlChanges = [{ hostname: '', protocol: '', port: '', pathname: '/', search: '', hash: '', state: null }]; | 
 |         if (config) { | 
 |             this.baseHref = config.appBaseHref || ''; | 
 |             const parsedChanges = this.parseChanges(null, config.startUrl || 'http://<empty>/', this.baseHref); | 
 |             this.urlChanges[0] = Object.assign({}, parsedChanges); | 
 |         } | 
 |     } | 
 |     get hostname() { | 
 |         return this.urlChanges[0].hostname; | 
 |     } | 
 |     get protocol() { | 
 |         return this.urlChanges[0].protocol; | 
 |     } | 
 |     get port() { | 
 |         return this.urlChanges[0].port; | 
 |     } | 
 |     get pathname() { | 
 |         return this.urlChanges[0].pathname; | 
 |     } | 
 |     get search() { | 
 |         return this.urlChanges[0].search; | 
 |     } | 
 |     get hash() { | 
 |         return this.urlChanges[0].hash; | 
 |     } | 
 |     get state() { | 
 |         return this.urlChanges[0].state; | 
 |     } | 
 |     getBaseHrefFromDOM() { | 
 |         return this.baseHref; | 
 |     } | 
 |     onPopState(fn) { | 
 |         // No-op: a state stack is not implemented, so | 
 |         // no events will ever come. | 
 |     } | 
 |     onHashChange(fn) { | 
 |         this.hashUpdate.subscribe(fn); | 
 |     } | 
 |     get href() { | 
 |         let url = `${this.protocol}//${this.hostname}${this.port ? ':' + this.port : ''}`; | 
 |         url += `${this.pathname === '/' ? '' : this.pathname}${this.search}${this.hash}`; | 
 |         return url; | 
 |     } | 
 |     get url() { | 
 |         return `${this.pathname}${this.search}${this.hash}`; | 
 |     } | 
 |     parseChanges(state, url, baseHref = '') { | 
 |         // When the `history.state` value is stored, it is always copied. | 
 |         state = JSON.parse(JSON.stringify(state)); | 
 |         return Object.assign(Object.assign({}, parseUrl(url, baseHref)), { state }); | 
 |     } | 
 |     replaceState(state, title, newUrl) { | 
 |         const { pathname, search, state: parsedState, hash } = this.parseChanges(state, newUrl); | 
 |         this.urlChanges[0] = Object.assign(Object.assign({}, this.urlChanges[0]), { pathname, search, hash, state: parsedState }); | 
 |     } | 
 |     pushState(state, title, newUrl) { | 
 |         const { pathname, search, state: parsedState, hash } = this.parseChanges(state, newUrl); | 
 |         this.urlChanges.unshift(Object.assign(Object.assign({}, this.urlChanges[0]), { pathname, search, hash, state: parsedState })); | 
 |     } | 
 |     forward() { | 
 |         throw new Error('Not implemented'); | 
 |     } | 
 |     back() { | 
 |         const oldUrl = this.url; | 
 |         const oldHash = this.hash; | 
 |         this.urlChanges.shift(); | 
 |         const newHash = this.hash; | 
 |         if (oldHash !== newHash) { | 
 |             scheduleMicroTask(() => this.hashUpdate.next({ type: 'hashchange', state: null, oldUrl, newUrl: this.url })); | 
 |         } | 
 |     } | 
 |     getState() { | 
 |         return this.state; | 
 |     } | 
 | } | 
 | MockPlatformLocation.decorators = [ | 
 |     { type: Injectable } | 
 | ]; | 
 | MockPlatformLocation.ctorParameters = () => [ | 
 |     { type: undefined, decorators: [{ type: Inject, args: [MOCK_PLATFORM_LOCATION_CONFIG,] }, { type: Optional }] } | 
 | ]; | 
 | function scheduleMicroTask(cb) { | 
 |     Promise.resolve(null).then(cb); | 
 | } | 
 |  | 
 | /** | 
 |  * @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 | 
 |  */ | 
 |  | 
 | /** | 
 |  * @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 | 
 |  */ | 
 | // This file only reexports content of the `src` folder. Keep it that way. | 
 |  | 
 | /** | 
 |  * @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 | 
 |  */ | 
 |  | 
 | /** | 
 |  * Generated bundle index. Do not edit. | 
 |  */ | 
 |  | 
 | export { MOCK_PLATFORM_LOCATION_CONFIG, MockLocationStrategy, MockPlatformLocation, SpyLocation }; | 
 | //# sourceMappingURL=testing.js.map |