| /** |
| * @license Angular v11.2.14 |
| * (c) 2010-2021 Google LLC. https://angular.io/ |
| * License: MIT |
| */ |
| |
| import { getDebugNode, RendererFactory2, ɵstringify, ɵReflectionCapabilities, Directive, Component, Pipe, NgModule, ɵgetInjectableDef, ɵNG_COMP_DEF, ɵRender3NgModuleRef, ApplicationInitStatus, LOCALE_ID, ɵDEFAULT_LOCALE_ID, ɵsetLocaleId, ɵRender3ComponentFactory, ɵcompileComponent, ɵNG_DIR_DEF, ɵcompileDirective, ɵNG_PIPE_DEF, ɵcompilePipe, ɵNG_MOD_DEF, ɵtransitiveScopesFor, ɵpatchComponentDefWithScope, ɵNG_INJ_DEF, ɵcompileNgModuleDefs, NgZone, Compiler, COMPILER_OPTIONS, ɵNgModuleFactory, ModuleWithComponentFactories, InjectionToken, Injector, InjectFlags, ɵresetCompiledComponents, ɵflushModuleScopingQueueAsMuchAsPossible, Injectable, ɵclearOverrides, ɵoverrideComponentView, ɵINJECTOR_SCOPE, Optional, SkipSelf, ɵoverrideProvider, ɵivyEnabled } from '@angular/core'; |
| import { __awaiter } from 'tslib'; |
| import { ResourceLoader } from '@angular/compiler'; |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Wraps a test function in an asynchronous test zone. The test will automatically |
| * complete when all asynchronous calls within this zone are done. Can be used |
| * to wrap an {@link inject} call. |
| * |
| * Example: |
| * |
| * ``` |
| * it('...', waitForAsync(inject([AClass], (object) => { |
| * object.doSomething.then(() => { |
| * expect(...); |
| * }) |
| * }); |
| * ``` |
| * |
| * @publicApi |
| */ |
| function waitForAsync(fn) { |
| const _Zone = typeof Zone !== 'undefined' ? Zone : null; |
| if (!_Zone) { |
| return function () { |
| return Promise.reject('Zone is needed for the waitForAsync() test helper but could not be found. ' + |
| 'Please make sure that your environment includes zone.js/dist/zone.js'); |
| }; |
| } |
| const asyncTest = _Zone && _Zone[_Zone.__symbol__('asyncTest')]; |
| if (typeof asyncTest === 'function') { |
| return asyncTest(fn); |
| } |
| return function () { |
| return Promise.reject('zone-testing.js is needed for the async() test helper but could not be found. ' + |
| 'Please make sure that your environment includes zone.js/dist/zone-testing.js'); |
| }; |
| } |
| /** |
| * @deprecated use `waitForAsync()`, (expected removal in v12) |
| * @see {@link waitForAsync} |
| * @publicApi |
| * */ |
| function async(fn) { |
| return waitForAsync(fn); |
| } |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Fixture for debugging and testing a component. |
| * |
| * @publicApi |
| */ |
| class ComponentFixture { |
| constructor(componentRef, ngZone, _autoDetect) { |
| this.componentRef = componentRef; |
| this.ngZone = ngZone; |
| this._autoDetect = _autoDetect; |
| this._isStable = true; |
| this._isDestroyed = false; |
| this._resolve = null; |
| this._promise = null; |
| this._onUnstableSubscription = null; |
| this._onStableSubscription = null; |
| this._onMicrotaskEmptySubscription = null; |
| this._onErrorSubscription = null; |
| this.changeDetectorRef = componentRef.changeDetectorRef; |
| this.elementRef = componentRef.location; |
| this.debugElement = getDebugNode(this.elementRef.nativeElement); |
| this.componentInstance = componentRef.instance; |
| this.nativeElement = this.elementRef.nativeElement; |
| this.componentRef = componentRef; |
| this.ngZone = ngZone; |
| if (ngZone) { |
| // Create subscriptions outside the NgZone so that the callbacks run oustide |
| // of NgZone. |
| ngZone.runOutsideAngular(() => { |
| this._onUnstableSubscription = ngZone.onUnstable.subscribe({ |
| next: () => { |
| this._isStable = false; |
| } |
| }); |
| this._onMicrotaskEmptySubscription = ngZone.onMicrotaskEmpty.subscribe({ |
| next: () => { |
| if (this._autoDetect) { |
| // Do a change detection run with checkNoChanges set to true to check |
| // there are no changes on the second run. |
| this.detectChanges(true); |
| } |
| } |
| }); |
| this._onStableSubscription = ngZone.onStable.subscribe({ |
| next: () => { |
| this._isStable = true; |
| // Check whether there is a pending whenStable() completer to resolve. |
| if (this._promise !== null) { |
| // If so check whether there are no pending macrotasks before resolving. |
| // Do this check in the next tick so that ngZone gets a chance to update the state of |
| // pending macrotasks. |
| scheduleMicroTask(() => { |
| if (!ngZone.hasPendingMacrotasks) { |
| if (this._promise !== null) { |
| this._resolve(true); |
| this._resolve = null; |
| this._promise = null; |
| } |
| } |
| }); |
| } |
| } |
| }); |
| this._onErrorSubscription = ngZone.onError.subscribe({ |
| next: (error) => { |
| throw error; |
| } |
| }); |
| }); |
| } |
| } |
| _tick(checkNoChanges) { |
| this.changeDetectorRef.detectChanges(); |
| if (checkNoChanges) { |
| this.checkNoChanges(); |
| } |
| } |
| /** |
| * Trigger a change detection cycle for the component. |
| */ |
| detectChanges(checkNoChanges = true) { |
| if (this.ngZone != null) { |
| // Run the change detection inside the NgZone so that any async tasks as part of the change |
| // detection are captured by the zone and can be waited for in isStable. |
| this.ngZone.run(() => { |
| this._tick(checkNoChanges); |
| }); |
| } |
| else { |
| // Running without zone. Just do the change detection. |
| this._tick(checkNoChanges); |
| } |
| } |
| /** |
| * Do a change detection run to make sure there were no changes. |
| */ |
| checkNoChanges() { |
| this.changeDetectorRef.checkNoChanges(); |
| } |
| /** |
| * Set whether the fixture should autodetect changes. |
| * |
| * Also runs detectChanges once so that any existing change is detected. |
| */ |
| autoDetectChanges(autoDetect = true) { |
| if (this.ngZone == null) { |
| throw new Error('Cannot call autoDetectChanges when ComponentFixtureNoNgZone is set'); |
| } |
| this._autoDetect = autoDetect; |
| this.detectChanges(); |
| } |
| /** |
| * Return whether the fixture is currently stable or has async tasks that have not been completed |
| * yet. |
| */ |
| isStable() { |
| return this._isStable && !this.ngZone.hasPendingMacrotasks; |
| } |
| /** |
| * Get a promise that resolves when the fixture is stable. |
| * |
| * This can be used to resume testing after events have triggered asynchronous activity or |
| * asynchronous change detection. |
| */ |
| whenStable() { |
| if (this.isStable()) { |
| return Promise.resolve(false); |
| } |
| else if (this._promise !== null) { |
| return this._promise; |
| } |
| else { |
| this._promise = new Promise(res => { |
| this._resolve = res; |
| }); |
| return this._promise; |
| } |
| } |
| _getRenderer() { |
| if (this._renderer === undefined) { |
| this._renderer = this.componentRef.injector.get(RendererFactory2, null); |
| } |
| return this._renderer; |
| } |
| /** |
| * Get a promise that resolves when the ui state is stable following animations. |
| */ |
| whenRenderingDone() { |
| const renderer = this._getRenderer(); |
| if (renderer && renderer.whenRenderingDone) { |
| return renderer.whenRenderingDone(); |
| } |
| return this.whenStable(); |
| } |
| /** |
| * Trigger component destruction. |
| */ |
| destroy() { |
| if (!this._isDestroyed) { |
| this.componentRef.destroy(); |
| if (this._onUnstableSubscription != null) { |
| this._onUnstableSubscription.unsubscribe(); |
| this._onUnstableSubscription = null; |
| } |
| if (this._onStableSubscription != null) { |
| this._onStableSubscription.unsubscribe(); |
| this._onStableSubscription = null; |
| } |
| if (this._onMicrotaskEmptySubscription != null) { |
| this._onMicrotaskEmptySubscription.unsubscribe(); |
| this._onMicrotaskEmptySubscription = null; |
| } |
| if (this._onErrorSubscription != null) { |
| this._onErrorSubscription.unsubscribe(); |
| this._onErrorSubscription = null; |
| } |
| this._isDestroyed = true; |
| } |
| } |
| } |
| function scheduleMicroTask(fn) { |
| Zone.current.scheduleMicroTask('scheduleMicrotask', fn); |
| } |
| |
| /** |
| * @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 |
| */ |
| const _Zone = typeof Zone !== 'undefined' ? Zone : null; |
| const fakeAsyncTestModule = _Zone && _Zone[_Zone.__symbol__('fakeAsyncTest')]; |
| const fakeAsyncTestModuleNotLoadedErrorMessage = `zone-testing.js is needed for the fakeAsync() test helper but could not be found. |
| Please make sure that your environment includes zone.js/dist/zone-testing.js`; |
| /** |
| * Clears out the shared fake async zone for a test. |
| * To be called in a global `beforeEach`. |
| * |
| * @publicApi |
| */ |
| function resetFakeAsyncZone() { |
| if (fakeAsyncTestModule) { |
| return fakeAsyncTestModule.resetFakeAsyncZone(); |
| } |
| throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage); |
| } |
| /** |
| * Wraps a function to be executed in the fakeAsync zone: |
| * - microtasks are manually executed by calling `flushMicrotasks()`, |
| * - timers are synchronous, `tick()` simulates the asynchronous passage of time. |
| * |
| * If there are any pending timers at the end of the function, an exception will be thrown. |
| * |
| * Can be used to wrap inject() calls. |
| * |
| * @usageNotes |
| * ### Example |
| * |
| * {@example core/testing/ts/fake_async.ts region='basic'} |
| * |
| * @param fn |
| * @returns The function wrapped to be executed in the fakeAsync zone |
| * |
| * @publicApi |
| */ |
| function fakeAsync(fn) { |
| if (fakeAsyncTestModule) { |
| return fakeAsyncTestModule.fakeAsync(fn); |
| } |
| throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage); |
| } |
| /** |
| * Simulates the asynchronous passage of time for the timers in the fakeAsync zone. |
| * |
| * The microtasks queue is drained at the very start of this function and after any timer callback |
| * has been executed. |
| * |
| * @usageNotes |
| * ### Example |
| * |
| * {@example core/testing/ts/fake_async.ts region='basic'} |
| * |
| * @param millis, the number of millisecond to advance the virtual timer |
| * @param tickOptions, the options of tick with a flag called |
| * processNewMacroTasksSynchronously, whether to invoke the new macroTasks, by default is |
| * false, means the new macroTasks will be invoked |
| * |
| * For example, |
| * |
| * it ('test with nested setTimeout', fakeAsync(() => { |
| * let nestedTimeoutInvoked = false; |
| * function funcWithNestedTimeout() { |
| * setTimeout(() => { |
| * nestedTimeoutInvoked = true; |
| * }); |
| * }; |
| * setTimeout(funcWithNestedTimeout); |
| * tick(); |
| * expect(nestedTimeoutInvoked).toBe(true); |
| * })); |
| * |
| * in this case, we have a nested timeout (new macroTask), when we tick, both the |
| * funcWithNestedTimeout and the nested timeout both will be invoked. |
| * |
| * it ('test with nested setTimeout', fakeAsync(() => { |
| * let nestedTimeoutInvoked = false; |
| * function funcWithNestedTimeout() { |
| * setTimeout(() => { |
| * nestedTimeoutInvoked = true; |
| * }); |
| * }; |
| * setTimeout(funcWithNestedTimeout); |
| * tick(0, {processNewMacroTasksSynchronously: false}); |
| * expect(nestedTimeoutInvoked).toBe(false); |
| * })); |
| * |
| * if we pass the tickOptions with processNewMacroTasksSynchronously to be false, the nested timeout |
| * will not be invoked. |
| * |
| * |
| * @publicApi |
| */ |
| function tick(millis = 0, tickOptions = { |
| processNewMacroTasksSynchronously: true |
| }) { |
| if (fakeAsyncTestModule) { |
| return fakeAsyncTestModule.tick(millis, tickOptions); |
| } |
| throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage); |
| } |
| /** |
| * Simulates the asynchronous passage of time for the timers in the fakeAsync zone by |
| * draining the macrotask queue until it is empty. The returned value is the milliseconds |
| * of time that would have been elapsed. |
| * |
| * @param maxTurns |
| * @returns The simulated time elapsed, in millis. |
| * |
| * @publicApi |
| */ |
| function flush(maxTurns) { |
| if (fakeAsyncTestModule) { |
| return fakeAsyncTestModule.flush(maxTurns); |
| } |
| throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage); |
| } |
| /** |
| * Discard all remaining periodic tasks. |
| * |
| * @publicApi |
| */ |
| function discardPeriodicTasks() { |
| if (fakeAsyncTestModule) { |
| return fakeAsyncTestModule.discardPeriodicTasks(); |
| } |
| throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage); |
| } |
| /** |
| * Flush any pending microtasks. |
| * |
| * @publicApi |
| */ |
| function flushMicrotasks() { |
| if (fakeAsyncTestModule) { |
| return fakeAsyncTestModule.flushMicrotasks(); |
| } |
| throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage); |
| } |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Injectable completer that allows signaling completion of an asynchronous test. Used internally. |
| */ |
| class AsyncTestCompleter { |
| constructor() { |
| this._promise = new Promise((res, rej) => { |
| this._resolve = res; |
| this._reject = rej; |
| }); |
| } |
| done(value) { |
| this._resolve(value); |
| } |
| fail(error, stackTrace) { |
| this._reject(error); |
| } |
| get promise() { |
| return this._promise; |
| } |
| } |
| |
| /** |
| * @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 |
| */ |
| /** |
| * Used to resolve resource URLs on `@Component` when used with JIT compilation. |
| * |
| * Example: |
| * ``` |
| * @Component({ |
| * selector: 'my-comp', |
| * templateUrl: 'my-comp.html', // This requires asynchronous resolution |
| * }) |
| * class MyComponent{ |
| * } |
| * |
| * // Calling `renderComponent` will fail because `renderComponent` is a synchronous process |
| * // and `MyComponent`'s `@Component.templateUrl` needs to be resolved asynchronously. |
| * |
| * // Calling `resolveComponentResources()` will resolve `@Component.templateUrl` into |
| * // `@Component.template`, which allows `renderComponent` to proceed in a synchronous manner. |
| * |
| * // Use browser's `fetch()` function as the default resource resolution strategy. |
| * resolveComponentResources(fetch).then(() => { |
| * // After resolution all URLs have been converted into `template` strings. |
| * renderComponent(MyComponent); |
| * }); |
| * |
| * ``` |
| * |
| * NOTE: In AOT the resolution happens during compilation, and so there should be no need |
| * to call this method outside JIT mode. |
| * |
| * @param resourceResolver a function which is responsible for returning a `Promise` to the |
| * contents of the resolved URL. Browser's `fetch()` method is a good default implementation. |
| */ |
| function resolveComponentResources(resourceResolver) { |
| // Store all promises which are fetching the resources. |
| const componentResolved = []; |
| // Cache so that we don't fetch the same resource more than once. |
| const urlMap = new Map(); |
| function cachedResourceResolve(url) { |
| let promise = urlMap.get(url); |
| if (!promise) { |
| const resp = resourceResolver(url); |
| urlMap.set(url, promise = resp.then(unwrapResponse)); |
| } |
| return promise; |
| } |
| componentResourceResolutionQueue.forEach((component, type) => { |
| const promises = []; |
| if (component.templateUrl) { |
| promises.push(cachedResourceResolve(component.templateUrl).then((template) => { |
| component.template = template; |
| })); |
| } |
| const styleUrls = component.styleUrls; |
| const styles = component.styles || (component.styles = []); |
| const styleOffset = component.styles.length; |
| styleUrls && styleUrls.forEach((styleUrl, index) => { |
| styles.push(''); // pre-allocate array. |
| promises.push(cachedResourceResolve(styleUrl).then((style) => { |
| styles[styleOffset + index] = style; |
| styleUrls.splice(styleUrls.indexOf(styleUrl), 1); |
| if (styleUrls.length == 0) { |
| component.styleUrls = undefined; |
| } |
| })); |
| }); |
| const fullyResolved = Promise.all(promises).then(() => componentDefResolved(type)); |
| componentResolved.push(fullyResolved); |
| }); |
| clearResolutionOfComponentResourcesQueue(); |
| return Promise.all(componentResolved).then(() => undefined); |
| } |
| let componentResourceResolutionQueue = new Map(); |
| // Track when existing ɵcmp for a Type is waiting on resources. |
| const componentDefPendingResolution = new Set(); |
| function maybeQueueResolutionOfComponentResources(type, metadata) { |
| if (componentNeedsResolution(metadata)) { |
| componentResourceResolutionQueue.set(type, metadata); |
| componentDefPendingResolution.add(type); |
| } |
| } |
| function isComponentDefPendingResolution(type) { |
| return componentDefPendingResolution.has(type); |
| } |
| function componentNeedsResolution(component) { |
| return !!((component.templateUrl && !component.hasOwnProperty('template')) || |
| component.styleUrls && component.styleUrls.length); |
| } |
| function clearResolutionOfComponentResourcesQueue() { |
| const old = componentResourceResolutionQueue; |
| componentResourceResolutionQueue = new Map(); |
| return old; |
| } |
| function restoreComponentResolutionQueue(queue) { |
| componentDefPendingResolution.clear(); |
| queue.forEach((_, type) => componentDefPendingResolution.add(type)); |
| componentResourceResolutionQueue = queue; |
| } |
| function isComponentResourceResolutionQueueEmpty() { |
| return componentResourceResolutionQueue.size === 0; |
| } |
| function unwrapResponse(response) { |
| return typeof response == 'string' ? response : response.text(); |
| } |
| function componentDefResolved(type) { |
| componentDefPendingResolution.delete(type); |
| } |
| |
| /** |
| * @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 |
| */ |
| let _nextReferenceId = 0; |
| class MetadataOverrider { |
| constructor() { |
| this._references = new Map(); |
| } |
| /** |
| * Creates a new instance for the given metadata class |
| * based on an old instance and overrides. |
| */ |
| overrideMetadata(metadataClass, oldMetadata, override) { |
| const props = {}; |
| if (oldMetadata) { |
| _valueProps(oldMetadata).forEach((prop) => props[prop] = oldMetadata[prop]); |
| } |
| if (override.set) { |
| if (override.remove || override.add) { |
| throw new Error(`Cannot set and add/remove ${ɵstringify(metadataClass)} at the same time!`); |
| } |
| setMetadata(props, override.set); |
| } |
| if (override.remove) { |
| removeMetadata(props, override.remove, this._references); |
| } |
| if (override.add) { |
| addMetadata(props, override.add); |
| } |
| return new metadataClass(props); |
| } |
| } |
| function removeMetadata(metadata, remove, references) { |
| const removeObjects = new Set(); |
| for (const prop in remove) { |
| const removeValue = remove[prop]; |
| if (Array.isArray(removeValue)) { |
| removeValue.forEach((value) => { |
| removeObjects.add(_propHashKey(prop, value, references)); |
| }); |
| } |
| else { |
| removeObjects.add(_propHashKey(prop, removeValue, references)); |
| } |
| } |
| for (const prop in metadata) { |
| const propValue = metadata[prop]; |
| if (Array.isArray(propValue)) { |
| metadata[prop] = propValue.filter((value) => !removeObjects.has(_propHashKey(prop, value, references))); |
| } |
| else { |
| if (removeObjects.has(_propHashKey(prop, propValue, references))) { |
| metadata[prop] = undefined; |
| } |
| } |
| } |
| } |
| function addMetadata(metadata, add) { |
| for (const prop in add) { |
| const addValue = add[prop]; |
| const propValue = metadata[prop]; |
| if (propValue != null && Array.isArray(propValue)) { |
| metadata[prop] = propValue.concat(addValue); |
| } |
| else { |
| metadata[prop] = addValue; |
| } |
| } |
| } |
| function setMetadata(metadata, set) { |
| for (const prop in set) { |
| metadata[prop] = set[prop]; |
| } |
| } |
| function _propHashKey(propName, propValue, references) { |
| const replacer = (key, value) => { |
| if (typeof value === 'function') { |
| value = _serializeReference(value, references); |
| } |
| return value; |
| }; |
| return `${propName}:${JSON.stringify(propValue, replacer)}`; |
| } |
| function _serializeReference(ref, references) { |
| let id = references.get(ref); |
| if (!id) { |
| id = `${ɵstringify(ref)}${_nextReferenceId++}`; |
| references.set(ref, id); |
| } |
| return id; |
| } |
| function _valueProps(obj) { |
| const props = []; |
| // regular public props |
| Object.keys(obj).forEach((prop) => { |
| if (!prop.startsWith('_')) { |
| props.push(prop); |
| } |
| }); |
| // getters |
| let proto = obj; |
| while (proto = Object.getPrototypeOf(proto)) { |
| Object.keys(proto).forEach((protoProp) => { |
| const desc = Object.getOwnPropertyDescriptor(proto, protoProp); |
| if (!protoProp.startsWith('_') && desc && 'get' in desc) { |
| props.push(protoProp); |
| } |
| }); |
| } |
| return props; |
| } |
| |
| /** |
| * @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 |
| */ |
| const reflection = new ɵReflectionCapabilities(); |
| /** |
| * Allows to override ivy metadata for tests (via the `TestBed`). |
| */ |
| class OverrideResolver { |
| constructor() { |
| this.overrides = new Map(); |
| this.resolved = new Map(); |
| } |
| addOverride(type, override) { |
| const overrides = this.overrides.get(type) || []; |
| overrides.push(override); |
| this.overrides.set(type, overrides); |
| this.resolved.delete(type); |
| } |
| setOverrides(overrides) { |
| this.overrides.clear(); |
| overrides.forEach(([type, override]) => { |
| this.addOverride(type, override); |
| }); |
| } |
| getAnnotation(type) { |
| const annotations = reflection.annotations(type); |
| // Try to find the nearest known Type annotation and make sure that this annotation is an |
| // instance of the type we are looking for, so we can use it for resolution. Note: there might |
| // be multiple known annotations found due to the fact that Components can extend Directives (so |
| // both Directive and Component annotations would be present), so we always check if the known |
| // annotation has the right type. |
| for (let i = annotations.length - 1; i >= 0; i--) { |
| const annotation = annotations[i]; |
| const isKnownType = annotation instanceof Directive || annotation instanceof Component || |
| annotation instanceof Pipe || annotation instanceof NgModule; |
| if (isKnownType) { |
| return annotation instanceof this.type ? annotation : null; |
| } |
| } |
| return null; |
| } |
| resolve(type) { |
| let resolved = this.resolved.get(type) || null; |
| if (!resolved) { |
| resolved = this.getAnnotation(type); |
| if (resolved) { |
| const overrides = this.overrides.get(type); |
| if (overrides) { |
| const overrider = new MetadataOverrider(); |
| overrides.forEach(override => { |
| resolved = overrider.overrideMetadata(this.type, resolved, override); |
| }); |
| } |
| } |
| this.resolved.set(type, resolved); |
| } |
| return resolved; |
| } |
| } |
| class DirectiveResolver extends OverrideResolver { |
| get type() { |
| return Directive; |
| } |
| } |
| class ComponentResolver extends OverrideResolver { |
| get type() { |
| return Component; |
| } |
| } |
| class PipeResolver extends OverrideResolver { |
| get type() { |
| return Pipe; |
| } |
| } |
| class NgModuleResolver extends OverrideResolver { |
| get type() { |
| return NgModule; |
| } |
| } |
| |
| /** |
| * @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 |
| */ |
| var TestingModuleOverride; |
| (function (TestingModuleOverride) { |
| TestingModuleOverride[TestingModuleOverride["DECLARATION"] = 0] = "DECLARATION"; |
| TestingModuleOverride[TestingModuleOverride["OVERRIDE_TEMPLATE"] = 1] = "OVERRIDE_TEMPLATE"; |
| })(TestingModuleOverride || (TestingModuleOverride = {})); |
| function isTestingModuleOverride(value) { |
| return value === TestingModuleOverride.DECLARATION || |
| value === TestingModuleOverride.OVERRIDE_TEMPLATE; |
| } |
| class R3TestBedCompiler { |
| constructor(platform, additionalModuleTypes) { |
| this.platform = platform; |
| this.additionalModuleTypes = additionalModuleTypes; |
| this.originalComponentResolutionQueue = null; |
| // Testing module configuration |
| this.declarations = []; |
| this.imports = []; |
| this.providers = []; |
| this.schemas = []; |
| // Queues of components/directives/pipes that should be recompiled. |
| this.pendingComponents = new Set(); |
| this.pendingDirectives = new Set(); |
| this.pendingPipes = new Set(); |
| // Keep track of all components and directives, so we can patch Providers onto defs later. |
| this.seenComponents = new Set(); |
| this.seenDirectives = new Set(); |
| // Keep track of overridden modules, so that we can collect all affected ones in the module tree. |
| this.overriddenModules = new Set(); |
| // Store resolved styles for Components that have template overrides present and `styleUrls` |
| // defined at the same time. |
| this.existingComponentStyles = new Map(); |
| this.resolvers = initResolvers(); |
| this.componentToModuleScope = new Map(); |
| // Map that keeps initial version of component/directive/pipe defs in case |
| // we compile a Type again, thus overriding respective static fields. This is |
| // required to make sure we restore defs to their initial states between test runs |
| // TODO: we should support the case with multiple defs on a type |
| this.initialNgDefs = new Map(); |
| // Array that keeps cleanup operations for initial versions of component/directive/pipe/module |
| // defs in case TestBed makes changes to the originals. |
| this.defCleanupOps = []; |
| this._injector = null; |
| this.compilerProviders = null; |
| this.providerOverrides = []; |
| this.rootProviderOverrides = []; |
| // Overrides for injectables with `{providedIn: SomeModule}` need to be tracked and added to that |
| // module's provider list. |
| this.providerOverridesByModule = new Map(); |
| this.providerOverridesByToken = new Map(); |
| this.moduleProvidersOverridden = new Set(); |
| this.testModuleRef = null; |
| class DynamicTestModule { |
| } |
| this.testModuleType = DynamicTestModule; |
| } |
| setCompilerProviders(providers) { |
| this.compilerProviders = providers; |
| this._injector = null; |
| } |
| configureTestingModule(moduleDef) { |
| // Enqueue any compilation tasks for the directly declared component. |
| if (moduleDef.declarations !== undefined) { |
| this.queueTypeArray(moduleDef.declarations, TestingModuleOverride.DECLARATION); |
| this.declarations.push(...moduleDef.declarations); |
| } |
| // Enqueue any compilation tasks for imported modules. |
| if (moduleDef.imports !== undefined) { |
| this.queueTypesFromModulesArray(moduleDef.imports); |
| this.imports.push(...moduleDef.imports); |
| } |
| if (moduleDef.providers !== undefined) { |
| this.providers.push(...moduleDef.providers); |
| } |
| if (moduleDef.schemas !== undefined) { |
| this.schemas.push(...moduleDef.schemas); |
| } |
| } |
| overrideModule(ngModule, override) { |
| this.overriddenModules.add(ngModule); |
| // Compile the module right away. |
| this.resolvers.module.addOverride(ngModule, override); |
| const metadata = this.resolvers.module.resolve(ngModule); |
| if (metadata === null) { |
| throw invalidTypeError(ngModule.name, 'NgModule'); |
| } |
| this.recompileNgModule(ngModule, metadata); |
| // At this point, the module has a valid module def (ɵmod), but the override may have introduced |
| // new declarations or imported modules. Ingest any possible new types and add them to the |
| // current queue. |
| this.queueTypesFromModulesArray([ngModule]); |
| } |
| overrideComponent(component, override) { |
| this.resolvers.component.addOverride(component, override); |
| this.pendingComponents.add(component); |
| } |
| overrideDirective(directive, override) { |
| this.resolvers.directive.addOverride(directive, override); |
| this.pendingDirectives.add(directive); |
| } |
| overridePipe(pipe, override) { |
| this.resolvers.pipe.addOverride(pipe, override); |
| this.pendingPipes.add(pipe); |
| } |
| overrideProvider(token, provider) { |
| let providerDef; |
| if (provider.useFactory !== undefined) { |
| providerDef = { |
| provide: token, |
| useFactory: provider.useFactory, |
| deps: provider.deps || [], |
| multi: provider.multi |
| }; |
| } |
| else if (provider.useValue !== undefined) { |
| providerDef = { provide: token, useValue: provider.useValue, multi: provider.multi }; |
| } |
| else { |
| providerDef = { provide: token }; |
| } |
| const injectableDef = typeof token !== 'string' ? ɵgetInjectableDef(token) : null; |
| const isRoot = injectableDef !== null && injectableDef.providedIn === 'root'; |
| const overridesBucket = isRoot ? this.rootProviderOverrides : this.providerOverrides; |
| overridesBucket.push(providerDef); |
| // Keep overrides grouped by token as well for fast lookups using token |
| this.providerOverridesByToken.set(token, providerDef); |
| if (injectableDef !== null && injectableDef.providedIn !== null && |
| typeof injectableDef.providedIn !== 'string') { |
| const existingOverrides = this.providerOverridesByModule.get(injectableDef.providedIn); |
| if (existingOverrides !== undefined) { |
| existingOverrides.push(providerDef); |
| } |
| else { |
| this.providerOverridesByModule.set(injectableDef.providedIn, [providerDef]); |
| } |
| } |
| } |
| overrideTemplateUsingTestingModule(type, template) { |
| const def = type[ɵNG_COMP_DEF]; |
| const hasStyleUrls = () => { |
| const metadata = this.resolvers.component.resolve(type); |
| return !!metadata.styleUrls && metadata.styleUrls.length > 0; |
| }; |
| const overrideStyleUrls = !!def && !isComponentDefPendingResolution(type) && hasStyleUrls(); |
| // In Ivy, compiling a component does not require knowing the module providing the |
| // component's scope, so overrideTemplateUsingTestingModule can be implemented purely via |
| // overrideComponent. Important: overriding template requires full Component re-compilation, |
| // which may fail in case styleUrls are also present (thus Component is considered as required |
| // resolution). In order to avoid this, we preemptively set styleUrls to an empty array, |
| // preserve current styles available on Component def and restore styles back once compilation |
| // is complete. |
| const override = overrideStyleUrls ? { template, styles: [], styleUrls: [] } : { template }; |
| this.overrideComponent(type, { set: override }); |
| if (overrideStyleUrls && def.styles && def.styles.length > 0) { |
| this.existingComponentStyles.set(type, def.styles); |
| } |
| // Set the component's scope to be the testing module. |
| this.componentToModuleScope.set(type, TestingModuleOverride.OVERRIDE_TEMPLATE); |
| } |
| compileComponents() { |
| return __awaiter(this, void 0, void 0, function* () { |
| this.clearComponentResolutionQueue(); |
| // Run compilers for all queued types. |
| let needsAsyncResources = this.compileTypesSync(); |
| // compileComponents() should not be async unless it needs to be. |
| if (needsAsyncResources) { |
| let resourceLoader; |
| let resolver = (url) => { |
| if (!resourceLoader) { |
| resourceLoader = this.injector.get(ResourceLoader); |
| } |
| return Promise.resolve(resourceLoader.get(url)); |
| }; |
| yield resolveComponentResources(resolver); |
| } |
| }); |
| } |
| finalize() { |
| // One last compile |
| this.compileTypesSync(); |
| // Create the testing module itself. |
| this.compileTestModule(); |
| this.applyTransitiveScopes(); |
| this.applyProviderOverrides(); |
| // Patch previously stored `styles` Component values (taken from ɵcmp), in case these |
| // Components have `styleUrls` fields defined and template override was requested. |
| this.patchComponentsWithExistingStyles(); |
| // Clear the componentToModuleScope map, so that future compilations don't reset the scope of |
| // every component. |
| this.componentToModuleScope.clear(); |
| const parentInjector = this.platform.injector; |
| this.testModuleRef = new ɵRender3NgModuleRef(this.testModuleType, parentInjector); |
| // ApplicationInitStatus.runInitializers() is marked @internal to core. |
| // Cast it to any before accessing it. |
| this.testModuleRef.injector.get(ApplicationInitStatus).runInitializers(); |
| // Set locale ID after running app initializers, since locale information might be updated while |
| // running initializers. This is also consistent with the execution order while bootstrapping an |
| // app (see `packages/core/src/application_ref.ts` file). |
| const localeId = this.testModuleRef.injector.get(LOCALE_ID, ɵDEFAULT_LOCALE_ID); |
| ɵsetLocaleId(localeId); |
| return this.testModuleRef; |
| } |
| /** |
| * @internal |
| */ |
| _compileNgModuleSync(moduleType) { |
| this.queueTypesFromModulesArray([moduleType]); |
| this.compileTypesSync(); |
| this.applyProviderOverrides(); |
| this.applyProviderOverridesToModule(moduleType); |
| this.applyTransitiveScopes(); |
| } |
| /** |
| * @internal |
| */ |
| _compileNgModuleAsync(moduleType) { |
| return __awaiter(this, void 0, void 0, function* () { |
| this.queueTypesFromModulesArray([moduleType]); |
| yield this.compileComponents(); |
| this.applyProviderOverrides(); |
| this.applyProviderOverridesToModule(moduleType); |
| this.applyTransitiveScopes(); |
| }); |
| } |
| /** |
| * @internal |
| */ |
| _getModuleResolver() { |
| return this.resolvers.module; |
| } |
| /** |
| * @internal |
| */ |
| _getComponentFactories(moduleType) { |
| return maybeUnwrapFn(moduleType.ɵmod.declarations).reduce((factories, declaration) => { |
| const componentDef = declaration.ɵcmp; |
| componentDef && factories.push(new ɵRender3ComponentFactory(componentDef, this.testModuleRef)); |
| return factories; |
| }, []); |
| } |
| compileTypesSync() { |
| // Compile all queued components, directives, pipes. |
| let needsAsyncResources = false; |
| this.pendingComponents.forEach(declaration => { |
| needsAsyncResources = needsAsyncResources || isComponentDefPendingResolution(declaration); |
| const metadata = this.resolvers.component.resolve(declaration); |
| if (metadata === null) { |
| throw invalidTypeError(declaration.name, 'Component'); |
| } |
| this.maybeStoreNgDef(ɵNG_COMP_DEF, declaration); |
| ɵcompileComponent(declaration, metadata); |
| }); |
| this.pendingComponents.clear(); |
| this.pendingDirectives.forEach(declaration => { |
| const metadata = this.resolvers.directive.resolve(declaration); |
| if (metadata === null) { |
| throw invalidTypeError(declaration.name, 'Directive'); |
| } |
| this.maybeStoreNgDef(ɵNG_DIR_DEF, declaration); |
| ɵcompileDirective(declaration, metadata); |
| }); |
| this.pendingDirectives.clear(); |
| this.pendingPipes.forEach(declaration => { |
| const metadata = this.resolvers.pipe.resolve(declaration); |
| if (metadata === null) { |
| throw invalidTypeError(declaration.name, 'Pipe'); |
| } |
| this.maybeStoreNgDef(ɵNG_PIPE_DEF, declaration); |
| ɵcompilePipe(declaration, metadata); |
| }); |
| this.pendingPipes.clear(); |
| return needsAsyncResources; |
| } |
| applyTransitiveScopes() { |
| if (this.overriddenModules.size > 0) { |
| // Module overrides (via `TestBed.overrideModule`) might affect scopes that were previously |
| // calculated and stored in `transitiveCompileScopes`. If module overrides are present, |
| // collect all affected modules and reset scopes to force their re-calculatation. |
| const testingModuleDef = this.testModuleType[ɵNG_MOD_DEF]; |
| const affectedModules = this.collectModulesAffectedByOverrides(testingModuleDef.imports); |
| if (affectedModules.size > 0) { |
| affectedModules.forEach(moduleType => { |
| this.storeFieldOfDefOnType(moduleType, ɵNG_MOD_DEF, 'transitiveCompileScopes'); |
| moduleType[ɵNG_MOD_DEF].transitiveCompileScopes = null; |
| }); |
| } |
| } |
| const moduleToScope = new Map(); |
| const getScopeOfModule = (moduleType) => { |
| if (!moduleToScope.has(moduleType)) { |
| const isTestingModule = isTestingModuleOverride(moduleType); |
| const realType = isTestingModule ? this.testModuleType : moduleType; |
| moduleToScope.set(moduleType, ɵtransitiveScopesFor(realType)); |
| } |
| return moduleToScope.get(moduleType); |
| }; |
| this.componentToModuleScope.forEach((moduleType, componentType) => { |
| const moduleScope = getScopeOfModule(moduleType); |
| this.storeFieldOfDefOnType(componentType, ɵNG_COMP_DEF, 'directiveDefs'); |
| this.storeFieldOfDefOnType(componentType, ɵNG_COMP_DEF, 'pipeDefs'); |
| // `tView` that is stored on component def contains information about directives and pipes |
| // that are in the scope of this component. Patching component scope will cause `tView` to be |
| // changed. Store original `tView` before patching scope, so the `tView` (including scope |
| // information) is restored back to its previous/original state before running next test. |
| this.storeFieldOfDefOnType(componentType, ɵNG_COMP_DEF, 'tView'); |
| ɵpatchComponentDefWithScope(componentType.ɵcmp, moduleScope); |
| }); |
| this.componentToModuleScope.clear(); |
| } |
| applyProviderOverrides() { |
| const maybeApplyOverrides = (field) => (type) => { |
| const resolver = field === ɵNG_COMP_DEF ? this.resolvers.component : this.resolvers.directive; |
| const metadata = resolver.resolve(type); |
| if (this.hasProviderOverrides(metadata.providers)) { |
| this.patchDefWithProviderOverrides(type, field); |
| } |
| }; |
| this.seenComponents.forEach(maybeApplyOverrides(ɵNG_COMP_DEF)); |
| this.seenDirectives.forEach(maybeApplyOverrides(ɵNG_DIR_DEF)); |
| this.seenComponents.clear(); |
| this.seenDirectives.clear(); |
| } |
| applyProviderOverridesToModule(moduleType) { |
| if (this.moduleProvidersOverridden.has(moduleType)) { |
| return; |
| } |
| this.moduleProvidersOverridden.add(moduleType); |
| const injectorDef = moduleType[ɵNG_INJ_DEF]; |
| if (this.providerOverridesByToken.size > 0) { |
| const providers = [ |
| ...injectorDef.providers, |
| ...(this.providerOverridesByModule.get(moduleType) || []) |
| ]; |
| if (this.hasProviderOverrides(providers)) { |
| this.maybeStoreNgDef(ɵNG_INJ_DEF, moduleType); |
| this.storeFieldOfDefOnType(moduleType, ɵNG_INJ_DEF, 'providers'); |
| injectorDef.providers = this.getOverriddenProviders(providers); |
| } |
| // Apply provider overrides to imported modules recursively |
| const moduleDef = moduleType[ɵNG_MOD_DEF]; |
| const imports = maybeUnwrapFn(moduleDef.imports); |
| for (const importedModule of imports) { |
| this.applyProviderOverridesToModule(importedModule); |
| } |
| // Also override the providers on any ModuleWithProviders imports since those don't appear in |
| // the moduleDef. |
| for (const importedModule of flatten(injectorDef.imports)) { |
| if (isModuleWithProviders(importedModule)) { |
| this.defCleanupOps.push({ |
| object: importedModule, |
| fieldName: 'providers', |
| originalValue: importedModule.providers |
| }); |
| importedModule.providers = this.getOverriddenProviders(importedModule.providers); |
| } |
| } |
| } |
| } |
| patchComponentsWithExistingStyles() { |
| this.existingComponentStyles.forEach((styles, type) => type[ɵNG_COMP_DEF].styles = styles); |
| this.existingComponentStyles.clear(); |
| } |
| queueTypeArray(arr, moduleType) { |
| for (const value of arr) { |
| if (Array.isArray(value)) { |
| this.queueTypeArray(value, moduleType); |
| } |
| else { |
| this.queueType(value, moduleType); |
| } |
| } |
| } |
| recompileNgModule(ngModule, metadata) { |
| // Cache the initial ngModuleDef as it will be overwritten. |
| this.maybeStoreNgDef(ɵNG_MOD_DEF, ngModule); |
| this.maybeStoreNgDef(ɵNG_INJ_DEF, ngModule); |
| ɵcompileNgModuleDefs(ngModule, metadata); |
| } |
| queueType(type, moduleType) { |
| const component = this.resolvers.component.resolve(type); |
| if (component) { |
| // Check whether a give Type has respective NG def (ɵcmp) and compile if def is |
| // missing. That might happen in case a class without any Angular decorators extends another |
| // class where Component/Directive/Pipe decorator is defined. |
| if (isComponentDefPendingResolution(type) || !type.hasOwnProperty(ɵNG_COMP_DEF)) { |
| this.pendingComponents.add(type); |
| } |
| this.seenComponents.add(type); |
| // Keep track of the module which declares this component, so later the component's scope |
| // can be set correctly. If the component has already been recorded here, then one of several |
| // cases is true: |
| // * the module containing the component was imported multiple times (common). |
| // * the component is declared in multiple modules (which is an error). |
| // * the component was in 'declarations' of the testing module, and also in an imported module |
| // in which case the module scope will be TestingModuleOverride.DECLARATION. |
| // * overrideTemplateUsingTestingModule was called for the component in which case the module |
| // scope will be TestingModuleOverride.OVERRIDE_TEMPLATE. |
| // |
| // If the component was previously in the testing module's 'declarations' (meaning the |
| // current value is TestingModuleOverride.DECLARATION), then `moduleType` is the component's |
| // real module, which was imported. This pattern is understood to mean that the component |
| // should use its original scope, but that the testing module should also contain the |
| // component in its scope. |
| if (!this.componentToModuleScope.has(type) || |
| this.componentToModuleScope.get(type) === TestingModuleOverride.DECLARATION) { |
| this.componentToModuleScope.set(type, moduleType); |
| } |
| return; |
| } |
| const directive = this.resolvers.directive.resolve(type); |
| if (directive) { |
| if (!type.hasOwnProperty(ɵNG_DIR_DEF)) { |
| this.pendingDirectives.add(type); |
| } |
| this.seenDirectives.add(type); |
| return; |
| } |
| const pipe = this.resolvers.pipe.resolve(type); |
| if (pipe && !type.hasOwnProperty(ɵNG_PIPE_DEF)) { |
| this.pendingPipes.add(type); |
| return; |
| } |
| } |
| queueTypesFromModulesArray(arr) { |
| // Because we may encounter the same NgModule while processing the imports and exports of an |
| // NgModule tree, we cache them in this set so we can skip ones that have already been seen |
| // encountered. In some test setups, this caching resulted in 10X runtime improvement. |
| const processedNgModuleDefs = new Set(); |
| const queueTypesFromModulesArrayRecur = (arr) => { |
| for (const value of arr) { |
| if (Array.isArray(value)) { |
| queueTypesFromModulesArrayRecur(value); |
| } |
| else if (hasNgModuleDef(value)) { |
| const def = value.ɵmod; |
| if (processedNgModuleDefs.has(def)) { |
| continue; |
| } |
| processedNgModuleDefs.add(def); |
| // Look through declarations, imports, and exports, and queue |
| // everything found there. |
| this.queueTypeArray(maybeUnwrapFn(def.declarations), value); |
| queueTypesFromModulesArrayRecur(maybeUnwrapFn(def.imports)); |
| queueTypesFromModulesArrayRecur(maybeUnwrapFn(def.exports)); |
| } |
| } |
| }; |
| queueTypesFromModulesArrayRecur(arr); |
| } |
| // When module overrides (via `TestBed.overrideModule`) are present, it might affect all modules |
| // that import (even transitively) an overridden one. For all affected modules we need to |
| // recalculate their scopes for a given test run and restore original scopes at the end. The goal |
| // of this function is to collect all affected modules in a set for further processing. Example: |
| // if we have the following module hierarchy: A -> B -> C (where `->` means `imports`) and module |
| // `C` is overridden, we consider `A` and `B` as affected, since their scopes might become |
| // invalidated with the override. |
| collectModulesAffectedByOverrides(arr) { |
| const seenModules = new Set(); |
| const affectedModules = new Set(); |
| const calcAffectedModulesRecur = (arr, path) => { |
| for (const value of arr) { |
| if (Array.isArray(value)) { |
| // If the value is an array, just flatten it (by invoking this function recursively), |
| // keeping "path" the same. |
| calcAffectedModulesRecur(value, path); |
| } |
| else if (hasNgModuleDef(value)) { |
| if (seenModules.has(value)) { |
| // If we've seen this module before and it's included into "affected modules" list, mark |
| // the whole path that leads to that module as affected, but do not descend into its |
| // imports, since we already examined them before. |
| if (affectedModules.has(value)) { |
| path.forEach(item => affectedModules.add(item)); |
| } |
| continue; |
| } |
| seenModules.add(value); |
| if (this.overriddenModules.has(value)) { |
| path.forEach(item => affectedModules.add(item)); |
| } |
| // Examine module imports recursively to look for overridden modules. |
| const moduleDef = value[ɵNG_MOD_DEF]; |
| calcAffectedModulesRecur(maybeUnwrapFn(moduleDef.imports), path.concat(value)); |
| } |
| } |
| }; |
| calcAffectedModulesRecur(arr, []); |
| return affectedModules; |
| } |
| maybeStoreNgDef(prop, type) { |
| if (!this.initialNgDefs.has(type)) { |
| const currentDef = Object.getOwnPropertyDescriptor(type, prop); |
| this.initialNgDefs.set(type, [prop, currentDef]); |
| } |
| } |
| storeFieldOfDefOnType(type, defField, fieldName) { |
| const def = type[defField]; |
| const originalValue = def[fieldName]; |
| this.defCleanupOps.push({ object: def, fieldName, originalValue }); |
| } |
| /** |
| * Clears current components resolution queue, but stores the state of the queue, so we can |
| * restore it later. Clearing the queue is required before we try to compile components (via |
| * `TestBed.compileComponents`), so that component defs are in sync with the resolution queue. |
| */ |
| clearComponentResolutionQueue() { |
| if (this.originalComponentResolutionQueue === null) { |
| this.originalComponentResolutionQueue = new Map(); |
| } |
| clearResolutionOfComponentResourcesQueue().forEach((value, key) => this.originalComponentResolutionQueue.set(key, value)); |
| } |
| /* |
| * Restores component resolution queue to the previously saved state. This operation is performed |
| * as a part of restoring the state after completion of the current set of tests (that might |
| * potentially mutate the state). |
| */ |
| restoreComponentResolutionQueue() { |
| if (this.originalComponentResolutionQueue !== null) { |
| restoreComponentResolutionQueue(this.originalComponentResolutionQueue); |
| this.originalComponentResolutionQueue = null; |
| } |
| } |
| restoreOriginalState() { |
| // Process cleanup ops in reverse order so the field's original value is restored correctly (in |
| // case there were multiple overrides for the same field). |
| forEachRight(this.defCleanupOps, (op) => { |
| op.object[op.fieldName] = op.originalValue; |
| }); |
| // Restore initial component/directive/pipe defs |
| this.initialNgDefs.forEach((value, type) => { |
| const [prop, descriptor] = value; |
| if (!descriptor) { |
| // Delete operations are generally undesirable since they have performance implications |
| // on objects they were applied to. In this particular case, situations where this code |
| // is invoked should be quite rare to cause any noticeable impact, since it's applied |
| // only to some test cases (for example when class with no annotations extends some |
| // @Component) when we need to clear 'ɵcmp' field on a given class to restore |
| // its original state (before applying overrides and running tests). |
| delete type[prop]; |
| } |
| else { |
| Object.defineProperty(type, prop, descriptor); |
| } |
| }); |
| this.initialNgDefs.clear(); |
| this.moduleProvidersOverridden.clear(); |
| this.restoreComponentResolutionQueue(); |
| // Restore the locale ID to the default value, this shouldn't be necessary but we never know |
| ɵsetLocaleId(ɵDEFAULT_LOCALE_ID); |
| } |
| compileTestModule() { |
| class RootScopeModule { |
| } |
| ɵcompileNgModuleDefs(RootScopeModule, { |
| providers: [...this.rootProviderOverrides], |
| }); |
| const ngZone = new NgZone({ enableLongStackTrace: true }); |
| const providers = [ |
| { provide: NgZone, useValue: ngZone }, |
| { provide: Compiler, useFactory: () => new R3TestCompiler(this) }, |
| ...this.providers, |
| ...this.providerOverrides, |
| ]; |
| const imports = [RootScopeModule, this.additionalModuleTypes, this.imports || []]; |
| // clang-format off |
| ɵcompileNgModuleDefs(this.testModuleType, { |
| declarations: this.declarations, |
| imports, |
| schemas: this.schemas, |
| providers, |
| }, /* allowDuplicateDeclarationsInRoot */ true); |
| // clang-format on |
| this.applyProviderOverridesToModule(this.testModuleType); |
| } |
| get injector() { |
| if (this._injector !== null) { |
| return this._injector; |
| } |
| const providers = []; |
| const compilerOptions = this.platform.injector.get(COMPILER_OPTIONS); |
| compilerOptions.forEach(opts => { |
| if (opts.providers) { |
| providers.push(opts.providers); |
| } |
| }); |
| if (this.compilerProviders !== null) { |
| providers.push(...this.compilerProviders); |
| } |
| // TODO(ocombe): make this work with an Injector directly instead of creating a module for it |
| class CompilerModule { |
| } |
| ɵcompileNgModuleDefs(CompilerModule, { providers }); |
| const CompilerModuleFactory = new ɵNgModuleFactory(CompilerModule); |
| this._injector = CompilerModuleFactory.create(this.platform.injector).injector; |
| return this._injector; |
| } |
| // get overrides for a specific provider (if any) |
| getSingleProviderOverrides(provider) { |
| const token = getProviderToken(provider); |
| return this.providerOverridesByToken.get(token) || null; |
| } |
| getProviderOverrides(providers) { |
| if (!providers || !providers.length || this.providerOverridesByToken.size === 0) |
| return []; |
| // There are two flattening operations here. The inner flatten() operates on the metadata's |
| // providers and applies a mapping function which retrieves overrides for each incoming |
| // provider. The outer flatten() then flattens the produced overrides array. If this is not |
| // done, the array can contain other empty arrays (e.g. `[[], []]`) which leak into the |
| // providers array and contaminate any error messages that might be generated. |
| return flatten(flatten(providers, (provider) => this.getSingleProviderOverrides(provider) || [])); |
| } |
| getOverriddenProviders(providers) { |
| if (!providers || !providers.length || this.providerOverridesByToken.size === 0) |
| return []; |
| const flattenedProviders = flatten(providers); |
| const overrides = this.getProviderOverrides(flattenedProviders); |
| const overriddenProviders = [...flattenedProviders, ...overrides]; |
| const final = []; |
| const seenOverriddenProviders = new Set(); |
| // We iterate through the list of providers in reverse order to make sure provider overrides |
| // take precedence over the values defined in provider list. We also filter out all providers |
| // that have overrides, keeping overridden values only. This is needed, since presence of a |
| // provider with `ngOnDestroy` hook will cause this hook to be registered and invoked later. |
| forEachRight(overriddenProviders, (provider) => { |
| const token = getProviderToken(provider); |
| if (this.providerOverridesByToken.has(token)) { |
| if (!seenOverriddenProviders.has(token)) { |
| seenOverriddenProviders.add(token); |
| // Treat all overridden providers as `{multi: false}` (even if it's a multi-provider) to |
| // make sure that provided override takes highest precedence and is not combined with |
| // other instances of the same multi provider. |
| final.unshift(Object.assign(Object.assign({}, provider), { multi: false })); |
| } |
| } |
| else { |
| final.unshift(provider); |
| } |
| }); |
| return final; |
| } |
| hasProviderOverrides(providers) { |
| return this.getProviderOverrides(providers).length > 0; |
| } |
| patchDefWithProviderOverrides(declaration, field) { |
| const def = declaration[field]; |
| if (def && def.providersResolver) { |
| this.maybeStoreNgDef(field, declaration); |
| const resolver = def.providersResolver; |
| const processProvidersFn = (providers) => this.getOverriddenProviders(providers); |
| this.storeFieldOfDefOnType(declaration, field, 'providersResolver'); |
| def.providersResolver = (ngDef) => resolver(ngDef, processProvidersFn); |
| } |
| } |
| } |
| function initResolvers() { |
| return { |
| module: new NgModuleResolver(), |
| component: new ComponentResolver(), |
| directive: new DirectiveResolver(), |
| pipe: new PipeResolver() |
| }; |
| } |
| function hasNgModuleDef(value) { |
| return value.hasOwnProperty('ɵmod'); |
| } |
| function maybeUnwrapFn(maybeFn) { |
| return maybeFn instanceof Function ? maybeFn() : maybeFn; |
| } |
| function flatten(values, mapFn) { |
| const out = []; |
| values.forEach(value => { |
| if (Array.isArray(value)) { |
| out.push(...flatten(value, mapFn)); |
| } |
| else { |
| out.push(mapFn ? mapFn(value) : value); |
| } |
| }); |
| return out; |
| } |
| function getProviderField(provider, field) { |
| return provider && typeof provider === 'object' && provider[field]; |
| } |
| function getProviderToken(provider) { |
| return getProviderField(provider, 'provide') || provider; |
| } |
| function isModuleWithProviders(value) { |
| return value.hasOwnProperty('ngModule'); |
| } |
| function forEachRight(values, fn) { |
| for (let idx = values.length - 1; idx >= 0; idx--) { |
| fn(values[idx], idx); |
| } |
| } |
| function invalidTypeError(name, expectedType) { |
| return new Error(`${name} class doesn't have @${expectedType} decorator or is missing metadata.`); |
| } |
| class R3TestCompiler { |
| constructor(testBed) { |
| this.testBed = testBed; |
| } |
| compileModuleSync(moduleType) { |
| this.testBed._compileNgModuleSync(moduleType); |
| return new ɵNgModuleFactory(moduleType); |
| } |
| compileModuleAsync(moduleType) { |
| return __awaiter(this, void 0, void 0, function* () { |
| yield this.testBed._compileNgModuleAsync(moduleType); |
| return new ɵNgModuleFactory(moduleType); |
| }); |
| } |
| compileModuleAndAllComponentsSync(moduleType) { |
| const ngModuleFactory = this.compileModuleSync(moduleType); |
| const componentFactories = this.testBed._getComponentFactories(moduleType); |
| return new ModuleWithComponentFactories(ngModuleFactory, componentFactories); |
| } |
| compileModuleAndAllComponentsAsync(moduleType) { |
| return __awaiter(this, void 0, void 0, function* () { |
| const ngModuleFactory = yield this.compileModuleAsync(moduleType); |
| const componentFactories = this.testBed._getComponentFactories(moduleType); |
| return new ModuleWithComponentFactories(ngModuleFactory, componentFactories); |
| }); |
| } |
| clearCache() { } |
| clearCacheFor(type) { } |
| getModuleId(moduleType) { |
| const meta = this.testBed._getModuleResolver().resolve(moduleType); |
| return meta && meta.id || undefined; |
| } |
| } |
| |
| /** |
| * @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 |
| */ |
| /** |
| * An abstract class for inserting the root test component element in a platform independent way. |
| * |
| * @publicApi |
| */ |
| class TestComponentRenderer { |
| insertRootElement(rootElementId) { } |
| } |
| /** |
| * @publicApi |
| */ |
| const ComponentFixtureAutoDetect = new InjectionToken('ComponentFixtureAutoDetect'); |
| /** |
| * @publicApi |
| */ |
| const ComponentFixtureNoNgZone = new InjectionToken('ComponentFixtureNoNgZone'); |
| |
| /** |
| * @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 |
| */ |
| let _nextRootElementId = 0; |
| /** |
| * @description |
| * Configures and initializes environment for unit testing and provides methods for |
| * creating components and services in unit tests. |
| * |
| * TestBed is the primary api for writing unit tests for Angular applications and libraries. |
| * |
| * Note: Use `TestBed` in tests. It will be set to either `TestBedViewEngine` or `TestBedRender3` |
| * according to the compiler used. |
| */ |
| class TestBedRender3 { |
| constructor() { |
| // Properties |
| this.platform = null; |
| this.ngModule = null; |
| this._compiler = null; |
| this._testModuleRef = null; |
| this._activeFixtures = []; |
| this._globalCompilationChecked = false; |
| } |
| /** |
| * Initialize the environment for testing with a compiler factory, a PlatformRef, and an |
| * angular module. These are common to every test in the suite. |
| * |
| * This may only be called once, to set up the common providers for the current test |
| * suite on the current platform. If you absolutely need to change the providers, |
| * first use `resetTestEnvironment`. |
| * |
| * Test modules and platforms for individual platforms are available from |
| * '@angular/<platform_name>/testing'. |
| * |
| * @publicApi |
| */ |
| static initTestEnvironment(ngModule, platform, aotSummaries) { |
| const testBed = _getTestBedRender3(); |
| testBed.initTestEnvironment(ngModule, platform, aotSummaries); |
| return testBed; |
| } |
| /** |
| * Reset the providers for the test injector. |
| * |
| * @publicApi |
| */ |
| static resetTestEnvironment() { |
| _getTestBedRender3().resetTestEnvironment(); |
| } |
| static configureCompiler(config) { |
| _getTestBedRender3().configureCompiler(config); |
| return TestBedRender3; |
| } |
| /** |
| * Allows overriding default providers, directives, pipes, modules of the test injector, |
| * which are defined in test_injector.js |
| */ |
| static configureTestingModule(moduleDef) { |
| _getTestBedRender3().configureTestingModule(moduleDef); |
| return TestBedRender3; |
| } |
| /** |
| * Compile components with a `templateUrl` for the test's NgModule. |
| * It is necessary to call this function |
| * as fetching urls is asynchronous. |
| */ |
| static compileComponents() { |
| return _getTestBedRender3().compileComponents(); |
| } |
| static overrideModule(ngModule, override) { |
| _getTestBedRender3().overrideModule(ngModule, override); |
| return TestBedRender3; |
| } |
| static overrideComponent(component, override) { |
| _getTestBedRender3().overrideComponent(component, override); |
| return TestBedRender3; |
| } |
| static overrideDirective(directive, override) { |
| _getTestBedRender3().overrideDirective(directive, override); |
| return TestBedRender3; |
| } |
| static overridePipe(pipe, override) { |
| _getTestBedRender3().overridePipe(pipe, override); |
| return TestBedRender3; |
| } |
| static overrideTemplate(component, template) { |
| _getTestBedRender3().overrideComponent(component, { set: { template, templateUrl: null } }); |
| return TestBedRender3; |
| } |
| /** |
| * Overrides the template of the given component, compiling the template |
| * in the context of the TestingModule. |
| * |
| * Note: This works for JIT and AOTed components as well. |
| */ |
| static overrideTemplateUsingTestingModule(component, template) { |
| _getTestBedRender3().overrideTemplateUsingTestingModule(component, template); |
| return TestBedRender3; |
| } |
| static overrideProvider(token, provider) { |
| _getTestBedRender3().overrideProvider(token, provider); |
| return TestBedRender3; |
| } |
| static inject(token, notFoundValue, flags) { |
| return _getTestBedRender3().inject(token, notFoundValue, flags); |
| } |
| /** @deprecated from v9.0.0 use TestBed.inject */ |
| static get(token, notFoundValue = Injector.THROW_IF_NOT_FOUND, flags = InjectFlags.Default) { |
| return _getTestBedRender3().inject(token, notFoundValue, flags); |
| } |
| static createComponent(component) { |
| return _getTestBedRender3().createComponent(component); |
| } |
| static resetTestingModule() { |
| _getTestBedRender3().resetTestingModule(); |
| return TestBedRender3; |
| } |
| /** |
| * Initialize the environment for testing with a compiler factory, a PlatformRef, and an |
| * angular module. These are common to every test in the suite. |
| * |
| * This may only be called once, to set up the common providers for the current test |
| * suite on the current platform. If you absolutely need to change the providers, |
| * first use `resetTestEnvironment`. |
| * |
| * Test modules and platforms for individual platforms are available from |
| * '@angular/<platform_name>/testing'. |
| * |
| * @publicApi |
| */ |
| initTestEnvironment(ngModule, platform, aotSummaries) { |
| if (this.platform || this.ngModule) { |
| throw new Error('Cannot set base providers because it has already been called'); |
| } |
| this.platform = platform; |
| this.ngModule = ngModule; |
| this._compiler = new R3TestBedCompiler(this.platform, this.ngModule); |
| } |
| /** |
| * Reset the providers for the test injector. |
| * |
| * @publicApi |
| */ |
| resetTestEnvironment() { |
| this.resetTestingModule(); |
| this._compiler = null; |
| this.platform = null; |
| this.ngModule = null; |
| } |
| resetTestingModule() { |
| this.checkGlobalCompilationFinished(); |
| ɵresetCompiledComponents(); |
| if (this._compiler !== null) { |
| this.compiler.restoreOriginalState(); |
| } |
| this._compiler = new R3TestBedCompiler(this.platform, this.ngModule); |
| this._testModuleRef = null; |
| this.destroyActiveFixtures(); |
| } |
| configureCompiler(config) { |
| if (config.useJit != null) { |
| throw new Error('the Render3 compiler JiT mode is not configurable !'); |
| } |
| if (config.providers !== undefined) { |
| this.compiler.setCompilerProviders(config.providers); |
| } |
| } |
| configureTestingModule(moduleDef) { |
| this.assertNotInstantiated('R3TestBed.configureTestingModule', 'configure the test module'); |
| this.compiler.configureTestingModule(moduleDef); |
| } |
| compileComponents() { |
| return this.compiler.compileComponents(); |
| } |
| inject(token, notFoundValue, flags) { |
| if (token === TestBedRender3) { |
| return this; |
| } |
| const UNDEFINED = {}; |
| const result = this.testModuleRef.injector.get(token, UNDEFINED, flags); |
| return result === UNDEFINED ? this.compiler.injector.get(token, notFoundValue, flags) : |
| result; |
| } |
| /** @deprecated from v9.0.0 use TestBed.inject */ |
| get(token, notFoundValue = Injector.THROW_IF_NOT_FOUND, flags = InjectFlags.Default) { |
| return this.inject(token, notFoundValue, flags); |
| } |
| execute(tokens, fn, context) { |
| const params = tokens.map(t => this.inject(t)); |
| return fn.apply(context, params); |
| } |
| overrideModule(ngModule, override) { |
| this.assertNotInstantiated('overrideModule', 'override module metadata'); |
| this.compiler.overrideModule(ngModule, override); |
| } |
| overrideComponent(component, override) { |
| this.assertNotInstantiated('overrideComponent', 'override component metadata'); |
| this.compiler.overrideComponent(component, override); |
| } |
| overrideTemplateUsingTestingModule(component, template) { |
| this.assertNotInstantiated('R3TestBed.overrideTemplateUsingTestingModule', 'Cannot override template when the test module has already been instantiated'); |
| this.compiler.overrideTemplateUsingTestingModule(component, template); |
| } |
| overrideDirective(directive, override) { |
| this.assertNotInstantiated('overrideDirective', 'override directive metadata'); |
| this.compiler.overrideDirective(directive, override); |
| } |
| overridePipe(pipe, override) { |
| this.assertNotInstantiated('overridePipe', 'override pipe metadata'); |
| this.compiler.overridePipe(pipe, override); |
| } |
| /** |
| * Overwrites all providers for the given token with the given provider definition. |
| */ |
| overrideProvider(token, provider) { |
| this.assertNotInstantiated('overrideProvider', 'override provider'); |
| this.compiler.overrideProvider(token, provider); |
| } |
| createComponent(type) { |
| const testComponentRenderer = this.inject(TestComponentRenderer); |
| const rootElId = `root${_nextRootElementId++}`; |
| testComponentRenderer.insertRootElement(rootElId); |
| const componentDef = type.ɵcmp; |
| if (!componentDef) { |
| throw new Error(`It looks like '${ɵstringify(type)}' has not been IVY compiled - it has no 'ɵcmp' field`); |
| } |
| // TODO: Don't cast as `InjectionToken<boolean>`, proper type is boolean[] |
| const noNgZone = this.inject(ComponentFixtureNoNgZone, false); |
| // TODO: Don't cast as `InjectionToken<boolean>`, proper type is boolean[] |
| const autoDetect = this.inject(ComponentFixtureAutoDetect, false); |
| const ngZone = noNgZone ? null : this.inject(NgZone, null); |
| const componentFactory = new ɵRender3ComponentFactory(componentDef); |
| const initComponent = () => { |
| const componentRef = componentFactory.create(Injector.NULL, [], `#${rootElId}`, this.testModuleRef); |
| return new ComponentFixture(componentRef, ngZone, autoDetect); |
| }; |
| const fixture = ngZone ? ngZone.run(initComponent) : initComponent(); |
| this._activeFixtures.push(fixture); |
| return fixture; |
| } |
| /** |
| * @internal strip this from published d.ts files due to |
| * https://github.com/microsoft/TypeScript/issues/36216 |
| */ |
| get compiler() { |
| if (this._compiler === null) { |
| throw new Error(`Need to call TestBed.initTestEnvironment() first`); |
| } |
| return this._compiler; |
| } |
| /** |
| * @internal strip this from published d.ts files due to |
| * https://github.com/microsoft/TypeScript/issues/36216 |
| */ |
| get testModuleRef() { |
| if (this._testModuleRef === null) { |
| this._testModuleRef = this.compiler.finalize(); |
| } |
| return this._testModuleRef; |
| } |
| assertNotInstantiated(methodName, methodDescription) { |
| if (this._testModuleRef !== null) { |
| throw new Error(`Cannot ${methodDescription} when the test module has already been instantiated. ` + |
| `Make sure you are not using \`inject\` before \`${methodName}\`.`); |
| } |
| } |
| /** |
| * Check whether the module scoping queue should be flushed, and flush it if needed. |
| * |
| * When the TestBed is reset, it clears the JIT module compilation queue, cancelling any |
| * in-progress module compilation. This creates a potential hazard - the very first time the |
| * TestBed is initialized (or if it's reset without being initialized), there may be pending |
| * compilations of modules declared in global scope. These compilations should be finished. |
| * |
| * To ensure that globally declared modules have their components scoped properly, this function |
| * is called whenever TestBed is initialized or reset. The _first_ time that this happens, prior |
| * to any other operations, the scoping queue is flushed. |
| */ |
| checkGlobalCompilationFinished() { |
| // Checking _testNgModuleRef is null should not be necessary, but is left in as an additional |
| // guard that compilations queued in tests (after instantiation) are never flushed accidentally. |
| if (!this._globalCompilationChecked && this._testModuleRef === null) { |
| ɵflushModuleScopingQueueAsMuchAsPossible(); |
| } |
| this._globalCompilationChecked = true; |
| } |
| destroyActiveFixtures() { |
| this._activeFixtures.forEach((fixture) => { |
| try { |
| fixture.destroy(); |
| } |
| catch (e) { |
| console.error('Error during cleanup of component', { |
| component: fixture.componentInstance, |
| stacktrace: e, |
| }); |
| } |
| }); |
| this._activeFixtures = []; |
| } |
| } |
| let testBed; |
| function _getTestBedRender3() { |
| return testBed = testBed || new TestBedRender3(); |
| } |
| |
| /** |
| * @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 |
| */ |
| function unimplemented() { |
| throw Error('unimplemented'); |
| } |
| /** |
| * Special interface to the compiler only used by testing |
| * |
| * @publicApi |
| */ |
| class TestingCompiler extends Compiler { |
| get injector() { |
| throw unimplemented(); |
| } |
| overrideModule(module, overrides) { |
| throw unimplemented(); |
| } |
| overrideDirective(directive, overrides) { |
| throw unimplemented(); |
| } |
| overrideComponent(component, overrides) { |
| throw unimplemented(); |
| } |
| overridePipe(directive, overrides) { |
| throw unimplemented(); |
| } |
| /** |
| * Allows to pass the compile summary from AOT compilation to the JIT compiler, |
| * so that it can use the code generated by AOT. |
| */ |
| loadAotSummaries(summaries) { |
| throw unimplemented(); |
| } |
| /** |
| * Gets the component factory for the given component. |
| * This assumes that the component has been compiled before calling this call using |
| * `compileModuleAndAllComponents*`. |
| */ |
| getComponentFactory(component) { |
| throw unimplemented(); |
| } |
| /** |
| * Returns the component type that is stored in the given error. |
| * This can be used for errors created by compileModule... |
| */ |
| getComponentFromError(error) { |
| throw unimplemented(); |
| } |
| } |
| TestingCompiler.decorators = [ |
| { type: Injectable } |
| ]; |
| /** |
| * A factory for creating a Compiler |
| * |
| * @publicApi |
| */ |
| class TestingCompilerFactory { |
| } |
| |
| /** |
| * @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 |
| */ |
| let _nextRootElementId$1 = 0; |
| /** |
| * @description |
| * Configures and initializes environment for unit testing and provides methods for |
| * creating components and services in unit tests. |
| * |
| * `TestBed` is the primary api for writing unit tests for Angular applications and libraries. |
| * |
| * Note: Use `TestBed` in tests. It will be set to either `TestBedViewEngine` or `TestBedRender3` |
| * according to the compiler used. |
| */ |
| class TestBedViewEngine { |
| constructor() { |
| this._instantiated = false; |
| this._compiler = null; |
| this._moduleRef = null; |
| this._moduleFactory = null; |
| this._compilerOptions = []; |
| this._moduleOverrides = []; |
| this._componentOverrides = []; |
| this._directiveOverrides = []; |
| this._pipeOverrides = []; |
| this._providers = []; |
| this._declarations = []; |
| this._imports = []; |
| this._schemas = []; |
| this._activeFixtures = []; |
| this._testEnvAotSummaries = () => []; |
| this._aotSummaries = []; |
| this._templateOverrides = []; |
| this._isRoot = true; |
| this._rootProviderOverrides = []; |
| this.platform = null; |
| this.ngModule = null; |
| } |
| /** |
| * Initialize the environment for testing with a compiler factory, a PlatformRef, and an |
| * angular module. These are common to every test in the suite. |
| * |
| * This may only be called once, to set up the common providers for the current test |
| * suite on the current platform. If you absolutely need to change the providers, |
| * first use `resetTestEnvironment`. |
| * |
| * Test modules and platforms for individual platforms are available from |
| * '@angular/<platform_name>/testing'. |
| */ |
| static initTestEnvironment(ngModule, platform, aotSummaries) { |
| const testBed = _getTestBedViewEngine(); |
| testBed.initTestEnvironment(ngModule, platform, aotSummaries); |
| return testBed; |
| } |
| /** |
| * Reset the providers for the test injector. |
| */ |
| static resetTestEnvironment() { |
| _getTestBedViewEngine().resetTestEnvironment(); |
| } |
| static resetTestingModule() { |
| _getTestBedViewEngine().resetTestingModule(); |
| return TestBedViewEngine; |
| } |
| /** |
| * Allows overriding default compiler providers and settings |
| * which are defined in test_injector.js |
| */ |
| static configureCompiler(config) { |
| _getTestBedViewEngine().configureCompiler(config); |
| return TestBedViewEngine; |
| } |
| /** |
| * Allows overriding default providers, directives, pipes, modules of the test injector, |
| * which are defined in test_injector.js |
| */ |
| static configureTestingModule(moduleDef) { |
| _getTestBedViewEngine().configureTestingModule(moduleDef); |
| return TestBedViewEngine; |
| } |
| /** |
| * Compile components with a `templateUrl` for the test's NgModule. |
| * It is necessary to call this function |
| * as fetching urls is asynchronous. |
| */ |
| static compileComponents() { |
| return getTestBed().compileComponents(); |
| } |
| static overrideModule(ngModule, override) { |
| _getTestBedViewEngine().overrideModule(ngModule, override); |
| return TestBedViewEngine; |
| } |
| static overrideComponent(component, override) { |
| _getTestBedViewEngine().overrideComponent(component, override); |
| return TestBedViewEngine; |
| } |
| static overrideDirective(directive, override) { |
| _getTestBedViewEngine().overrideDirective(directive, override); |
| return TestBedViewEngine; |
| } |
| static overridePipe(pipe, override) { |
| _getTestBedViewEngine().overridePipe(pipe, override); |
| return TestBedViewEngine; |
| } |
| static overrideTemplate(component, template) { |
| _getTestBedViewEngine().overrideComponent(component, { set: { template, templateUrl: null } }); |
| return TestBedViewEngine; |
| } |
| /** |
| * Overrides the template of the given component, compiling the template |
| * in the context of the TestingModule. |
| * |
| * Note: This works for JIT and AOTed components as well. |
| */ |
| static overrideTemplateUsingTestingModule(component, template) { |
| _getTestBedViewEngine().overrideTemplateUsingTestingModule(component, template); |
| return TestBedViewEngine; |
| } |
| static overrideProvider(token, provider) { |
| _getTestBedViewEngine().overrideProvider(token, provider); |
| return TestBedViewEngine; |
| } |
| static inject(token, notFoundValue, flags) { |
| return _getTestBedViewEngine().inject(token, notFoundValue, flags); |
| } |
| /** @deprecated from v9.0.0 use TestBed.inject */ |
| static get(token, notFoundValue = Injector.THROW_IF_NOT_FOUND, flags = InjectFlags.Default) { |
| return _getTestBedViewEngine().inject(token, notFoundValue, flags); |
| } |
| static createComponent(component) { |
| return _getTestBedViewEngine().createComponent(component); |
| } |
| /** |
| * Initialize the environment for testing with a compiler factory, a PlatformRef, and an |
| * angular module. These are common to every test in the suite. |
| * |
| * This may only be called once, to set up the common providers for the current test |
| * suite on the current platform. If you absolutely need to change the providers, |
| * first use `resetTestEnvironment`. |
| * |
| * Test modules and platforms for individual platforms are available from |
| * '@angular/<platform_name>/testing'. |
| */ |
| initTestEnvironment(ngModule, platform, aotSummaries) { |
| if (this.platform || this.ngModule) { |
| throw new Error('Cannot set base providers because it has already been called'); |
| } |
| this.platform = platform; |
| this.ngModule = ngModule; |
| if (aotSummaries) { |
| this._testEnvAotSummaries = aotSummaries; |
| } |
| } |
| /** |
| * Reset the providers for the test injector. |
| */ |
| resetTestEnvironment() { |
| this.resetTestingModule(); |
| this.platform = null; |
| this.ngModule = null; |
| this._testEnvAotSummaries = () => []; |
| } |
| resetTestingModule() { |
| ɵclearOverrides(); |
| this._aotSummaries = []; |
| this._templateOverrides = []; |
| this._compiler = null; |
| this._moduleOverrides = []; |
| this._componentOverrides = []; |
| this._directiveOverrides = []; |
| this._pipeOverrides = []; |
| this._isRoot = true; |
| this._rootProviderOverrides = []; |
| this._moduleRef = null; |
| this._moduleFactory = null; |
| this._compilerOptions = []; |
| this._providers = []; |
| this._declarations = []; |
| this._imports = []; |
| this._schemas = []; |
| this._instantiated = false; |
| this._activeFixtures.forEach((fixture) => { |
| try { |
| fixture.destroy(); |
| } |
| catch (e) { |
| console.error('Error during cleanup of component', { |
| component: fixture.componentInstance, |
| stacktrace: e, |
| }); |
| } |
| }); |
| this._activeFixtures = []; |
| } |
| configureCompiler(config) { |
| this._assertNotInstantiated('TestBed.configureCompiler', 'configure the compiler'); |
| this._compilerOptions.push(config); |
| } |
| configureTestingModule(moduleDef) { |
| this._assertNotInstantiated('TestBed.configureTestingModule', 'configure the test module'); |
| if (moduleDef.providers) { |
| this._providers.push(...moduleDef.providers); |
| } |
| if (moduleDef.declarations) { |
| this._declarations.push(...moduleDef.declarations); |
| } |
| if (moduleDef.imports) { |
| this._imports.push(...moduleDef.imports); |
| } |
| if (moduleDef.schemas) { |
| this._schemas.push(...moduleDef.schemas); |
| } |
| if (moduleDef.aotSummaries) { |
| this._aotSummaries.push(moduleDef.aotSummaries); |
| } |
| } |
| compileComponents() { |
| if (this._moduleFactory || this._instantiated) { |
| return Promise.resolve(null); |
| } |
| const moduleType = this._createCompilerAndModule(); |
| return this._compiler.compileModuleAndAllComponentsAsync(moduleType) |
| .then((moduleAndComponentFactories) => { |
| this._moduleFactory = moduleAndComponentFactories.ngModuleFactory; |
| }); |
| } |
| _initIfNeeded() { |
| if (this._instantiated) { |
| return; |
| } |
| if (!this._moduleFactory) { |
| try { |
| const moduleType = this._createCompilerAndModule(); |
| this._moduleFactory = |
| this._compiler.compileModuleAndAllComponentsSync(moduleType).ngModuleFactory; |
| } |
| catch (e) { |
| const errorCompType = this._compiler.getComponentFromError(e); |
| if (errorCompType) { |
| throw new Error(`This test module uses the component ${ɵstringify(errorCompType)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` + |
| `Please call "TestBed.compileComponents" before your test.`); |
| } |
| else { |
| throw e; |
| } |
| } |
| } |
| for (const { component, templateOf } of this._templateOverrides) { |
| const compFactory = this._compiler.getComponentFactory(templateOf); |
| ɵoverrideComponentView(component, compFactory); |
| } |
| const ngZone = new NgZone({ enableLongStackTrace: true, shouldCoalesceEventChangeDetection: false }); |
| const providers = [{ provide: NgZone, useValue: ngZone }]; |
| const ngZoneInjector = Injector.create({ |
| providers: providers, |
| parent: this.platform.injector, |
| name: this._moduleFactory.moduleType.name |
| }); |
| this._moduleRef = this._moduleFactory.create(ngZoneInjector); |
| // ApplicationInitStatus.runInitializers() is marked @internal to core. So casting to any |
| // before accessing it. |
| this._moduleRef.injector.get(ApplicationInitStatus).runInitializers(); |
| this._instantiated = true; |
| } |
| _createCompilerAndModule() { |
| const providers = this._providers.concat([{ provide: TestBed, useValue: this }]); |
| const declarations = [...this._declarations, ...this._templateOverrides.map(entry => entry.templateOf)]; |
| const rootScopeImports = []; |
| const rootProviderOverrides = this._rootProviderOverrides; |
| if (this._isRoot) { |
| class RootScopeModule { |
| } |
| RootScopeModule.decorators = [ |
| { type: NgModule, args: [{ |
| providers: [ |
| ...rootProviderOverrides, |
| ], |
| jit: true, |
| },] } |
| ]; |
| rootScopeImports.push(RootScopeModule); |
| } |
| providers.push({ provide: ɵINJECTOR_SCOPE, useValue: this._isRoot ? 'root' : null }); |
| const imports = [rootScopeImports, this.ngModule, this._imports]; |
| const schemas = this._schemas; |
| class DynamicTestModule { |
| } |
| DynamicTestModule.decorators = [ |
| { type: NgModule, args: [{ providers, declarations, imports, schemas, jit: true },] } |
| ]; |
| const compilerFactory = this.platform.injector.get(TestingCompilerFactory); |
| this._compiler = compilerFactory.createTestingCompiler(this._compilerOptions); |
| for (const summary of [this._testEnvAotSummaries, ...this._aotSummaries]) { |
| this._compiler.loadAotSummaries(summary); |
| } |
| this._moduleOverrides.forEach((entry) => this._compiler.overrideModule(entry[0], entry[1])); |
| this._componentOverrides.forEach((entry) => this._compiler.overrideComponent(entry[0], entry[1])); |
| this._directiveOverrides.forEach((entry) => this._compiler.overrideDirective(entry[0], entry[1])); |
| this._pipeOverrides.forEach((entry) => this._compiler.overridePipe(entry[0], entry[1])); |
| return DynamicTestModule; |
| } |
| _assertNotInstantiated(methodName, methodDescription) { |
| if (this._instantiated) { |
| throw new Error(`Cannot ${methodDescription} when the test module has already been instantiated. ` + |
| `Make sure you are not using \`inject\` before \`${methodName}\`.`); |
| } |
| } |
| inject(token, notFoundValue, flags) { |
| this._initIfNeeded(); |
| if (token === TestBed) { |
| return this; |
| } |
| // Tests can inject things from the ng module and from the compiler, |
| // but the ng module can't inject things from the compiler and vice versa. |
| const UNDEFINED = {}; |
| const result = this._moduleRef.injector.get(token, UNDEFINED, flags); |
| return result === UNDEFINED ? this._compiler.injector.get(token, notFoundValue, flags) : |
| result; |
| } |
| /** @deprecated from v9.0.0 use TestBed.inject */ |
| get(token, notFoundValue = Injector.THROW_IF_NOT_FOUND, flags = InjectFlags.Default) { |
| return this.inject(token, notFoundValue, flags); |
| } |
| execute(tokens, fn, context) { |
| this._initIfNeeded(); |
| const params = tokens.map(t => this.inject(t)); |
| return fn.apply(context, params); |
| } |
| overrideModule(ngModule, override) { |
| this._assertNotInstantiated('overrideModule', 'override module metadata'); |
| this._moduleOverrides.push([ngModule, override]); |
| } |
| overrideComponent(component, override) { |
| this._assertNotInstantiated('overrideComponent', 'override component metadata'); |
| this._componentOverrides.push([component, override]); |
| } |
| overrideDirective(directive, override) { |
| this._assertNotInstantiated('overrideDirective', 'override directive metadata'); |
| this._directiveOverrides.push([directive, override]); |
| } |
| overridePipe(pipe, override) { |
| this._assertNotInstantiated('overridePipe', 'override pipe metadata'); |
| this._pipeOverrides.push([pipe, override]); |
| } |
| overrideProvider(token, provider) { |
| this._assertNotInstantiated('overrideProvider', 'override provider'); |
| this.overrideProviderImpl(token, provider); |
| } |
| overrideProviderImpl(token, provider, deprecated = false) { |
| let def = null; |
| if (typeof token !== 'string' && (def = ɵgetInjectableDef(token)) && def.providedIn === 'root') { |
| if (provider.useFactory) { |
| this._rootProviderOverrides.push({ provide: token, useFactory: provider.useFactory, deps: provider.deps || [] }); |
| } |
| else { |
| this._rootProviderOverrides.push({ provide: token, useValue: provider.useValue }); |
| } |
| } |
| let flags = 0; |
| let value; |
| if (provider.useFactory) { |
| flags |= 1024 /* TypeFactoryProvider */; |
| value = provider.useFactory; |
| } |
| else { |
| flags |= 256 /* TypeValueProvider */; |
| value = provider.useValue; |
| } |
| const deps = (provider.deps || []).map((dep) => { |
| let depFlags = 0 /* None */; |
| let depToken; |
| if (Array.isArray(dep)) { |
| dep.forEach((entry) => { |
| if (entry instanceof Optional) { |
| depFlags |= 2 /* Optional */; |
| } |
| else if (entry instanceof SkipSelf) { |
| depFlags |= 1 /* SkipSelf */; |
| } |
| else { |
| depToken = entry; |
| } |
| }); |
| } |
| else { |
| depToken = dep; |
| } |
| return [depFlags, depToken]; |
| }); |
| ɵoverrideProvider({ token, flags, deps, value, deprecatedBehavior: deprecated }); |
| } |
| overrideTemplateUsingTestingModule(component, template) { |
| this._assertNotInstantiated('overrideTemplateUsingTestingModule', 'override template'); |
| class OverrideComponent { |
| } |
| OverrideComponent.decorators = [ |
| { type: Component, args: [{ selector: 'empty', template, jit: true },] } |
| ]; |
| this._templateOverrides.push({ component, templateOf: OverrideComponent }); |
| } |
| createComponent(component) { |
| this._initIfNeeded(); |
| const componentFactory = this._compiler.getComponentFactory(component); |
| if (!componentFactory) { |
| throw new Error(`Cannot create the component ${ɵstringify(component)} as it was not imported into the testing module!`); |
| } |
| // TODO: Don't cast as `InjectionToken<boolean>`, declared type is boolean[] |
| const noNgZone = this.inject(ComponentFixtureNoNgZone, false); |
| // TODO: Don't cast as `InjectionToken<boolean>`, declared type is boolean[] |
| const autoDetect = this.inject(ComponentFixtureAutoDetect, false); |
| const ngZone = noNgZone ? null : this.inject(NgZone, null); |
| const testComponentRenderer = this.inject(TestComponentRenderer); |
| const rootElId = `root${_nextRootElementId$1++}`; |
| testComponentRenderer.insertRootElement(rootElId); |
| const initComponent = () => { |
| const componentRef = componentFactory.create(Injector.NULL, [], `#${rootElId}`, this._moduleRef); |
| return new ComponentFixture(componentRef, ngZone, autoDetect); |
| }; |
| const fixture = !ngZone ? initComponent() : ngZone.run(initComponent); |
| this._activeFixtures.push(fixture); |
| return fixture; |
| } |
| } |
| /** |
| * @description |
| * Configures and initializes environment for unit testing and provides methods for |
| * creating components and services in unit tests. |
| * |
| * `TestBed` is the primary api for writing unit tests for Angular applications and libraries. |
| * |
| * Note: Use `TestBed` in tests. It will be set to either `TestBedViewEngine` or `TestBedRender3` |
| * according to the compiler used. |
| * |
| * @publicApi |
| */ |
| const TestBed = ɵivyEnabled ? TestBedRender3 : TestBedViewEngine; |
| /** |
| * Returns a singleton of the applicable `TestBed`. |
| * |
| * It will be either an instance of `TestBedViewEngine` or `TestBedRender3`. |
| * |
| * @publicApi |
| */ |
| const getTestBed = ɵivyEnabled ? _getTestBedRender3 : _getTestBedViewEngine; |
| let testBed$1; |
| function _getTestBedViewEngine() { |
| return testBed$1 = testBed$1 || new TestBedViewEngine(); |
| } |
| /** |
| * Allows injecting dependencies in `beforeEach()` and `it()`. |
| * |
| * Example: |
| * |
| * ``` |
| * beforeEach(inject([Dependency, AClass], (dep, object) => { |
| * // some code that uses `dep` and `object` |
| * // ... |
| * })); |
| * |
| * it('...', inject([AClass], (object) => { |
| * object.doSomething(); |
| * expect(...); |
| * }) |
| * ``` |
| * |
| * Notes: |
| * - inject is currently a function because of some Traceur limitation the syntax should |
| * eventually |
| * becomes `it('...', @Inject (object: AClass, async: AsyncTestCompleter) => { ... });` |
| * |
| * @publicApi |
| */ |
| function inject(tokens, fn) { |
| const testBed = getTestBed(); |
| if (tokens.indexOf(AsyncTestCompleter) >= 0) { |
| // Not using an arrow function to preserve context passed from call site |
| return function () { |
| // Return an async test method that returns a Promise if AsyncTestCompleter is one of |
| // the injected tokens. |
| return testBed.compileComponents().then(() => { |
| const completer = testBed.inject(AsyncTestCompleter); |
| testBed.execute(tokens, fn, this); |
| return completer.promise; |
| }); |
| }; |
| } |
| else { |
| // Not using an arrow function to preserve context passed from call site |
| return function () { |
| return testBed.execute(tokens, fn, this); |
| }; |
| } |
| } |
| /** |
| * @publicApi |
| */ |
| class InjectSetupWrapper { |
| constructor(_moduleDef) { |
| this._moduleDef = _moduleDef; |
| } |
| _addModule() { |
| const moduleDef = this._moduleDef(); |
| if (moduleDef) { |
| getTestBed().configureTestingModule(moduleDef); |
| } |
| } |
| inject(tokens, fn) { |
| const self = this; |
| // Not using an arrow function to preserve context passed from call site |
| return function () { |
| self._addModule(); |
| return inject(tokens, fn).call(this); |
| }; |
| } |
| } |
| function withModule(moduleDef, fn) { |
| if (fn) { |
| // Not using an arrow function to preserve context passed from call site |
| return function () { |
| const testBed = getTestBed(); |
| if (moduleDef) { |
| testBed.configureTestingModule(moduleDef); |
| } |
| return fn.apply(this); |
| }; |
| } |
| return new InjectSetupWrapper(() => moduleDef); |
| } |
| |
| /** |
| * @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 |
| */ |
| const _global = (typeof window === 'undefined' ? global : window); |
| // Reset the test providers and the fake async zone before each test. |
| if (_global.beforeEach) { |
| _global.beforeEach(() => { |
| TestBed.resetTestingModule(); |
| resetFakeAsyncZone(); |
| }); |
| } |
| /** |
| * This API should be removed. But doing so seems to break `google3` and so it requires a bit of |
| * investigation. |
| * |
| * A work around is to mark it as `@codeGenApi` for now and investigate later. |
| * |
| * @codeGenApi |
| */ |
| // TODO(iminar): Remove this code in a safe way. |
| const __core_private_testing_placeholder__ = ''; |
| |
| /** |
| * @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 |
| */ |
| |
| /** |
| * @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 { ComponentFixture, ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, InjectSetupWrapper, TestBed, TestComponentRenderer, __core_private_testing_placeholder__, async, discardPeriodicTasks, fakeAsync, flush, flushMicrotasks, getTestBed, inject, resetFakeAsyncZone, tick, waitForAsync, withModule, MetadataOverrider as ɵMetadataOverrider, TestingCompiler as ɵTestingCompiler, TestingCompilerFactory as ɵTestingCompilerFactory, TestBedViewEngine as ɵangular_packages_core_testing_testing_a, TestBedRender3 as ɵangular_packages_core_testing_testing_b, _getTestBedRender3 as ɵangular_packages_core_testing_testing_c }; |
| //# sourceMappingURL=testing.js.map |