diff --git a/.gitignore b/.gitignore index 7b21aa525..83f0e2ce0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ tags !/nativescript-angular/postinstall.js !/nativescript-angular/hooks/**/*.js !/nativescript-angular/gulpfile.js -!/nativescript-angular/zone-js/**/*.js +!/nativescript-angular/zone-js/dist/*.js .tscache .nvm diff --git a/nativescript-angular/package.json b/nativescript-angular/package.json index f3fc81f8e..9ee50c793 100644 --- a/nativescript-angular/package.json +++ b/nativescript-angular/package.json @@ -53,7 +53,7 @@ "@angular/router": "~5.1.0", "rxjs": "^5.5.0", "tns-core-modules": "^3.1.0 || >3.4.0-", - "zone.js": "^0.8.4", + "zone.js": "*", "typescript": "~2.4.2" }, "devDependencies": { @@ -71,7 +71,6 @@ "rxjs": "^5.5.0", "tns-core-modules": "next", "tslint": "^5.5.0", - "typescript": "~2.4.2", - "zone.js": "^0.8.12" + "typescript": "~2.4.2" } } diff --git a/nativescript-angular/renderer.ts b/nativescript-angular/renderer.ts index 405f1c91d..4330e58a5 100644 --- a/nativescript-angular/renderer.ts +++ b/nativescript-angular/renderer.ts @@ -5,7 +5,7 @@ import { } from "@angular/core"; import { Device } from "tns-core-modules/platform"; -import { View } from "tns-core-modules/ui/core/view"; +import { View, getViewById } from "tns-core-modules/ui/core/view"; import { addCss } from "tns-core-modules/application"; import { topmost } from "tns-core-modules/ui/frame"; import { profile } from "tns-core-modules/profiling"; @@ -110,6 +110,10 @@ export class NativeScriptRenderer extends Renderer2 { @profile selectRootElement(selector: string): NgView { traceLog("NativeScriptRenderer.selectRootElement: " + selector); + if (selector && selector[0] === "#") { + const result = getViewById(this.rootView, selector.slice(1)); + return (result || this.rootView) as NgView; + } return this.rootView; } diff --git a/nativescript-angular/testing/index.ts b/nativescript-angular/testing/index.ts new file mode 100644 index 000000000..d8d00bbe6 --- /dev/null +++ b/nativescript-angular/testing/index.ts @@ -0,0 +1,29 @@ +import { NgModule } from "@angular/core"; +import { TestComponentRenderer } from "@angular/core/testing"; +import { NativeScriptTestComponentRenderer } from "./src/nativescript_test_component_renderer"; +import { COMMON_PROVIDERS } from "../platform-common"; +import { APP_ROOT_VIEW } from "../platform-providers"; +import { testingRootView } from "./src/util"; +export * from "./src/util"; + +/** + * Providers array is exported for cases where a custom module has to be constructed + * to test a particular piece of code. This can happen, for example, if you are trying + * to test dynamic component loading and need to specify an entryComponent for the testing + * module. + */ +export const NATIVESCRIPT_TESTING_PROVIDERS: any[] = [ + COMMON_PROVIDERS, + {provide: APP_ROOT_VIEW, useFactory: testingRootView}, + {provide: TestComponentRenderer, useClass: NativeScriptTestComponentRenderer}, +]; + +/** + * NativeScript testing support module. Enables use of TestBed for angular components, directives, + * pipes, and services. + */ +@NgModule({ + providers: NATIVESCRIPT_TESTING_PROVIDERS +}) +export class NativeScriptTestingModule { +} diff --git a/nativescript-angular/testing/src/nativescript_test_component_renderer.ts b/nativescript-angular/testing/src/nativescript_test_component_renderer.ts new file mode 100644 index 000000000..5cac9a488 --- /dev/null +++ b/nativescript-angular/testing/src/nativescript_test_component_renderer.ts @@ -0,0 +1,24 @@ +import { Injectable } from "@angular/core"; +import { TestComponentRenderer } from "@angular/core/testing"; +import { topmost } from "tns-core-modules/ui/frame"; +import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base"; +import { ProxyViewContainer } from "tns-core-modules/ui/proxy-view-container"; + +/** + * A NativeScript based implementation of the TestComponentRenderer. + */ +@Injectable() +export class NativeScriptTestComponentRenderer extends TestComponentRenderer { + + insertRootElement(rootElId: string) { + const page = topmost().currentPage; + + const layout = new ProxyViewContainer(); + layout.id = rootElId; + + const rootLayout = page.layoutView as LayoutBase; + rootLayout.addChild(layout); + } + +} + diff --git a/nativescript-angular/testing/src/util.ts b/nativescript-angular/testing/src/util.ts new file mode 100644 index 000000000..52859e89e --- /dev/null +++ b/nativescript-angular/testing/src/util.ts @@ -0,0 +1,165 @@ + +import { View } from "tns-core-modules/ui/core/view"; +import { topmost } from "tns-core-modules/ui/frame"; +import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { NgModule, Type } from "@angular/core"; +import { NativeScriptModule } from "../../nativescript.module"; +import { platformBrowserDynamicTesting } from "@angular/platform-browser-dynamic/testing"; +import { NS_COMPILER_PROVIDERS } from "../../platform"; +import { NATIVESCRIPT_TESTING_PROVIDERS, NativeScriptTestingModule } from "../index"; +import { CommonModule } from "@angular/common"; +/** + * Get a reference to the root application view. + */ +export function testingRootView(): View { + return topmost().currentPage.content; +} + +/** + * Declared test contexts. When the suite is done this map should be empty if all lifecycle + * calls have happened as expected. + * @private + */ +const activeTestFixtures: ComponentFixture[][] = []; + +/** + * Return a promise that resolves after (durationMs) milliseconds + */ +export function promiseWait(durationMs: number) { + return () => new Promise((resolve) => setTimeout(() => resolve(), durationMs)); +} + +/** + * Perform basic TestBed environment initialization. Call this once in the main entry point to your tests. + */ +export function nsTestBedInit() { + TestBed.initTestEnvironment( + NativeScriptTestingModule, + platformBrowserDynamicTesting(NS_COMPILER_PROVIDERS) + ); +} + +/** + * Helper for configuring a TestBed instance for rendering components for test. Ideally this + * would not be needed, and in truth it's just a wrapper to eliminate some boilerplate. It + * exists because when you need to specify `entryComponents` for a test the setup becomes quite + * a bit more complex than if you're just doing a basic component test. + * + * More about entryComponents complexity: https://github.com/angular/angular/issues/12079 + * + * Use: + * ``` + * beforeEach(nsTestBedBeforeEach([MyComponent,MyFailComponent])); + * ``` + * + * **NOTE*** Remember to pair with {@see nsTestBedAfterEach} + * + * @param components Any components that you will create during the test + * @param providers Any services your tests depend on + * @param imports Any module imports your tests depend on + * @param entryComponents Any entry components that your tests depend on + */ +export function nsTestBedBeforeEach( + components: any[], + providers: any[] = [], + imports: any[] = [], + entryComponents: any[] = []) { + return (done) => { + activeTestFixtures.push([]); + // If there are no entry components we can take the simple path. + if (entryComponents.length === 0) { + TestBed.configureTestingModule({ + declarations: [...components], + providers: [...providers], + imports: [NativeScriptModule, ...imports] + }); + } else { + // If there are entry components, we have to reset the testing platform. + // + // There's got to be a better way... (o_O) + TestBed.resetTestEnvironment(); + @NgModule({ + declarations: entryComponents, + exports: entryComponents, + entryComponents: entryComponents + }) + class EntryComponentsTestModule { + } + TestBed.initTestEnvironment( + EntryComponentsTestModule, + platformBrowserDynamicTesting(NS_COMPILER_PROVIDERS) + ); + TestBed.configureTestingModule({ + declarations: components, + imports: [ + NativeScriptModule, NativeScriptTestingModule, CommonModule, + ...imports + ], + providers: [...providers, ...NATIVESCRIPT_TESTING_PROVIDERS], + }); + } + TestBed.compileComponents() + .then(() => done()) + .catch((e) => { + console.log(`Failed to instantiate test component with error: ${e}`); + console.log(e.stack); + done(); + }); + }; +} + +/** + * Helper for a basic component TestBed clean up. + * @param resetEnv When true the testing environment will be reset + * @param resetFn When resetting the environment, use this init function + */ +export function nsTestBedAfterEach(resetEnv = true, resetFn = nsTestBedInit) { + return () => { + if (activeTestFixtures.length === 0) { + throw new Error( + `There are no more declared fixtures.` + + `Did you call "nsTestBedBeforeEach" and "nsTestBedAfterEach" an equal number of times?` + ); + } + const root = testingRootView() as LayoutBase; + const fixtures = activeTestFixtures.pop(); + fixtures.forEach((fixture) => { + root.removeChild(fixture.nativeElement); + fixture.destroy(); + }); + TestBed.resetTestingModule(); + if (resetEnv) { + TestBed.resetTestEnvironment(); + resetFn(); + } + }; +} + +/** + * Render a component using the TestBed helper, and return a promise that resolves when the + * ComponentFixture is fully initialized. + */ +export function nsTestBedRender(componentType: Type): Promise> { + const fixture = TestBed.createComponent(componentType); + fixture.detectChanges(); + return fixture.whenRenderingDone() + // TODO(jd): it seems that the whenStable and whenRenderingDone utilities of ComponentFixture + // do not work as expected. I looked at how to fix it and it's not clear how to provide + // a {N} specific subclass, because ComponentFixture is newed directly rather than injected + // What to do about it? Maybe fakeAsync can help? For now just setTimeout for 100ms (x_X) + .then(promiseWait(100)) + .then(() => { + const list = activeTestFixtures[activeTestFixtures.length - 1]; + if (!list) { + console.warn( + "nsTestBedRender called without nsTestBedBeforeEach/nsTestBedAfter each. " + + "You are responsible for calling 'fixture.destroy()' when your test is done " + + "in order to clean up the components that are created." + ); + } else { + list.push(fixture); + } + return fixture; + }); +} diff --git a/nativescript-angular/zone-js/README.md b/nativescript-angular/zone-js/README.md new file mode 100644 index 000000000..38a0ebc26 --- /dev/null +++ b/nativescript-angular/zone-js/README.md @@ -0,0 +1,10 @@ +Zone.js for NativeScript +--- + +Zone.js is a library that aims to intercept all asynchronous API calls made in an environment, in order +to wrap them into coherent execution contexts over time. + +NativeScript executes inside an environment that Zone.js is not designed to work in, so a custom Zone.js output +must be created. + +Find out more about this in the [Upgrading Zone.js document](../../doc/upgrading-zonejs.md) \ No newline at end of file diff --git a/nativescript-angular/zone-js/dist/zone-nativescript.jasmine.js b/nativescript-angular/zone-js/dist/zone-nativescript.jasmine.js new file mode 100644 index 000000000..ac1cb7093 --- /dev/null +++ b/nativescript-angular/zone-js/dist/zone-nativescript.jasmine.js @@ -0,0 +1,924 @@ +/** +* @license +* Copyright Google Inc. 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 (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + +/** + * @license + * Copyright Google Inc. 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 + */ +/** + * @fileoverview + * @suppress {globalThis} + */ +var NEWLINE = '\n'; +var IGNORE_FRAMES = {}; +var creationTrace = '__creationTrace__'; +var ERROR_TAG = 'STACKTRACE TRACKING'; +var SEP_TAG = '__SEP_TAG__'; +var sepTemplate = ''; +var LongStackTrace = (function () { + function LongStackTrace() { + this.error = getStacktrace(); + this.timestamp = new Date(); + } + return LongStackTrace; +}()); +function getStacktraceWithUncaughtError() { + return new Error(ERROR_TAG); +} +function getStacktraceWithCaughtError() { + try { + throw getStacktraceWithUncaughtError(); + } + catch (err) { + return err; + } +} +// Some implementations of exception handling don't create a stack trace if the exception +// isn't thrown, however it's faster not to actually throw the exception. +var error = getStacktraceWithUncaughtError(); +var caughtError = getStacktraceWithCaughtError(); +var getStacktrace = error.stack ? + getStacktraceWithUncaughtError : + (caughtError.stack ? getStacktraceWithCaughtError : getStacktraceWithUncaughtError); +function getFrames(error) { + return error.stack ? error.stack.split(NEWLINE) : []; +} +function addErrorStack(lines, error) { + var trace = getFrames(error); + for (var i = 0; i < trace.length; i++) { + var frame = trace[i]; + // Filter out the Frames which are part of stack capturing. + if (!IGNORE_FRAMES.hasOwnProperty(frame)) { + lines.push(trace[i]); + } + } +} +function renderLongStackTrace(frames, stack) { + var longTrace = [stack.trim()]; + if (frames) { + var timestamp = new Date().getTime(); + for (var i = 0; i < frames.length; i++) { + var traceFrames = frames[i]; + var lastTime = traceFrames.timestamp; + var separator = "____________________Elapsed " + (timestamp - lastTime.getTime()) + " ms; At: " + lastTime; + separator = separator.replace(/[^\w\d]/g, '_'); + longTrace.push(sepTemplate.replace(SEP_TAG, separator)); + addErrorStack(longTrace, traceFrames.error); + timestamp = lastTime.getTime(); + } + } + return longTrace.join(NEWLINE); +} +Zone['longStackTraceZoneSpec'] = { + name: 'long-stack-trace', + longStackTraceLimit: 10, + // add a getLongStackTrace method in spec to + // handle handled reject promise error. + getLongStackTrace: function (error) { + if (!error) { + return undefined; + } + var task = error[Zone.__symbol__('currentTask')]; + var trace = task && task.data && task.data[creationTrace]; + if (!trace) { + return error.stack; + } + return renderLongStackTrace(trace, error.stack); + }, + onScheduleTask: function (parentZoneDelegate, currentZone, targetZone, task) { + var currentTask = Zone.currentTask; + var trace = currentTask && currentTask.data && currentTask.data[creationTrace] || []; + trace = [new LongStackTrace()].concat(trace); + if (trace.length > this.longStackTraceLimit) { + trace.length = this.longStackTraceLimit; + } + if (!task.data) + task.data = {}; + task.data[creationTrace] = trace; + return parentZoneDelegate.scheduleTask(targetZone, task); + }, + onHandleError: function (parentZoneDelegate, currentZone, targetZone, error) { + var parentTask = Zone.currentTask || error.task; + if (error instanceof Error && parentTask) { + var longStack = renderLongStackTrace(parentTask.data && parentTask.data[creationTrace], error.stack); + try { + error.stack = error.longStack = longStack; + } + catch (err) { + } + } + return parentZoneDelegate.handleError(targetZone, error); + } +}; +function captureStackTraces(stackTraces, count) { + if (count > 0) { + stackTraces.push(getFrames((new LongStackTrace()).error)); + captureStackTraces(stackTraces, count - 1); + } +} +function computeIgnoreFrames() { + var frames = []; + captureStackTraces(frames, 2); + var frames1 = frames[0]; + var frames2 = frames[1]; + for (var i = 0; i < frames1.length; i++) { + var frame1 = frames1[i]; + var frame2 = frames2[i]; + if (!sepTemplate && frame1.indexOf(ERROR_TAG) == -1) { + sepTemplate = frame1.replace(/^(\s*(at)?\s*)([\w\/\<]+)/, '$1' + SEP_TAG); + } + if (frame1 === frame2) { + IGNORE_FRAMES[frame1] = true; + } + else { + break; + } + console.log('>>>>>>', sepTemplate, frame1); + } + if (!sepTemplate) { + // If we could not find it default to this text. + sepTemplate = SEP_TAG + '@[native code]'; + } +} +computeIgnoreFrames(); + +/** + * @license + * Copyright Google Inc. 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 ProxyZoneSpec = (function () { + function ProxyZoneSpec(defaultSpecDelegate) { + if (defaultSpecDelegate === void 0) { defaultSpecDelegate = null; } + this.defaultSpecDelegate = defaultSpecDelegate; + this.name = 'ProxyZone'; + this.properties = { 'ProxyZoneSpec': this }; + this.propertyKeys = null; + this.setDelegate(defaultSpecDelegate); + } + ProxyZoneSpec.get = function () { + return Zone.current.get('ProxyZoneSpec'); + }; + ProxyZoneSpec.isLoaded = function () { + return ProxyZoneSpec.get() instanceof ProxyZoneSpec; + }; + ProxyZoneSpec.assertPresent = function () { + if (!this.isLoaded()) { + throw new Error("Expected to be running in 'ProxyZone', but it was not found."); + } + return ProxyZoneSpec.get(); + }; + ProxyZoneSpec.prototype.setDelegate = function (delegateSpec) { + var _this = this; + this._delegateSpec = delegateSpec; + this.propertyKeys && this.propertyKeys.forEach(function (key) { return delete _this.properties[key]; }); + this.propertyKeys = null; + if (delegateSpec && delegateSpec.properties) { + this.propertyKeys = Object.keys(delegateSpec.properties); + this.propertyKeys.forEach(function (k) { return _this.properties[k] = delegateSpec.properties[k]; }); + } + }; + ProxyZoneSpec.prototype.getDelegate = function () { + return this._delegateSpec; + }; + ProxyZoneSpec.prototype.resetDelegate = function () { + this.setDelegate(this.defaultSpecDelegate); + }; + ProxyZoneSpec.prototype.onFork = function (parentZoneDelegate, currentZone, targetZone, zoneSpec) { + if (this._delegateSpec && this._delegateSpec.onFork) { + return this._delegateSpec.onFork(parentZoneDelegate, currentZone, targetZone, zoneSpec); + } + else { + return parentZoneDelegate.fork(targetZone, zoneSpec); + } + }; + ProxyZoneSpec.prototype.onIntercept = function (parentZoneDelegate, currentZone, targetZone, delegate, source) { + if (this._delegateSpec && this._delegateSpec.onIntercept) { + return this._delegateSpec.onIntercept(parentZoneDelegate, currentZone, targetZone, delegate, source); + } + else { + return parentZoneDelegate.intercept(targetZone, delegate, source); + } + }; + ProxyZoneSpec.prototype.onInvoke = function (parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) { + if (this._delegateSpec && this._delegateSpec.onInvoke) { + return this._delegateSpec.onInvoke(parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source); + } + else { + return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source); + } + }; + ProxyZoneSpec.prototype.onHandleError = function (parentZoneDelegate, currentZone, targetZone, error) { + if (this._delegateSpec && this._delegateSpec.onHandleError) { + return this._delegateSpec.onHandleError(parentZoneDelegate, currentZone, targetZone, error); + } + else { + return parentZoneDelegate.handleError(targetZone, error); + } + }; + ProxyZoneSpec.prototype.onScheduleTask = function (parentZoneDelegate, currentZone, targetZone, task) { + if (this._delegateSpec && this._delegateSpec.onScheduleTask) { + return this._delegateSpec.onScheduleTask(parentZoneDelegate, currentZone, targetZone, task); + } + else { + return parentZoneDelegate.scheduleTask(targetZone, task); + } + }; + ProxyZoneSpec.prototype.onInvokeTask = function (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) { + if (this._delegateSpec && this._delegateSpec.onFork) { + return this._delegateSpec.onInvokeTask(parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs); + } + else { + return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs); + } + }; + ProxyZoneSpec.prototype.onCancelTask = function (parentZoneDelegate, currentZone, targetZone, task) { + if (this._delegateSpec && this._delegateSpec.onCancelTask) { + return this._delegateSpec.onCancelTask(parentZoneDelegate, currentZone, targetZone, task); + } + else { + return parentZoneDelegate.cancelTask(targetZone, task); + } + }; + ProxyZoneSpec.prototype.onHasTask = function (delegate, current, target, hasTaskState) { + if (this._delegateSpec && this._delegateSpec.onHasTask) { + this._delegateSpec.onHasTask(delegate, current, target, hasTaskState); + } + else { + delegate.hasTask(target, hasTaskState); + } + }; + return ProxyZoneSpec; +}()); +// Export the class so that new instances can be created with proper +// constructor params. +Zone['ProxyZoneSpec'] = ProxyZoneSpec; + +/** + * @license + * Copyright Google Inc. 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 SyncTestZoneSpec = (function () { + function SyncTestZoneSpec(namePrefix) { + this.runZone = Zone.current; + this.name = 'syncTestZone for ' + namePrefix; + } + SyncTestZoneSpec.prototype.onScheduleTask = function (delegate, current, target, task) { + switch (task.type) { + case 'microTask': + case 'macroTask': + throw new Error("Cannot call " + task.source + " from within a sync test."); + case 'eventTask': + task = delegate.scheduleTask(target, task); + break; + } + return task; + }; + return SyncTestZoneSpec; +}()); +// Export the class so that new instances can be created with proper +// constructor params. +Zone['SyncTestZoneSpec'] = SyncTestZoneSpec; + +/** + * @license + * Copyright Google Inc. 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 AsyncTestZoneSpec = (function () { + function AsyncTestZoneSpec(finishCallback, failCallback, namePrefix) { + this._pendingMicroTasks = false; + this._pendingMacroTasks = false; + this._alreadyErrored = false; + this.runZone = Zone.current; + this._finishCallback = finishCallback; + this._failCallback = failCallback; + this.name = 'asyncTestZone for ' + namePrefix; + } + AsyncTestZoneSpec.prototype._finishCallbackIfDone = function () { + var _this = this; + if (!(this._pendingMicroTasks || this._pendingMacroTasks)) { + // We do this because we would like to catch unhandled rejected promises. + this.runZone.run(function () { + setTimeout(function () { + if (!_this._alreadyErrored && !(_this._pendingMicroTasks || _this._pendingMacroTasks)) { + _this._finishCallback(); + } + }, 0); + }); + } + }; + // Note - we need to use onInvoke at the moment to call finish when a test is + // fully synchronous. TODO(juliemr): remove this when the logic for + // onHasTask changes and it calls whenever the task queues are dirty. + AsyncTestZoneSpec.prototype.onInvoke = function (parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) { + try { + return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source); + } + finally { + this._finishCallbackIfDone(); + } + }; + AsyncTestZoneSpec.prototype.onHandleError = function (parentZoneDelegate, currentZone, targetZone, error) { + // Let the parent try to handle the error. + var result = parentZoneDelegate.handleError(targetZone, error); + if (result) { + this._failCallback(error); + this._alreadyErrored = true; + } + return false; + }; + AsyncTestZoneSpec.prototype.onHasTask = function (delegate, current, target, hasTaskState) { + delegate.hasTask(target, hasTaskState); + if (hasTaskState.change == 'microTask') { + this._pendingMicroTasks = hasTaskState.microTask; + this._finishCallbackIfDone(); + } + else if (hasTaskState.change == 'macroTask') { + this._pendingMacroTasks = hasTaskState.macroTask; + this._finishCallbackIfDone(); + } + }; + return AsyncTestZoneSpec; +}()); +// Export the class so that new instances can be created with proper +// constructor params. +Zone['AsyncTestZoneSpec'] = AsyncTestZoneSpec; + +/** + * @license + * Copyright Google Inc. 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 (global) { + var Scheduler = (function () { + function Scheduler() { + // Next scheduler id. + this.nextId = 0; + // Scheduler queue with the tuple of end time and callback function - sorted by end time. + this._schedulerQueue = []; + // Current simulated time in millis. + this._currentTime = 0; + } + Scheduler.prototype.scheduleFunction = function (cb, delay, args, id) { + if (args === void 0) { args = []; } + if (id === void 0) { id = -1; } + var currentId = id < 0 ? this.nextId++ : id; + var endTime = this._currentTime + delay; + // Insert so that scheduler queue remains sorted by end time. + var newEntry = { endTime: endTime, id: currentId, func: cb, args: args, delay: delay }; + var i = 0; + for (; i < this._schedulerQueue.length; i++) { + var currentEntry = this._schedulerQueue[i]; + if (newEntry.endTime < currentEntry.endTime) { + break; + } + } + this._schedulerQueue.splice(i, 0, newEntry); + return currentId; + }; + Scheduler.prototype.removeScheduledFunctionWithId = function (id) { + for (var i = 0; i < this._schedulerQueue.length; i++) { + if (this._schedulerQueue[i].id == id) { + this._schedulerQueue.splice(i, 1); + break; + } + } + }; + Scheduler.prototype.tick = function (millis) { + if (millis === void 0) { millis = 0; } + var finalTime = this._currentTime + millis; + while (this._schedulerQueue.length > 0) { + var current = this._schedulerQueue[0]; + if (finalTime < current.endTime) { + // Done processing the queue since it's sorted by endTime. + break; + } + else { + // Time to run scheduled function. Remove it from the head of queue. + var current_1 = this._schedulerQueue.shift(); + this._currentTime = current_1.endTime; + var retval = current_1.func.apply(global, current_1.args); + if (!retval) { + // Uncaught exception in the current scheduled function. Stop processing the queue. + break; + } + } + } + this._currentTime = finalTime; + }; + return Scheduler; + }()); + var FakeAsyncTestZoneSpec = (function () { + function FakeAsyncTestZoneSpec(namePrefix) { + this._scheduler = new Scheduler(); + this._microtasks = []; + this._lastError = null; + this._uncaughtPromiseErrors = Promise[Zone.__symbol__('uncaughtPromiseErrors')]; + this.pendingPeriodicTimers = []; + this.pendingTimers = []; + this.properties = { 'FakeAsyncTestZoneSpec': this }; + this.name = 'fakeAsyncTestZone for ' + namePrefix; + } + FakeAsyncTestZoneSpec.assertInZone = function () { + if (Zone.current.get('FakeAsyncTestZoneSpec') == null) { + throw new Error('The code should be running in the fakeAsync zone to call this function'); + } + }; + FakeAsyncTestZoneSpec.prototype._fnAndFlush = function (fn, completers) { + var _this = this; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + fn.apply(global, args); + if (_this._lastError === null) { + if (completers.onSuccess != null) { + completers.onSuccess.apply(global); + } + // Flush microtasks only on success. + _this.flushMicrotasks(); + } + else { + if (completers.onError != null) { + completers.onError.apply(global); + } + } + // Return true if there were no errors, false otherwise. + return _this._lastError === null; + }; + }; + FakeAsyncTestZoneSpec._removeTimer = function (timers, id) { + var index = timers.indexOf(id); + if (index > -1) { + timers.splice(index, 1); + } + }; + FakeAsyncTestZoneSpec.prototype._dequeueTimer = function (id) { + var _this = this; + return function () { + FakeAsyncTestZoneSpec._removeTimer(_this.pendingTimers, id); + }; + }; + FakeAsyncTestZoneSpec.prototype._requeuePeriodicTimer = function (fn, interval, args, id) { + var _this = this; + return function () { + // Requeue the timer callback if it's not been canceled. + if (_this.pendingPeriodicTimers.indexOf(id) !== -1) { + _this._scheduler.scheduleFunction(fn, interval, args, id); + } + }; + }; + FakeAsyncTestZoneSpec.prototype._dequeuePeriodicTimer = function (id) { + var _this = this; + return function () { + FakeAsyncTestZoneSpec._removeTimer(_this.pendingPeriodicTimers, id); + }; + }; + FakeAsyncTestZoneSpec.prototype._setTimeout = function (fn, delay, args) { + var removeTimerFn = this._dequeueTimer(this._scheduler.nextId); + // Queue the callback and dequeue the timer on success and error. + var cb = this._fnAndFlush(fn, { onSuccess: removeTimerFn, onError: removeTimerFn }); + var id = this._scheduler.scheduleFunction(cb, delay, args); + this.pendingTimers.push(id); + return id; + }; + FakeAsyncTestZoneSpec.prototype._clearTimeout = function (id) { + FakeAsyncTestZoneSpec._removeTimer(this.pendingTimers, id); + this._scheduler.removeScheduledFunctionWithId(id); + }; + FakeAsyncTestZoneSpec.prototype._setInterval = function (fn, interval) { + var args = []; + for (var _i = 2; _i < arguments.length; _i++) { + args[_i - 2] = arguments[_i]; + } + var id = this._scheduler.nextId; + var completers = { onSuccess: null, onError: this._dequeuePeriodicTimer(id) }; + var cb = this._fnAndFlush(fn, completers); + // Use the callback created above to requeue on success. + completers.onSuccess = this._requeuePeriodicTimer(cb, interval, args, id); + // Queue the callback and dequeue the periodic timer only on error. + this._scheduler.scheduleFunction(cb, interval, args); + this.pendingPeriodicTimers.push(id); + return id; + }; + FakeAsyncTestZoneSpec.prototype._clearInterval = function (id) { + FakeAsyncTestZoneSpec._removeTimer(this.pendingPeriodicTimers, id); + this._scheduler.removeScheduledFunctionWithId(id); + }; + FakeAsyncTestZoneSpec.prototype._resetLastErrorAndThrow = function () { + var error = this._lastError || this._uncaughtPromiseErrors[0]; + this._uncaughtPromiseErrors.length = 0; + this._lastError = null; + throw error; + }; + FakeAsyncTestZoneSpec.prototype.tick = function (millis) { + if (millis === void 0) { millis = 0; } + FakeAsyncTestZoneSpec.assertInZone(); + this.flushMicrotasks(); + this._scheduler.tick(millis); + if (this._lastError !== null) { + this._resetLastErrorAndThrow(); + } + }; + FakeAsyncTestZoneSpec.prototype.flushMicrotasks = function () { + var _this = this; + FakeAsyncTestZoneSpec.assertInZone(); + var flushErrors = function () { + if (_this._lastError !== null || _this._uncaughtPromiseErrors.length) { + // If there is an error stop processing the microtask queue and rethrow the error. + _this._resetLastErrorAndThrow(); + } + }; + while (this._microtasks.length > 0) { + var microtask = this._microtasks.shift(); + microtask(); + } + flushErrors(); + }; + FakeAsyncTestZoneSpec.prototype.onScheduleTask = function (delegate, current, target, task) { + switch (task.type) { + case 'microTask': + this._microtasks.push(task.invoke); + break; + case 'macroTask': + switch (task.source) { + case 'setTimeout': + task.data['handleId'] = + this._setTimeout(task.invoke, task.data['delay'], task.data['args']); + break; + case 'setInterval': + task.data['handleId'] = + this._setInterval(task.invoke, task.data['delay'], task.data['args']); + break; + case 'XMLHttpRequest.send': + throw new Error('Cannot make XHRs from within a fake async test.'); + default: + task = delegate.scheduleTask(target, task); + } + break; + case 'eventTask': + task = delegate.scheduleTask(target, task); + break; + } + return task; + }; + FakeAsyncTestZoneSpec.prototype.onCancelTask = function (delegate, current, target, task) { + switch (task.source) { + case 'setTimeout': + return this._clearTimeout(task.data['handleId']); + case 'setInterval': + return this._clearInterval(task.data['handleId']); + default: + return delegate.cancelTask(target, task); + } + }; + FakeAsyncTestZoneSpec.prototype.onHandleError = function (parentZoneDelegate, currentZone, targetZone, error) { + this._lastError = error; + return false; // Don't propagate error to parent zone. + }; + return FakeAsyncTestZoneSpec; + }()); + // Export the class so that new instances can be created with proper + // constructor params. + Zone['FakeAsyncTestZoneSpec'] = FakeAsyncTestZoneSpec; +})(typeof window === 'object' && window || typeof self === 'object' && self || global); + +/** + * @license + * Copyright Google Inc. 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 `TaskTrackingZoneSpec` allows one to track all outstanding Tasks. + * + * This is useful in tests. For example to see which tasks are preventing a test from completing + * or an automated way of releasing all of the event listeners at the end of the test. + */ +var TaskTrackingZoneSpec = (function () { + function TaskTrackingZoneSpec() { + this.name = 'TaskTrackingZone'; + this.microTasks = []; + this.macroTasks = []; + this.eventTasks = []; + this.properties = { 'TaskTrackingZone': this }; + } + TaskTrackingZoneSpec.get = function () { + return Zone.current.get('TaskTrackingZone'); + }; + TaskTrackingZoneSpec.prototype.getTasksFor = function (type) { + switch (type) { + case 'microTask': + return this.microTasks; + case 'macroTask': + return this.macroTasks; + case 'eventTask': + return this.eventTasks; + } + throw new Error('Unknown task format: ' + type); + }; + TaskTrackingZoneSpec.prototype.onScheduleTask = function (parentZoneDelegate, currentZone, targetZone, task) { + task['creationLocation'] = new Error("Task '" + task.type + "' from '" + task.source + "'."); + var tasks = this.getTasksFor(task.type); + tasks.push(task); + return parentZoneDelegate.scheduleTask(targetZone, task); + }; + TaskTrackingZoneSpec.prototype.onCancelTask = function (parentZoneDelegate, currentZone, targetZone, task) { + var tasks = this.getTasksFor(task.type); + for (var i = 0; i < tasks.length; i++) { + if (tasks[i] == task) { + tasks.splice(i, 1); + break; + } + } + return parentZoneDelegate.cancelTask(targetZone, task); + }; + TaskTrackingZoneSpec.prototype.onInvokeTask = function (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) { + if (task.type === 'eventTask') + return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs); + var tasks = this.getTasksFor(task.type); + for (var i = 0; i < tasks.length; i++) { + if (tasks[i] == task) { + tasks.splice(i, 1); + break; + } + } + return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs); + }; + TaskTrackingZoneSpec.prototype.clearEvents = function () { + while (this.eventTasks.length) { + Zone.current.cancelTask(this.eventTasks[0]); + } + }; + return TaskTrackingZoneSpec; +}()); +// Export the class so that new instances can be created with proper +// constructor params. +Zone['TaskTrackingZoneSpec'] = TaskTrackingZoneSpec; + +/** + * @license + * Copyright Google Inc. 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 (global) { + // Detect and setup WTF. + var wtfTrace = null; + var wtfEvents = null; + var wtfEnabled = (function () { + var wtf = global['wtf']; + if (wtf) { + wtfTrace = wtf.trace; + if (wtfTrace) { + wtfEvents = wtfTrace.events; + return true; + } + } + return false; + })(); + var WtfZoneSpec = (function () { + function WtfZoneSpec() { + this.name = 'WTF'; + } + WtfZoneSpec.prototype.onFork = function (parentZoneDelegate, currentZone, targetZone, zoneSpec) { + var retValue = parentZoneDelegate.fork(targetZone, zoneSpec); + WtfZoneSpec.forkInstance(zonePathName(targetZone), retValue.name); + return retValue; + }; + WtfZoneSpec.prototype.onInvoke = function (parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) { + var scope = WtfZoneSpec.invokeScope[source]; + if (!scope) { + scope = WtfZoneSpec.invokeScope[source] = + wtfEvents.createScope("Zone:invoke:" + source + "(ascii zone)"); + } + return wtfTrace.leaveScope(scope(zonePathName(targetZone)), parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source)); + }; + WtfZoneSpec.prototype.onHandleError = function (parentZoneDelegate, currentZone, targetZone, error) { + return parentZoneDelegate.handleError(targetZone, error); + }; + WtfZoneSpec.prototype.onScheduleTask = function (parentZoneDelegate, currentZone, targetZone, task) { + var key = task.type + ':' + task.source; + var instance = WtfZoneSpec.scheduleInstance[key]; + if (!instance) { + instance = WtfZoneSpec.scheduleInstance[key] = + wtfEvents.createInstance("Zone:schedule:" + key + "(ascii zone, any data)"); + } + var retValue = parentZoneDelegate.scheduleTask(targetZone, task); + instance(zonePathName(targetZone), shallowObj(task.data, 2)); + return retValue; + }; + WtfZoneSpec.prototype.onInvokeTask = function (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) { + var source = task.source; + var scope = WtfZoneSpec.invokeTaskScope[source]; + if (!scope) { + scope = WtfZoneSpec.invokeTaskScope[source] = + wtfEvents.createScope("Zone:invokeTask:" + source + "(ascii zone)"); + } + return wtfTrace.leaveScope(scope(zonePathName(targetZone)), parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs)); + }; + WtfZoneSpec.prototype.onCancelTask = function (parentZoneDelegate, currentZone, targetZone, task) { + var key = task.source; + var instance = WtfZoneSpec.cancelInstance[key]; + if (!instance) { + instance = WtfZoneSpec.cancelInstance[key] = + wtfEvents.createInstance("Zone:cancel:" + key + "(ascii zone, any options)"); + } + var retValue = parentZoneDelegate.cancelTask(targetZone, task); + instance(zonePathName(targetZone), shallowObj(task.data, 2)); + return retValue; + }; + + return WtfZoneSpec; + }()); + WtfZoneSpec.forkInstance = wtfEnabled && wtfEvents.createInstance('Zone:fork(ascii zone, ascii newZone)'); + WtfZoneSpec.scheduleInstance = {}; + WtfZoneSpec.cancelInstance = {}; + WtfZoneSpec.invokeScope = {}; + WtfZoneSpec.invokeTaskScope = {}; + function shallowObj(obj, depth) { + if (!depth) + return null; + var out = {}; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + var value = obj[key]; + switch (typeof value) { + case 'object': + var name_1 = value && value.constructor && value.constructor.name; + value = name_1 == Object.name ? shallowObj(value, depth - 1) : name_1; + break; + case 'function': + value = value.name || undefined; + break; + } + out[key] = value; + } + } + return out; + } + function zonePathName(zone) { + var name = zone.name; + zone = zone.parent; + while (zone != null) { + name = zone.name + '::' + name; + zone = zone.parent; + } + return name; + } + Zone['wtfZoneSpec'] = !wtfEnabled ? null : new WtfZoneSpec(); +})(typeof window === 'object' && window || typeof self === 'object' && self || global); + +/** + * @license + * Copyright Google Inc. 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 + */ +'use strict'; +(function () { + var __extends = function (d, b) { + for (var p in b) + if (b.hasOwnProperty(p)) + d[p] = b[p]; + function __() { + this.constructor = d; + } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + // Patch jasmine's describe/it/beforeEach/afterEach functions so test code always runs + // in a testZone (ProxyZone). (See: angular/zone.js#91 & angular/angular#10503) + if (!Zone) + throw new Error('Missing: zone.js'); + if (typeof jasmine == 'undefined') + throw new Error('Missing: jasmine.js'); + if (jasmine['__zone_patch__']) + throw new Error('\'jasmine\' has already been patched with \'Zone\'.'); + jasmine['__zone_patch__'] = true; + var SyncTestZoneSpec = Zone['SyncTestZoneSpec']; + var ProxyZoneSpec = Zone['ProxyZoneSpec']; + if (!SyncTestZoneSpec) + throw new Error('Missing: SyncTestZoneSpec'); + if (!ProxyZoneSpec) + throw new Error('Missing: ProxyZoneSpec'); + var ambientZone = Zone.current; + // Create a synchronous-only zone in which to run `describe` blocks in order to raise an + // error if any asynchronous operations are attempted inside of a `describe` but outside of + // a `beforeEach` or `it`. + var syncZone = ambientZone.fork(new SyncTestZoneSpec('jasmine.describe')); + // This is the zone which will be used for running individual tests. + // It will be a proxy zone, so that the tests function can retroactively install + // different zones. + // Example: + // - In beforeEach() do childZone = Zone.current.fork(...); + // - In it() try to do fakeAsync(). The issue is that because the beforeEach forked the + // zone outside of fakeAsync it will be able to escope the fakeAsync rules. + // - Because ProxyZone is parent fo `childZone` fakeAsync can retroactively add + // fakeAsync behavior to the childZone. + var testProxyZone = null; + // Monkey patch all of the jasmine DSL so that each function runs in appropriate zone. + var jasmineEnv = jasmine.getEnv(); + ['describe', 'xdescribe', 'fdescribe'].forEach(function (methodName) { + var originalJasmineFn = jasmineEnv[methodName]; + jasmineEnv[methodName] = function (description, specDefinitions) { + return originalJasmineFn.call(this, description, wrapDescribeInZone(specDefinitions)); + }; + }); + ['it', 'xit', 'fit'].forEach(function (methodName) { + var originalJasmineFn = jasmineEnv[methodName]; + jasmineEnv[methodName] = function (description, specDefinitions, timeout) { + arguments[1] = wrapTestInZone(specDefinitions); + return originalJasmineFn.apply(this, arguments); + }; + }); + ['beforeEach', 'afterEach'].forEach(function (methodName) { + var originalJasmineFn = jasmineEnv[methodName]; + jasmineEnv[methodName] = function (specDefinitions, timeout) { + arguments[0] = wrapTestInZone(specDefinitions); + return originalJasmineFn.apply(this, arguments); + }; + }); + /** + * Gets a function wrapping the body of a Jasmine `describe` block to execute in a + * synchronous-only zone. + */ + function wrapDescribeInZone(describeBody) { + return function () { + return syncZone.run(describeBody, this, arguments); + }; + } + /** + * Gets a function wrapping the body of a Jasmine `it/beforeEach/afterEach` block to + * execute in a ProxyZone zone. + * This will run in `testProxyZone`. The `testProxyZone` will be reset by the `ZoneQueueRunner` + */ + function wrapTestInZone(testBody) { + // The `done` callback is only passed through if the function expects at least one argument. + // Note we have to make a function with correct number of arguments, otherwise jasmine will + // think that all functions are sync or async. + return testBody && (testBody.length ? function (done) { + return testProxyZone.run(testBody, this, [done]); + } : function () { + return testProxyZone.run(testBody, this); + }); + } + var QueueRunner = jasmine.QueueRunner; + jasmine.QueueRunner = (function (_super) { + __extends(ZoneQueueRunner, _super); + function ZoneQueueRunner(attrs) { + attrs.onComplete = (function (fn) { return function () { + // All functions are done, clear the test zone. + testProxyZone = null; + ambientZone.scheduleMicroTask('jasmine.onComplete', fn); + }; })(attrs.onComplete); + _super.call(this, attrs); + } + ZoneQueueRunner.prototype.execute = function () { + var _this = this; + if (Zone.current !== ambientZone) + throw new Error('Unexpected Zone: ' + Zone.current.name); + testProxyZone = ambientZone.fork(new ProxyZoneSpec()); + if (!Zone.currentTask) { + // if we are not running in a task then if someone would register a + // element.addEventListener and then calling element.click() the + // addEventListener callback would think that it is the top most task and would + // drain the microtask queue on element.click() which would be incorrect. + // For this reason we always force a task when running jasmine tests. + Zone.current.scheduleMicroTask('jasmine.execute().forceTask', function () { return QueueRunner.prototype.execute.call(_this); }); + } + else { + _super.prototype.execute.call(this); + } + }; + return ZoneQueueRunner; + }(QueueRunner)); +})(); + +}))); diff --git a/nativescript-angular/zone-js/dist/zone-nativescript.mocha.js b/nativescript-angular/zone-js/dist/zone-nativescript.mocha.js new file mode 100644 index 000000000..0aa13a09f --- /dev/null +++ b/nativescript-angular/zone-js/dist/zone-nativescript.mocha.js @@ -0,0 +1,940 @@ +/** +* @license +* Copyright Google Inc. 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 (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : + typeof define === 'function' && define.amd ? define(factory) : + (factory()); +}(this, (function () { 'use strict'; + +/** + * @license + * Copyright Google Inc. 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 + */ +/** + * @fileoverview + * @suppress {globalThis} + */ +var NEWLINE = '\n'; +var IGNORE_FRAMES = {}; +var creationTrace = '__creationTrace__'; +var ERROR_TAG = 'STACKTRACE TRACKING'; +var SEP_TAG = '__SEP_TAG__'; +var sepTemplate = ''; +var LongStackTrace = (function () { + function LongStackTrace() { + this.error = getStacktrace(); + this.timestamp = new Date(); + } + return LongStackTrace; +}()); +function getStacktraceWithUncaughtError() { + return new Error(ERROR_TAG); +} +function getStacktraceWithCaughtError() { + try { + throw getStacktraceWithUncaughtError(); + } + catch (err) { + return err; + } +} +// Some implementations of exception handling don't create a stack trace if the exception +// isn't thrown, however it's faster not to actually throw the exception. +var error = getStacktraceWithUncaughtError(); +var caughtError = getStacktraceWithCaughtError(); +var getStacktrace = error.stack ? + getStacktraceWithUncaughtError : + (caughtError.stack ? getStacktraceWithCaughtError : getStacktraceWithUncaughtError); +function getFrames(error) { + return error.stack ? error.stack.split(NEWLINE) : []; +} +function addErrorStack(lines, error) { + var trace = getFrames(error); + for (var i = 0; i < trace.length; i++) { + var frame = trace[i]; + // Filter out the Frames which are part of stack capturing. + if (!IGNORE_FRAMES.hasOwnProperty(frame)) { + lines.push(trace[i]); + } + } +} +function renderLongStackTrace(frames, stack) { + var longTrace = [stack.trim()]; + if (frames) { + var timestamp = new Date().getTime(); + for (var i = 0; i < frames.length; i++) { + var traceFrames = frames[i]; + var lastTime = traceFrames.timestamp; + var separator = "____________________Elapsed " + (timestamp - lastTime.getTime()) + " ms; At: " + lastTime; + separator = separator.replace(/[^\w\d]/g, '_'); + longTrace.push(sepTemplate.replace(SEP_TAG, separator)); + addErrorStack(longTrace, traceFrames.error); + timestamp = lastTime.getTime(); + } + } + return longTrace.join(NEWLINE); +} +Zone['longStackTraceZoneSpec'] = { + name: 'long-stack-trace', + longStackTraceLimit: 10, + // add a getLongStackTrace method in spec to + // handle handled reject promise error. + getLongStackTrace: function (error) { + if (!error) { + return undefined; + } + var task = error[Zone.__symbol__('currentTask')]; + var trace = task && task.data && task.data[creationTrace]; + if (!trace) { + return error.stack; + } + return renderLongStackTrace(trace, error.stack); + }, + onScheduleTask: function (parentZoneDelegate, currentZone, targetZone, task) { + var currentTask = Zone.currentTask; + var trace = currentTask && currentTask.data && currentTask.data[creationTrace] || []; + trace = [new LongStackTrace()].concat(trace); + if (trace.length > this.longStackTraceLimit) { + trace.length = this.longStackTraceLimit; + } + if (!task.data) + task.data = {}; + task.data[creationTrace] = trace; + return parentZoneDelegate.scheduleTask(targetZone, task); + }, + onHandleError: function (parentZoneDelegate, currentZone, targetZone, error) { + var parentTask = Zone.currentTask || error.task; + if (error instanceof Error && parentTask) { + var longStack = renderLongStackTrace(parentTask.data && parentTask.data[creationTrace], error.stack); + try { + error.stack = error.longStack = longStack; + } + catch (err) { + } + } + return parentZoneDelegate.handleError(targetZone, error); + } +}; +function captureStackTraces(stackTraces, count) { + if (count > 0) { + stackTraces.push(getFrames((new LongStackTrace()).error)); + captureStackTraces(stackTraces, count - 1); + } +} +function computeIgnoreFrames() { + var frames = []; + captureStackTraces(frames, 2); + var frames1 = frames[0]; + var frames2 = frames[1]; + for (var i = 0; i < frames1.length; i++) { + var frame1 = frames1[i]; + var frame2 = frames2[i]; + if (!sepTemplate && frame1.indexOf(ERROR_TAG) == -1) { + sepTemplate = frame1.replace(/^(\s*(at)?\s*)([\w\/\<]+)/, '$1' + SEP_TAG); + } + if (frame1 === frame2) { + IGNORE_FRAMES[frame1] = true; + } + else { + break; + } + console.log('>>>>>>', sepTemplate, frame1); + } + if (!sepTemplate) { + // If we could not find it default to this text. + sepTemplate = SEP_TAG + '@[native code]'; + } +} +computeIgnoreFrames(); + +/** + * @license + * Copyright Google Inc. 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 ProxyZoneSpec = (function () { + function ProxyZoneSpec(defaultSpecDelegate) { + if (defaultSpecDelegate === void 0) { defaultSpecDelegate = null; } + this.defaultSpecDelegate = defaultSpecDelegate; + this.name = 'ProxyZone'; + this.properties = { 'ProxyZoneSpec': this }; + this.propertyKeys = null; + this.setDelegate(defaultSpecDelegate); + } + ProxyZoneSpec.get = function () { + return Zone.current.get('ProxyZoneSpec'); + }; + ProxyZoneSpec.isLoaded = function () { + return ProxyZoneSpec.get() instanceof ProxyZoneSpec; + }; + ProxyZoneSpec.assertPresent = function () { + if (!this.isLoaded()) { + throw new Error("Expected to be running in 'ProxyZone', but it was not found."); + } + return ProxyZoneSpec.get(); + }; + ProxyZoneSpec.prototype.setDelegate = function (delegateSpec) { + var _this = this; + this._delegateSpec = delegateSpec; + this.propertyKeys && this.propertyKeys.forEach(function (key) { return delete _this.properties[key]; }); + this.propertyKeys = null; + if (delegateSpec && delegateSpec.properties) { + this.propertyKeys = Object.keys(delegateSpec.properties); + this.propertyKeys.forEach(function (k) { return _this.properties[k] = delegateSpec.properties[k]; }); + } + }; + ProxyZoneSpec.prototype.getDelegate = function () { + return this._delegateSpec; + }; + ProxyZoneSpec.prototype.resetDelegate = function () { + this.setDelegate(this.defaultSpecDelegate); + }; + ProxyZoneSpec.prototype.onFork = function (parentZoneDelegate, currentZone, targetZone, zoneSpec) { + if (this._delegateSpec && this._delegateSpec.onFork) { + return this._delegateSpec.onFork(parentZoneDelegate, currentZone, targetZone, zoneSpec); + } + else { + return parentZoneDelegate.fork(targetZone, zoneSpec); + } + }; + ProxyZoneSpec.prototype.onIntercept = function (parentZoneDelegate, currentZone, targetZone, delegate, source) { + if (this._delegateSpec && this._delegateSpec.onIntercept) { + return this._delegateSpec.onIntercept(parentZoneDelegate, currentZone, targetZone, delegate, source); + } + else { + return parentZoneDelegate.intercept(targetZone, delegate, source); + } + }; + ProxyZoneSpec.prototype.onInvoke = function (parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) { + if (this._delegateSpec && this._delegateSpec.onInvoke) { + return this._delegateSpec.onInvoke(parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source); + } + else { + return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source); + } + }; + ProxyZoneSpec.prototype.onHandleError = function (parentZoneDelegate, currentZone, targetZone, error) { + if (this._delegateSpec && this._delegateSpec.onHandleError) { + return this._delegateSpec.onHandleError(parentZoneDelegate, currentZone, targetZone, error); + } + else { + return parentZoneDelegate.handleError(targetZone, error); + } + }; + ProxyZoneSpec.prototype.onScheduleTask = function (parentZoneDelegate, currentZone, targetZone, task) { + if (this._delegateSpec && this._delegateSpec.onScheduleTask) { + return this._delegateSpec.onScheduleTask(parentZoneDelegate, currentZone, targetZone, task); + } + else { + return parentZoneDelegate.scheduleTask(targetZone, task); + } + }; + ProxyZoneSpec.prototype.onInvokeTask = function (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) { + if (this._delegateSpec && this._delegateSpec.onFork) { + return this._delegateSpec.onInvokeTask(parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs); + } + else { + return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs); + } + }; + ProxyZoneSpec.prototype.onCancelTask = function (parentZoneDelegate, currentZone, targetZone, task) { + if (this._delegateSpec && this._delegateSpec.onCancelTask) { + return this._delegateSpec.onCancelTask(parentZoneDelegate, currentZone, targetZone, task); + } + else { + return parentZoneDelegate.cancelTask(targetZone, task); + } + }; + ProxyZoneSpec.prototype.onHasTask = function (delegate, current, target, hasTaskState) { + if (this._delegateSpec && this._delegateSpec.onHasTask) { + this._delegateSpec.onHasTask(delegate, current, target, hasTaskState); + } + else { + delegate.hasTask(target, hasTaskState); + } + }; + return ProxyZoneSpec; +}()); +// Export the class so that new instances can be created with proper +// constructor params. +Zone['ProxyZoneSpec'] = ProxyZoneSpec; + +/** + * @license + * Copyright Google Inc. 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 SyncTestZoneSpec = (function () { + function SyncTestZoneSpec(namePrefix) { + this.runZone = Zone.current; + this.name = 'syncTestZone for ' + namePrefix; + } + SyncTestZoneSpec.prototype.onScheduleTask = function (delegate, current, target, task) { + switch (task.type) { + case 'microTask': + case 'macroTask': + throw new Error("Cannot call " + task.source + " from within a sync test."); + case 'eventTask': + task = delegate.scheduleTask(target, task); + break; + } + return task; + }; + return SyncTestZoneSpec; +}()); +// Export the class so that new instances can be created with proper +// constructor params. +Zone['SyncTestZoneSpec'] = SyncTestZoneSpec; + +/** + * @license + * Copyright Google Inc. 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 AsyncTestZoneSpec = (function () { + function AsyncTestZoneSpec(finishCallback, failCallback, namePrefix) { + this._pendingMicroTasks = false; + this._pendingMacroTasks = false; + this._alreadyErrored = false; + this.runZone = Zone.current; + this._finishCallback = finishCallback; + this._failCallback = failCallback; + this.name = 'asyncTestZone for ' + namePrefix; + } + AsyncTestZoneSpec.prototype._finishCallbackIfDone = function () { + var _this = this; + if (!(this._pendingMicroTasks || this._pendingMacroTasks)) { + // We do this because we would like to catch unhandled rejected promises. + this.runZone.run(function () { + setTimeout(function () { + if (!_this._alreadyErrored && !(_this._pendingMicroTasks || _this._pendingMacroTasks)) { + _this._finishCallback(); + } + }, 0); + }); + } + }; + // Note - we need to use onInvoke at the moment to call finish when a test is + // fully synchronous. TODO(juliemr): remove this when the logic for + // onHasTask changes and it calls whenever the task queues are dirty. + AsyncTestZoneSpec.prototype.onInvoke = function (parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) { + try { + return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source); + } + finally { + this._finishCallbackIfDone(); + } + }; + AsyncTestZoneSpec.prototype.onHandleError = function (parentZoneDelegate, currentZone, targetZone, error) { + // Let the parent try to handle the error. + var result = parentZoneDelegate.handleError(targetZone, error); + if (result) { + this._failCallback(error); + this._alreadyErrored = true; + } + return false; + }; + AsyncTestZoneSpec.prototype.onHasTask = function (delegate, current, target, hasTaskState) { + delegate.hasTask(target, hasTaskState); + if (hasTaskState.change == 'microTask') { + this._pendingMicroTasks = hasTaskState.microTask; + this._finishCallbackIfDone(); + } + else if (hasTaskState.change == 'macroTask') { + this._pendingMacroTasks = hasTaskState.macroTask; + this._finishCallbackIfDone(); + } + }; + return AsyncTestZoneSpec; +}()); +// Export the class so that new instances can be created with proper +// constructor params. +Zone['AsyncTestZoneSpec'] = AsyncTestZoneSpec; + +/** + * @license + * Copyright Google Inc. 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 (global) { + var Scheduler = (function () { + function Scheduler() { + // Next scheduler id. + this.nextId = 0; + // Scheduler queue with the tuple of end time and callback function - sorted by end time. + this._schedulerQueue = []; + // Current simulated time in millis. + this._currentTime = 0; + } + Scheduler.prototype.scheduleFunction = function (cb, delay, args, id) { + if (args === void 0) { args = []; } + if (id === void 0) { id = -1; } + var currentId = id < 0 ? this.nextId++ : id; + var endTime = this._currentTime + delay; + // Insert so that scheduler queue remains sorted by end time. + var newEntry = { endTime: endTime, id: currentId, func: cb, args: args, delay: delay }; + var i = 0; + for (; i < this._schedulerQueue.length; i++) { + var currentEntry = this._schedulerQueue[i]; + if (newEntry.endTime < currentEntry.endTime) { + break; + } + } + this._schedulerQueue.splice(i, 0, newEntry); + return currentId; + }; + Scheduler.prototype.removeScheduledFunctionWithId = function (id) { + for (var i = 0; i < this._schedulerQueue.length; i++) { + if (this._schedulerQueue[i].id == id) { + this._schedulerQueue.splice(i, 1); + break; + } + } + }; + Scheduler.prototype.tick = function (millis) { + if (millis === void 0) { millis = 0; } + var finalTime = this._currentTime + millis; + while (this._schedulerQueue.length > 0) { + var current = this._schedulerQueue[0]; + if (finalTime < current.endTime) { + // Done processing the queue since it's sorted by endTime. + break; + } + else { + // Time to run scheduled function. Remove it from the head of queue. + var current_1 = this._schedulerQueue.shift(); + this._currentTime = current_1.endTime; + var retval = current_1.func.apply(global, current_1.args); + if (!retval) { + // Uncaught exception in the current scheduled function. Stop processing the queue. + break; + } + } + } + this._currentTime = finalTime; + }; + return Scheduler; + }()); + var FakeAsyncTestZoneSpec = (function () { + function FakeAsyncTestZoneSpec(namePrefix) { + this._scheduler = new Scheduler(); + this._microtasks = []; + this._lastError = null; + this._uncaughtPromiseErrors = Promise[Zone.__symbol__('uncaughtPromiseErrors')]; + this.pendingPeriodicTimers = []; + this.pendingTimers = []; + this.properties = { 'FakeAsyncTestZoneSpec': this }; + this.name = 'fakeAsyncTestZone for ' + namePrefix; + } + FakeAsyncTestZoneSpec.assertInZone = function () { + if (Zone.current.get('FakeAsyncTestZoneSpec') == null) { + throw new Error('The code should be running in the fakeAsync zone to call this function'); + } + }; + FakeAsyncTestZoneSpec.prototype._fnAndFlush = function (fn, completers) { + var _this = this; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + fn.apply(global, args); + if (_this._lastError === null) { + if (completers.onSuccess != null) { + completers.onSuccess.apply(global); + } + // Flush microtasks only on success. + _this.flushMicrotasks(); + } + else { + if (completers.onError != null) { + completers.onError.apply(global); + } + } + // Return true if there were no errors, false otherwise. + return _this._lastError === null; + }; + }; + FakeAsyncTestZoneSpec._removeTimer = function (timers, id) { + var index = timers.indexOf(id); + if (index > -1) { + timers.splice(index, 1); + } + }; + FakeAsyncTestZoneSpec.prototype._dequeueTimer = function (id) { + var _this = this; + return function () { + FakeAsyncTestZoneSpec._removeTimer(_this.pendingTimers, id); + }; + }; + FakeAsyncTestZoneSpec.prototype._requeuePeriodicTimer = function (fn, interval, args, id) { + var _this = this; + return function () { + // Requeue the timer callback if it's not been canceled. + if (_this.pendingPeriodicTimers.indexOf(id) !== -1) { + _this._scheduler.scheduleFunction(fn, interval, args, id); + } + }; + }; + FakeAsyncTestZoneSpec.prototype._dequeuePeriodicTimer = function (id) { + var _this = this; + return function () { + FakeAsyncTestZoneSpec._removeTimer(_this.pendingPeriodicTimers, id); + }; + }; + FakeAsyncTestZoneSpec.prototype._setTimeout = function (fn, delay, args) { + var removeTimerFn = this._dequeueTimer(this._scheduler.nextId); + // Queue the callback and dequeue the timer on success and error. + var cb = this._fnAndFlush(fn, { onSuccess: removeTimerFn, onError: removeTimerFn }); + var id = this._scheduler.scheduleFunction(cb, delay, args); + this.pendingTimers.push(id); + return id; + }; + FakeAsyncTestZoneSpec.prototype._clearTimeout = function (id) { + FakeAsyncTestZoneSpec._removeTimer(this.pendingTimers, id); + this._scheduler.removeScheduledFunctionWithId(id); + }; + FakeAsyncTestZoneSpec.prototype._setInterval = function (fn, interval) { + var args = []; + for (var _i = 2; _i < arguments.length; _i++) { + args[_i - 2] = arguments[_i]; + } + var id = this._scheduler.nextId; + var completers = { onSuccess: null, onError: this._dequeuePeriodicTimer(id) }; + var cb = this._fnAndFlush(fn, completers); + // Use the callback created above to requeue on success. + completers.onSuccess = this._requeuePeriodicTimer(cb, interval, args, id); + // Queue the callback and dequeue the periodic timer only on error. + this._scheduler.scheduleFunction(cb, interval, args); + this.pendingPeriodicTimers.push(id); + return id; + }; + FakeAsyncTestZoneSpec.prototype._clearInterval = function (id) { + FakeAsyncTestZoneSpec._removeTimer(this.pendingPeriodicTimers, id); + this._scheduler.removeScheduledFunctionWithId(id); + }; + FakeAsyncTestZoneSpec.prototype._resetLastErrorAndThrow = function () { + var error = this._lastError || this._uncaughtPromiseErrors[0]; + this._uncaughtPromiseErrors.length = 0; + this._lastError = null; + throw error; + }; + FakeAsyncTestZoneSpec.prototype.tick = function (millis) { + if (millis === void 0) { millis = 0; } + FakeAsyncTestZoneSpec.assertInZone(); + this.flushMicrotasks(); + this._scheduler.tick(millis); + if (this._lastError !== null) { + this._resetLastErrorAndThrow(); + } + }; + FakeAsyncTestZoneSpec.prototype.flushMicrotasks = function () { + var _this = this; + FakeAsyncTestZoneSpec.assertInZone(); + var flushErrors = function () { + if (_this._lastError !== null || _this._uncaughtPromiseErrors.length) { + // If there is an error stop processing the microtask queue and rethrow the error. + _this._resetLastErrorAndThrow(); + } + }; + while (this._microtasks.length > 0) { + var microtask = this._microtasks.shift(); + microtask(); + } + flushErrors(); + }; + FakeAsyncTestZoneSpec.prototype.onScheduleTask = function (delegate, current, target, task) { + switch (task.type) { + case 'microTask': + this._microtasks.push(task.invoke); + break; + case 'macroTask': + switch (task.source) { + case 'setTimeout': + task.data['handleId'] = + this._setTimeout(task.invoke, task.data['delay'], task.data['args']); + break; + case 'setInterval': + task.data['handleId'] = + this._setInterval(task.invoke, task.data['delay'], task.data['args']); + break; + case 'XMLHttpRequest.send': + throw new Error('Cannot make XHRs from within a fake async test.'); + default: + task = delegate.scheduleTask(target, task); + } + break; + case 'eventTask': + task = delegate.scheduleTask(target, task); + break; + } + return task; + }; + FakeAsyncTestZoneSpec.prototype.onCancelTask = function (delegate, current, target, task) { + switch (task.source) { + case 'setTimeout': + return this._clearTimeout(task.data['handleId']); + case 'setInterval': + return this._clearInterval(task.data['handleId']); + default: + return delegate.cancelTask(target, task); + } + }; + FakeAsyncTestZoneSpec.prototype.onHandleError = function (parentZoneDelegate, currentZone, targetZone, error) { + this._lastError = error; + return false; // Don't propagate error to parent zone. + }; + return FakeAsyncTestZoneSpec; + }()); + // Export the class so that new instances can be created with proper + // constructor params. + Zone['FakeAsyncTestZoneSpec'] = FakeAsyncTestZoneSpec; +})(typeof window === 'object' && window || typeof self === 'object' && self || global); + +/** + * @license + * Copyright Google Inc. 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 `TaskTrackingZoneSpec` allows one to track all outstanding Tasks. + * + * This is useful in tests. For example to see which tasks are preventing a test from completing + * or an automated way of releasing all of the event listeners at the end of the test. + */ +var TaskTrackingZoneSpec = (function () { + function TaskTrackingZoneSpec() { + this.name = 'TaskTrackingZone'; + this.microTasks = []; + this.macroTasks = []; + this.eventTasks = []; + this.properties = { 'TaskTrackingZone': this }; + } + TaskTrackingZoneSpec.get = function () { + return Zone.current.get('TaskTrackingZone'); + }; + TaskTrackingZoneSpec.prototype.getTasksFor = function (type) { + switch (type) { + case 'microTask': + return this.microTasks; + case 'macroTask': + return this.macroTasks; + case 'eventTask': + return this.eventTasks; + } + throw new Error('Unknown task format: ' + type); + }; + TaskTrackingZoneSpec.prototype.onScheduleTask = function (parentZoneDelegate, currentZone, targetZone, task) { + task['creationLocation'] = new Error("Task '" + task.type + "' from '" + task.source + "'."); + var tasks = this.getTasksFor(task.type); + tasks.push(task); + return parentZoneDelegate.scheduleTask(targetZone, task); + }; + TaskTrackingZoneSpec.prototype.onCancelTask = function (parentZoneDelegate, currentZone, targetZone, task) { + var tasks = this.getTasksFor(task.type); + for (var i = 0; i < tasks.length; i++) { + if (tasks[i] == task) { + tasks.splice(i, 1); + break; + } + } + return parentZoneDelegate.cancelTask(targetZone, task); + }; + TaskTrackingZoneSpec.prototype.onInvokeTask = function (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) { + if (task.type === 'eventTask') + return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs); + var tasks = this.getTasksFor(task.type); + for (var i = 0; i < tasks.length; i++) { + if (tasks[i] == task) { + tasks.splice(i, 1); + break; + } + } + return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs); + }; + TaskTrackingZoneSpec.prototype.clearEvents = function () { + while (this.eventTasks.length) { + Zone.current.cancelTask(this.eventTasks[0]); + } + }; + return TaskTrackingZoneSpec; +}()); +// Export the class so that new instances can be created with proper +// constructor params. +Zone['TaskTrackingZoneSpec'] = TaskTrackingZoneSpec; + +/** + * @license + * Copyright Google Inc. 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 (global) { + // Detect and setup WTF. + var wtfTrace = null; + var wtfEvents = null; + var wtfEnabled = (function () { + var wtf = global['wtf']; + if (wtf) { + wtfTrace = wtf.trace; + if (wtfTrace) { + wtfEvents = wtfTrace.events; + return true; + } + } + return false; + })(); + var WtfZoneSpec = (function () { + function WtfZoneSpec() { + this.name = 'WTF'; + } + WtfZoneSpec.prototype.onFork = function (parentZoneDelegate, currentZone, targetZone, zoneSpec) { + var retValue = parentZoneDelegate.fork(targetZone, zoneSpec); + WtfZoneSpec.forkInstance(zonePathName(targetZone), retValue.name); + return retValue; + }; + WtfZoneSpec.prototype.onInvoke = function (parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) { + var scope = WtfZoneSpec.invokeScope[source]; + if (!scope) { + scope = WtfZoneSpec.invokeScope[source] = + wtfEvents.createScope("Zone:invoke:" + source + "(ascii zone)"); + } + return wtfTrace.leaveScope(scope(zonePathName(targetZone)), parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source)); + }; + WtfZoneSpec.prototype.onHandleError = function (parentZoneDelegate, currentZone, targetZone, error) { + return parentZoneDelegate.handleError(targetZone, error); + }; + WtfZoneSpec.prototype.onScheduleTask = function (parentZoneDelegate, currentZone, targetZone, task) { + var key = task.type + ':' + task.source; + var instance = WtfZoneSpec.scheduleInstance[key]; + if (!instance) { + instance = WtfZoneSpec.scheduleInstance[key] = + wtfEvents.createInstance("Zone:schedule:" + key + "(ascii zone, any data)"); + } + var retValue = parentZoneDelegate.scheduleTask(targetZone, task); + instance(zonePathName(targetZone), shallowObj(task.data, 2)); + return retValue; + }; + WtfZoneSpec.prototype.onInvokeTask = function (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) { + var source = task.source; + var scope = WtfZoneSpec.invokeTaskScope[source]; + if (!scope) { + scope = WtfZoneSpec.invokeTaskScope[source] = + wtfEvents.createScope("Zone:invokeTask:" + source + "(ascii zone)"); + } + return wtfTrace.leaveScope(scope(zonePathName(targetZone)), parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs)); + }; + WtfZoneSpec.prototype.onCancelTask = function (parentZoneDelegate, currentZone, targetZone, task) { + var key = task.source; + var instance = WtfZoneSpec.cancelInstance[key]; + if (!instance) { + instance = WtfZoneSpec.cancelInstance[key] = + wtfEvents.createInstance("Zone:cancel:" + key + "(ascii zone, any options)"); + } + var retValue = parentZoneDelegate.cancelTask(targetZone, task); + instance(zonePathName(targetZone), shallowObj(task.data, 2)); + return retValue; + }; + + return WtfZoneSpec; + }()); + WtfZoneSpec.forkInstance = wtfEnabled && wtfEvents.createInstance('Zone:fork(ascii zone, ascii newZone)'); + WtfZoneSpec.scheduleInstance = {}; + WtfZoneSpec.cancelInstance = {}; + WtfZoneSpec.invokeScope = {}; + WtfZoneSpec.invokeTaskScope = {}; + function shallowObj(obj, depth) { + if (!depth) + return null; + var out = {}; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + var value = obj[key]; + switch (typeof value) { + case 'object': + var name_1 = value && value.constructor && value.constructor.name; + value = name_1 == Object.name ? shallowObj(value, depth - 1) : name_1; + break; + case 'function': + value = value.name || undefined; + break; + } + out[key] = value; + } + } + return out; + } + function zonePathName(zone) { + var name = zone.name; + zone = zone.parent; + while (zone != null) { + name = zone.name + '::' + name; + zone = zone.parent; + } + return name; + } + Zone['wtfZoneSpec'] = !wtfEnabled ? null : new WtfZoneSpec(); +})(typeof window === 'object' && window || typeof self === 'object' && self || global); + +/** + * @license + * Copyright Google Inc. 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 + */ +'use strict'; +(function (context) { + var Mocha = context.Mocha; + if (typeof Mocha === 'undefined') { + throw new Error('Missing Mocha.js'); + } + if (typeof Zone === 'undefined') { + throw new Error('Missing Zone.js'); + } + var ProxyZoneSpec = Zone['ProxyZoneSpec']; + var SyncTestZoneSpec = Zone['SyncTestZoneSpec']; + if (!ProxyZoneSpec) { + throw new Error('Missing ProxyZoneSpec'); + } + if (Mocha['__zone_patch__']) { + throw new Error('"Mocha" has already been patched with "Zone".'); + } + Mocha['__zone_patch__'] = true; + var rootZone = Zone.current; + var syncZone = rootZone.fork(new SyncTestZoneSpec('Mocha.describe')); + var testZone = null; + var suiteZone = rootZone.fork(new ProxyZoneSpec()); + var mochaOriginal = { + after: Mocha.after, + afterEach: Mocha.afterEach, + before: Mocha.before, + beforeEach: Mocha.beforeEach, + describe: Mocha.describe, + it: Mocha.it + }; + function modifyArguments(args, syncTest, asyncTest) { + var _loop_1 = function (i) { + var arg = args[i]; + if (typeof arg === 'function') { + // The `done` callback is only passed through if the function expects at + // least one argument. + // Note we have to make a function with correct number of arguments, + // otherwise mocha will + // think that all functions are sync or async. + args[i] = (arg.length === 0) ? syncTest(arg) : asyncTest(arg); + // Mocha uses toString to view the test body in the result list, make sure we return the + // correct function body + args[i].toString = function () { + return arg.toString(); + }; + } + }; + for (var i = 0; i < args.length; i++) { + _loop_1(i); + } + return args; + } + function wrapDescribeInZone(args) { + var syncTest = function (fn) { + return function () { + return syncZone.run(fn, this, arguments); + }; + }; + return modifyArguments(args, syncTest); + } + function wrapTestInZone(args) { + var asyncTest = function (fn) { + return function (done) { + return testZone.run(fn, this, [done]); + }; + }; + var syncTest = function (fn) { + return function () { + return testZone.run(fn, this); + }; + }; + return modifyArguments(args, syncTest, asyncTest); + } + function wrapSuiteInZone(args) { + var asyncTest = function (fn) { + return function (done) { + return suiteZone.run(fn, this, [done]); + }; + }; + var syncTest = function (fn) { + return function () { + return suiteZone.run(fn, this); + }; + }; + return modifyArguments(args, syncTest, asyncTest); + } + context.describe = context.suite = Mocha.describe = function () { + return mochaOriginal.describe.apply(this, wrapDescribeInZone(arguments)); + }; + context.xdescribe = context.suite.skip = Mocha.describe.skip = function () { + return mochaOriginal.describe.skip.apply(this, wrapDescribeInZone(arguments)); + }; + context.describe.only = context.suite.only = Mocha.describe.only = function () { + return mochaOriginal.describe.only.apply(this, wrapDescribeInZone(arguments)); + }; + context.it = context.specify = context.test = Mocha.it = function () { + return mochaOriginal.it.apply(this, wrapTestInZone(arguments)); + }; + context.xit = context.xspecify = Mocha.it.skip = function () { + return mochaOriginal.it.skip.apply(this, wrapTestInZone(arguments)); + }; + context.it.only = context.test.only = Mocha.it.only = function () { + return mochaOriginal.it.only.apply(this, wrapTestInZone(arguments)); + }; + context.after = context.suiteTeardown = Mocha.after = function () { + return mochaOriginal.after.apply(this, wrapSuiteInZone(arguments)); + }; + context.afterEach = context.teardown = Mocha.afterEach = function () { + return mochaOriginal.afterEach.apply(this, wrapTestInZone(arguments)); + }; + context.before = context.suiteSetup = Mocha.before = function () { + return mochaOriginal.before.apply(this, wrapSuiteInZone(arguments)); + }; + context.beforeEach = context.setup = Mocha.beforeEach = function () { + return mochaOriginal.beforeEach.apply(this, wrapTestInZone(arguments)); + }; + (function (originalRunTest, originalRun) { + Mocha.Runner.prototype.runTest = function (fn) { + var _this = this; + Zone.current.scheduleMicroTask('mocha.forceTask', function () { + originalRunTest.call(_this, fn); + }); + }; + Mocha.Runner.prototype.run = function (fn) { + this.on('test', function (e) { + if (Zone.current !== rootZone) { + throw new Error('Unexpected zone: ' + Zone.current.name); + } + testZone = rootZone.fork(new ProxyZoneSpec()); + }); + return originalRun.call(this, fn); + }; + })(Mocha.Runner.prototype.runTest, Mocha.Runner.prototype.run); +})(typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global); + +}))); diff --git a/nativescript-angular/zone-js/testing.jasmine.ts b/nativescript-angular/zone-js/testing.jasmine.ts new file mode 100644 index 000000000..c363546af --- /dev/null +++ b/nativescript-angular/zone-js/testing.jasmine.ts @@ -0,0 +1,3 @@ +// Bootstrap helper module for jasmine spec tests +import "../platform"; +import "./dist/zone-nativescript.jasmine.js"; diff --git a/nativescript-angular/zone-js/testing.mocha.ts b/nativescript-angular/zone-js/testing.mocha.ts new file mode 100644 index 000000000..0f75b1d11 --- /dev/null +++ b/nativescript-angular/zone-js/testing.mocha.ts @@ -0,0 +1,2 @@ +import "../platform"; +import "./dist/zone-nativescript.mocha.js"; diff --git a/tests/app/snippets/navigation/page-outlet.ts b/tests/app/snippets/navigation/page-outlet.ts index 75354b5f6..27ce9ee48 100644 --- a/tests/app/snippets/navigation/page-outlet.ts +++ b/tests/app/snippets/navigation/page-outlet.ts @@ -1,4 +1,4 @@ -import { TestApp, registerTestApp } from "../../tests/test-app"; +import { registerTestApp } from "../../tests/test-app"; import { ApplicationRef } from "@angular/core"; import { Router, NavigationStart, NavigationEnd } from "@angular/router"; // >> page-outlet-example diff --git a/tests/app/snippets/navigation/router-outlet.ts b/tests/app/snippets/navigation/router-outlet.ts index bfd50fafc..bca549d74 100644 --- a/tests/app/snippets/navigation/router-outlet.ts +++ b/tests/app/snippets/navigation/router-outlet.ts @@ -1,4 +1,4 @@ -import {TestApp, registerTestApp} from "../../tests/test-app"; +import {registerTestApp} from "../../tests/test-app"; import { ApplicationRef } from "@angular/core"; // >> router-outlet-example import { Component, NgModule } from "@angular/core"; diff --git a/tests/app/tests/detached-loader-tests.ts b/tests/app/tests/detached-loader-tests.ts index 014217e35..e5dbe4ba7 100644 --- a/tests/app/tests/detached-loader-tests.ts +++ b/tests/app/tests/detached-loader-tests.ts @@ -1,21 +1,7 @@ // make sure you import mocha-config before @angular/core -import {assert} from "./test-config"; -import {TestApp} from "./test-app"; -import { - Component, - ElementRef, - Renderer, - AfterViewInit, - OnInit, - ViewChild, - ChangeDetectionStrategy -} from "@angular/core"; -import {ProxyViewContainer} from "ui/proxy-view-container"; -import {Red} from "color/known-colors"; -import {dumpView} from "./test-utils"; -import {LayoutBase} from "ui/layouts/layout-base"; -import {StackLayout} from "ui/layouts/stack-layout"; -import {DetachedLoader} from "nativescript-angular/common/detached-loader"; +import { ChangeDetectionStrategy, Component, ViewChild } from "@angular/core"; +import { DetachedLoader } from "nativescript-angular"; +import { nsTestBedAfterEach, nsTestBedBeforeEach, nsTestBedRender } from "nativescript-angular/testing"; @Component({ template: `` @@ -25,18 +11,6 @@ class TestComponent { } class LoaderComponentBase { @ViewChild(DetachedLoader) public loader: DetachedLoader; - - public ready: Promise; - private resolve; - constructor() { - this.ready = new Promise((reslove, reject) => { - this.resolve = reslove; - }); - } - ngAfterViewInit() { - console.log("!!! ngAfterViewInit -> loader: " + this.loader); - this.resolve(this); - } } @Component({ @@ -45,9 +19,9 @@ class LoaderComponentBase { - ` + ` }) -export class LoaderComponent extends LoaderComponentBase { } +export class LoaderComponent extends LoaderComponentBase {} @Component({ selector: "loader-component-on-push", @@ -56,58 +30,27 @@ export class LoaderComponent extends LoaderComponentBase { } - ` + ` }) export class LoaderComponentOnPush extends LoaderComponentBase { } describe("DetachedLoader", () => { - let testApp: TestApp = null; - - before(() => { - return TestApp.create([], [LoaderComponent, LoaderComponentOnPush, TestComponent]).then((app) => { - console.log("TEST APP: " + app); - testApp = app; - }); - }); - after(() => { - testApp.dispose(); - }); - - afterEach(() => { - testApp.disposeComponents(); - }); + beforeEach(nsTestBedBeforeEach([LoaderComponent, LoaderComponentOnPush], [], [], [TestComponent])); + afterEach(nsTestBedAfterEach()); - it("creates component", (done) => { - testApp.loadComponent(LoaderComponent) - .then((componentRef) => { - // wait for the ngAfterViewInit - return (componentRef.instance).ready; - }) - .then((comp) => { - // load test component with loader - return comp.loader.loadComponent(TestComponent); - }) - .then((compRef) => { - done(); - }) - .catch(done); + it("creates component", () => { + return nsTestBedRender(LoaderComponent).then((fixture) => { + const component: LoaderComponent = fixture.componentRef.instance; + return component.loader.loadComponent(TestComponent); + }); }); - it("creates component when ChangeDetectionStrategy is OnPush", (done) => { - testApp.loadComponent(LoaderComponentOnPush) - .then((componentRef) => { - // wait for the ngAfterViewInit - return (componentRef.instance).ready; - }) - .then((comp) => { - // load test component with loader - return comp.loader.loadComponent(TestComponent); - }) - .then((compRef) => { - done(); - }) - .catch(done); + it("creates component when ChangeDetectionStrategy is OnPush", () => { + return nsTestBedRender(LoaderComponentOnPush).then((fixture) => { + const component: LoaderComponentOnPush = fixture.componentRef.instance; + return component.loader.loadComponent(TestComponent); + }); }); }); diff --git a/tests/app/tests/list-view-tests.ts b/tests/app/tests/list-view-tests.ts index 3c3d7fb54..7b425343b 100644 --- a/tests/app/tests/list-view-tests.ts +++ b/tests/app/tests/list-view-tests.ts @@ -1,9 +1,7 @@ import { assert } from "./test-config"; -import { Component, Input, AfterViewInit } from "@angular/core"; -import { TestApp } from "./test-app"; -import { RootLocator, ComponentView, getItemViewRoot } from "nativescript-angular/directives/list-view-comp"; -import { ProxyViewContainer } from "tns-core-modules/ui/proxy-view-container"; - +import { Component, Input } from "@angular/core"; +import { ComponentFixture } from "@angular/core/testing"; +import { nsTestBedAfterEach, nsTestBedBeforeEach, nsTestBedRender } from "nativescript-angular/testing"; // import trace = require("trace"); // trace.setCategories("ns-list-view, " + trace.categories.Navigation); // trace.enable(); @@ -68,7 +66,7 @@ export class ItemTemplateComponent { - ` + ` }) export class TestListViewSelectorComponent { public myItems: Array = ITEMS; @@ -79,45 +77,27 @@ export class TestListViewSelectorComponent { } describe("ListView-tests", () => { - let testApp: TestApp = null; - - before(() => { - return TestApp.create([], [ - TestListViewComponent, - TestListViewSelectorComponent, - ItemTemplateComponent - ]).then((app) => { - testApp = app; - }); - }); - - after(() => { - testApp.dispose(); - }); - - afterEach(() => { - testApp.disposeComponents(); - }); + beforeEach(nsTestBedBeforeEach([ + TestListViewComponent, + TestListViewSelectorComponent, + ItemTemplateComponent + ])); + afterEach(nsTestBedAfterEach(false)); it("setupItemView is called for every item", (done) => { - testApp.loadComponent(TestListViewComponent).then((componentRef) => { - const component = componentRef.instance; - setTimeout(() => { + nsTestBedRender(TestListViewComponent) + .then((fixture: ComponentFixture) => { + const component = fixture.componentRef.instance; assert.equal(component.counter, 3); done(); - }, 1000); - }) - .catch(done); + }); }); it("itemTemplateSelector selects templates", (done) => { - testApp.loadComponent(TestListViewSelectorComponent).then((componentRef) => { - setTimeout(() => { - assert.deepEqual(testTemplates, { first: 2, second: 1 }); - done(); - }, 1000); - }) - .catch(done); + nsTestBedRender(TestListViewSelectorComponent).then(() => { + assert.deepEqual(testTemplates, {first: 2, second: 1}); + done(); + }); }); }); diff --git a/tests/app/tests/modal-dialog.ts b/tests/app/tests/modal-dialog.ts index 69a909c73..9a275d132 100644 --- a/tests/app/tests/modal-dialog.ts +++ b/tests/app/tests/modal-dialog.ts @@ -1,12 +1,14 @@ // make sure you import mocha-config before @angular/core -import { assert } from "./test-config"; -import { TestApp } from "./test-app"; -import { Component, ViewContainerRef } from "@angular/core"; -import { Page } from "ui/page"; -import { topmost } from "ui/frame"; -import { ModalDialogParams, ModalDialogService } from "nativescript-angular/directives/dialogs"; +import {assert} from "./test-config"; +import {Component, ViewContainerRef} from "@angular/core"; +import {Page} from "ui/page"; +import {topmost} from "ui/frame"; +import {ModalDialogParams, ModalDialogService} from "nativescript-angular/directives/dialogs"; -import { device, platformNames } from "platform"; +import {device, platformNames} from "platform"; + +import {ComponentFixture} from "@angular/core/testing"; +import {nsTestBedRender, nsTestBedAfterEach, nsTestBedBeforeEach} from "nativescript-angular/testing"; const CLOSE_WAIT = (device.os === platformNames.ios) ? 1000 : 0; @Component({ @@ -47,20 +49,13 @@ export class SuccessComponent { } describe("modal-dialog", () => { - let testApp: TestApp = null; + beforeEach(nsTestBedBeforeEach([FailComponent, SuccessComponent], [], [], [ModalComponent])); + afterEach(nsTestBedAfterEach()); before((done) => { - TestApp.create([], [ModalComponent, FailComponent, SuccessComponent]).then((app) => { - testApp = app; - - // HACK: Wait for the navigations from the test runner app - // Remove the setTimeout when test runner start tests on page.navigatedTo - setTimeout(done, 1000); - }); - }); - - after(() => { - testApp.dispose(); + // HACK: Wait for the navigations from the test runner app + // Remove the setTimeout when test runner start tests on page.navigatedTo + setTimeout(() => done(), 1000); }); afterEach(() => { @@ -69,36 +64,38 @@ describe("modal-dialog", () => { console.log("Warning: closing a leftover modal page!"); page.modal.closeModal(); } - testApp.disposeComponents(); }); it("showModal throws when there is no viewContainer provided", (done) => { - testApp.loadComponent(FailComponent) - .then((ref) => { - const service = ref.instance.service; + nsTestBedRender(FailComponent) + .then((fixture: ComponentFixture) => { + const service = fixture.componentRef.instance.service; assert.throws(() => service.showModal(ModalComponent, {}), - "No viewContainerRef: Make sure you pass viewContainerRef in ModalDialogOptions." - ); - }).then(() => done(), err => done(err)); + "No viewContainerRef: Make sure you pass viewContainerRef in ModalDialogOptions." + ); + }) + .then(() => done()) + .catch((e) => done(e)); }); it("showModal succeeds when there is viewContainer provided", (done) => { - testApp.loadComponent(SuccessComponent) - .then((ref) => { - const service = ref.instance.service; - const comp = ref.instance; - return service.showModal(ModalComponent, { viewContainerRef: comp.vcRef }); + nsTestBedRender(SuccessComponent) + .then((fixture: ComponentFixture) => { + const service = fixture.componentRef.instance.service; + const comp = fixture.componentRef.instance; + return service.showModal(ModalComponent, {viewContainerRef: comp.vcRef}); }) - .then((res) => setTimeout(done, CLOSE_WAIT), err => done(err)); // wait for the dialog to close in IOS + .then((res) => setTimeout(done, CLOSE_WAIT)) // wait for the dialog to close in IOS + .catch((e) => done(e)); }); it("showModal passes modal params and gets result when resolved", (done) => { - const context = { property: "my context" }; - testApp.loadComponent(SuccessComponent) - .then((ref) => { - const service = ref.instance.service; - const comp = ref.instance; + const context = {property: "my context"}; + nsTestBedRender(SuccessComponent) + .then((fixture: ComponentFixture) => { + const service = fixture.componentRef.instance.service; + const comp = fixture.componentRef.instance; return service.showModal(ModalComponent, { viewContainerRef: comp.vcRef, context: context @@ -107,6 +104,7 @@ describe("modal-dialog", () => { .then((res) => { assert.strictEqual(res, context); setTimeout(done, CLOSE_WAIT); // wait for the dialog to close in IOS - }, err => done(err)); + }) + .catch((e) => done(e)); }); }); diff --git a/tests/app/tests/platform-filter-components.ts b/tests/app/tests/platform-filter-components.ts index 5665e1364..fff8368ec 100644 --- a/tests/app/tests/platform-filter-components.ts +++ b/tests/app/tests/platform-filter-components.ts @@ -1,122 +1,102 @@ -// make sure you import mocha-config before @angular/core -import { assert } from "./test-config"; -import { Component, ElementRef } from "@angular/core"; -import { dumpView, createDevice } from "./test-utils"; -import { TestApp } from "./test-app"; -import { DEVICE } from "nativescript-angular/platform-providers"; -import { platformNames } from "platform"; - -@Component({ - template: ` - - - ` -}) -export class IosSpecificComponent { - constructor(public elementRef: ElementRef) { } -} - -@Component({ - template: ` - - - ` -}) -export class AndroidSpecificComponent { - constructor(public elementRef: ElementRef) { } -} - -@Component({ - template: ` - - - ` -}) -export class PlatformSpecificAttributeComponent { - constructor(public elementRef: ElementRef) { } -} - -describe("Platform filter directives", () => { - describe("on IOS device", () => { - let testApp: TestApp = null; - - before(() => { - return TestApp.create([{ provide: DEVICE, useValue: createDevice(platformNames.ios) }], [ - PlatformSpecificAttributeComponent, - AndroidSpecificComponent, - IosSpecificComponent - ]).then((app) => { - testApp = app; - }); - }); - - after(() => { - testApp.dispose(); - }); - - it("does render ios specific content", () => { - return testApp.loadComponent(IosSpecificComponent).then((componentRef) => { - const componentRoot = componentRef.instance.elementRef.nativeElement; - assert.isTrue(dumpView(componentRoot, true).indexOf("(Label[text=IOS])") >= 0); - }); - }); - - it("does not render android specific content", () => { - return testApp.loadComponent(AndroidSpecificComponent).then((componentRef) => { - const componentRoot = componentRef.instance.elementRef.nativeElement; - assert.isTrue(dumpView(componentRoot, true).indexOf("Label") < 0); - }); - }); - - - it("applies iOS specific attribute", () => { - return testApp.loadComponent(PlatformSpecificAttributeComponent).then((componentRef) => { - const componentRoot = componentRef.instance.elementRef.nativeElement; - assert.equal( - "(ProxyViewContainer (StackLayout (Label[text=IOS])))", - dumpView(componentRoot, true)); - }); - }); - }); - - describe("on Android device", () => { - let testApp: TestApp = null; - - before(() => { - return TestApp.create([{ provide: DEVICE, useValue: createDevice(platformNames.android) }], [ - AndroidSpecificComponent, - IosSpecificComponent, - PlatformSpecificAttributeComponent - ]).then((app) => { - testApp = app; - }); - }); - - after(() => { - testApp.dispose(); - }); - - it("does render android specific content", () => { - return testApp.loadComponent(AndroidSpecificComponent).then((componentRef) => { - const componentRoot = componentRef.instance.elementRef.nativeElement; - assert.isTrue(dumpView(componentRoot, true).indexOf("(Label[text=ANDROID])") >= 0); - }); - }); - - it("does not render ios specific content", () => { - return testApp.loadComponent(IosSpecificComponent).then((componentRef) => { - const componentRoot = componentRef.instance.elementRef.nativeElement; - assert.isTrue(dumpView(componentRoot, true).indexOf("Label") < 0); - }); - }); - - it("applies Android specific attribute", () => { - return testApp.loadComponent(PlatformSpecificAttributeComponent).then((componentRef) => { - const componentRoot = componentRef.instance.elementRef.nativeElement; - assert.equal( - "(ProxyViewContainer (StackLayout (Label[text=ANDROID])))", - dumpView(componentRoot, true)); - }); - }); - }); -}); +// make sure you import mocha-config before @angular/core +import { assert } from "./test-config"; +import { Component, ElementRef } from "@angular/core"; +import { dumpView, createDevice } from "./test-utils"; +import { DEVICE } from "nativescript-angular/platform-providers"; +import { platformNames } from "platform"; +import { nsTestBedAfterEach, nsTestBedBeforeEach, nsTestBedRender } from "nativescript-angular/testing"; + +@Component({ + template: ` + + + ` +}) +export class IosSpecificComponent { + constructor(public elementRef: ElementRef) { } +} + +@Component({ + template: ` + + + ` +}) +export class AndroidSpecificComponent { + constructor(public elementRef: ElementRef) { } +} + +@Component({ + template: ` + + + ` +}) +export class PlatformSpecificAttributeComponent { + constructor(public elementRef: ElementRef) { } +} + +describe("Platform filter directives", () => { + describe("on IOS device", () => { + beforeEach(nsTestBedBeforeEach( + [PlatformSpecificAttributeComponent, AndroidSpecificComponent, IosSpecificComponent], + [{provide: DEVICE, useValue: createDevice(platformNames.ios)}] + )); + afterEach(nsTestBedAfterEach()); + it("does render ios specific content", () => { + return nsTestBedRender(IosSpecificComponent).then((fixture) => { + const componentRef = fixture.componentRef; + const componentRoot = componentRef.instance.elementRef.nativeElement; + assert.isTrue(dumpView(componentRoot, true).indexOf("(Label[text=IOS])") >= 0); + }); + }); + it("does not render android specific content", () => { + return nsTestBedRender(AndroidSpecificComponent).then((fixture) => { + const componentRef = fixture.componentRef; + const componentRoot = componentRef.instance.elementRef.nativeElement; + assert.isTrue(dumpView(componentRoot, true).indexOf("Label") < 0); + }); + }); + it("applies iOS specific attribute", () => { + return nsTestBedRender(PlatformSpecificAttributeComponent).then((fixture) => { + const componentRef = fixture.componentRef; + const componentRoot = componentRef.instance.elementRef.nativeElement; + assert.equal( + "(ProxyViewContainer (StackLayout (Label[text=IOS])))", + dumpView(componentRoot, true)); + }); + }); + }); + + describe("on Android device", () => { + beforeEach(nsTestBedBeforeEach( + [PlatformSpecificAttributeComponent, AndroidSpecificComponent, IosSpecificComponent], + [{provide: DEVICE, useValue: createDevice(platformNames.android)}] + )); + afterEach(nsTestBedAfterEach()); + + it("does render android specific content", () => { + return nsTestBedRender(AndroidSpecificComponent).then((fixture) => { + const componentRef = fixture.componentRef; + const componentRoot = componentRef.instance.elementRef.nativeElement; + assert.isTrue(dumpView(componentRoot, true).indexOf("(Label[text=ANDROID])") >= 0); + }); + }); + it("does not render ios specific content", () => { + return nsTestBedRender(IosSpecificComponent).then((fixture) => { + const componentRef = fixture.componentRef; + const componentRoot = componentRef.instance.elementRef.nativeElement; + assert.isTrue(dumpView(componentRoot, true).indexOf("Label") < 0); + }); + }); + it("applies Android specific attribute", () => { + return nsTestBedRender(PlatformSpecificAttributeComponent).then((fixture) => { + const componentRef = fixture.componentRef; + const componentRoot = componentRef.instance.elementRef.nativeElement; + assert.equal( + "(ProxyViewContainer (StackLayout (Label[text=ANDROID])))", + dumpView(componentRoot, true)); + }); + }); + }); +}); diff --git a/tests/app/tests/renderer-tests.ts b/tests/app/tests/renderer-tests.ts index ebb9091ee..458c29738 100644 --- a/tests/app/tests/renderer-tests.ts +++ b/tests/app/tests/renderer-tests.ts @@ -1,18 +1,27 @@ // make sure you import mocha-config before @angular/core -import { assert } from "./test-config"; -import { Component, ElementRef, Renderer2, NgZone, ViewChild } from "@angular/core"; -import { ProxyViewContainer } from "ui/proxy-view-container"; -import { Red } from "color/known-colors"; -import { dumpView } from "./test-utils"; -import { TestApp } from "./test-app"; -import { LayoutBase } from "ui/layouts/layout-base"; -import { StackLayout } from "ui/layouts/stack-layout"; -import { ContentView } from "ui/content-view"; -import { Button } from "ui/button"; -import { NgView } from "nativescript-angular/element-registry"; -import { registerElement } from "nativescript-angular/element-registry"; +import {assert} from "./test-config"; +import {Component, ComponentRef, ElementRef, NgZone, Renderer2, ViewChild} from "@angular/core"; +import {ProxyViewContainer} from "ui/proxy-view-container"; +import {Red} from "color/known-colors"; +import {dumpView} from "./test-utils"; +import {LayoutBase} from "ui/layouts/layout-base"; +import {StackLayout} from "ui/layouts/stack-layout"; +import {ContentView} from "ui/content-view"; +import {Button} from "ui/button"; +import {registerElement} from "nativescript-angular/element-registry"; import * as button from "tns-core-modules/ui/button"; import * as view from "tns-core-modules/ui/core/view"; +import {nsTestBedAfterEach, nsTestBedBeforeEach, nsTestBedRender} from "nativescript-angular/testing"; +import {ComponentFixture, TestBed} from "@angular/core/testing"; +import {Observable} from "rxjs/Observable"; +import {ReplaySubject} from "rxjs/ReplaySubject"; + +@Component({ + template: `` +}) +export class ZonedRenderer { + constructor(public elementRef: ElementRef, public renderer: Renderer2) { } +} @Component({ template: `` @@ -219,10 +228,10 @@ export class NgControlSettersCount { get buttons(): ElementRef[] { return [this.btn1, this.btn2, this.btn3, this.btn4]; } - isAfterViewInit: boolean = false; + ready$: Observable = new ReplaySubject(1); ngAfterViewInit() { - this.isAfterViewInit = true; + (this.ready$ as ReplaySubject).next(true); } } @@ -237,46 +246,36 @@ export class NgForLabel { } describe("Renderer E2E", () => { - let testApp: TestApp = null; - - before(() => { - return TestApp.create([], [ - LayoutWithLabel, LabelCmp, LabelContainer, - ProjectableCmp, ProjectionContainer, - StyledLabelCmp, StyledLabelCmp2, - NgIfLabel, NgIfThenElseComponent, NgIfMultiple, - NgIfTwoElements, NgIfMultiple, - NgIfElseComponent, NgIfThenElseComponent, - NgForLabel, - ]).then((app) => { - testApp = app; - }); - }); - - after(() => { - testApp.dispose(); - }); - - afterEach(() => { - testApp.disposeComponents(); - }); + beforeEach(nsTestBedBeforeEach([ + LayoutWithLabel, LabelCmp, LabelContainer, + ProjectableCmp, ProjectionContainer, + StyledLabelCmp, StyledLabelCmp2, + NgIfLabel, NgIfThenElseComponent, NgIfMultiple, + NgIfTwoElements, NgIfMultiple, + NgIfElseComponent, NgIfThenElseComponent, + NgForLabel, ZonedRenderer + ])); + afterEach(nsTestBedAfterEach(false)); it("component with a layout", () => { - return testApp.loadComponent(LayoutWithLabel).then((componentRef) => { + return nsTestBedRender(LayoutWithLabel).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const componentRoot = componentRef.instance.elementRef.nativeElement; assert.equal("(ProxyViewContainer (StackLayout (Label)))", dumpView(componentRoot)); }); }); it("component without a layout", () => { - return testApp.loadComponent(LabelContainer).then((componentRef) => { + return nsTestBedRender(LabelContainer).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const componentRoot = componentRef.instance.elementRef.nativeElement; assert.equal("(ProxyViewContainer (GridLayout (ProxyViewContainer (Label))))", dumpView(componentRoot)); }); }); it("projects content into components", () => { - return testApp.loadComponent(ProjectionContainer).then((componentRef) => { + return nsTestBedRender(ProjectionContainer).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const componentRoot = componentRef.instance.elementRef.nativeElement; assert.equal( "(ProxyViewContainer (GridLayout (ProxyViewContainer (StackLayout (Button)))))", @@ -285,7 +284,8 @@ describe("Renderer E2E", () => { }); it("applies component styles from single source", () => { - return testApp.loadComponent(StyledLabelCmp).then((componentRef) => { + return nsTestBedRender(StyledLabelCmp).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const componentRoot = componentRef.instance.elementRef.nativeElement; const label = (componentRoot).getChildAt(0); assert.equal(Red, label.style.color.hex); @@ -293,7 +293,8 @@ describe("Renderer E2E", () => { }); it("applies component styles from multiple sources", () => { - return testApp.loadComponent(StyledLabelCmp2).then((componentRef) => { + return nsTestBedRender(StyledLabelCmp2).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const componentRoot = componentRef.instance.elementRef.nativeElement; const layout = (componentRoot).getChildAt(0); @@ -316,15 +317,18 @@ describe("Renderer E2E", () => { done(); }; - testApp.zone.run(() => { - testApp.renderer.listen(view, eventName, callback); + nsTestBedRender(ZonedRenderer).then((fixture: ComponentFixture) => { + fixture.ngZone.run(() => { + fixture.componentInstance.renderer.listen(view, eventName, callback); + }); + + setTimeout(() => { + fixture.ngZone.runOutsideAngular(() => { + view.notify(eventArg); + }); + }, 10); }); - setTimeout(() => { - testApp.zone.runOutsideAngular(() => { - view.notify(eventArg); - }); - }, 10); }); it("executes events inside NgZone when listen is called outside NgZone", (done) => { @@ -336,40 +340,44 @@ describe("Renderer E2E", () => { assert.isTrue(NgZone.isInAngularZone(), "Event should be executed inside NgZone"); done(); }; + nsTestBedRender(ZonedRenderer).then((fixture: ComponentFixture) => { + fixture.ngZone.runOutsideAngular(() => { + fixture.componentInstance.renderer.listen(view, eventName, callback); - testApp.zone.runOutsideAngular(() => { - testApp.renderer.listen(view, eventName, callback); - - view.notify(eventArg); + view.notify(eventArg); + }); }); }); describe("Structural directives", () => { it("ngIf hides component when false", () => { - return testApp.loadComponent(NgIfLabel).then((componentRef) => { + return nsTestBedRender(NgIfLabel).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const componentRoot = componentRef.instance.elementRef.nativeElement; assert.equal("(ProxyViewContainer)", dumpView(componentRoot)); }); }); it("ngIf show component when true", () => { - return testApp.loadComponent(NgIfLabel).then((componentRef) => { + return nsTestBedRender(NgIfLabel).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const component = componentRef.instance; const componentRoot = component.elementRef.nativeElement; component.show = true; - testApp.appRef.tick(); + fixture.detectChanges(); assert.equal("(ProxyViewContainer (Label))", dumpView(componentRoot)); }); }); it("ngIf shows elements in correct order when two are rendered", () => { - return testApp.loadComponent(NgIfTwoElements).then((componentRef) => { + return nsTestBedRender(NgIfTwoElements).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const component = componentRef.instance; const componentRoot = component.elementRef.nativeElement; component.show = true; - testApp.appRef.tick(); + fixture.detectChanges(); assert.equal( "(ProxyViewContainer (StackLayout (Label), (Button)))", dumpView(componentRoot)); @@ -377,12 +385,13 @@ describe("Renderer E2E", () => { }); it("ngIf shows elements in correct order when multiple are rendered and there's *ngIf", () => { - return testApp.loadComponent(NgIfMultiple).then((componentRef) => { + return nsTestBedRender(NgIfMultiple).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const component = componentRef.instance; const componentRoot = component.elementRef.nativeElement; component.show = true; - testApp.appRef.tick(); + fixture.detectChanges(); assert.equal( "(ProxyViewContainer " + "(StackLayout " + @@ -398,11 +407,12 @@ describe("Renderer E2E", () => { }); it("ngIfElse show 'if' template when condition is true", () => { - return testApp.loadComponent(NgIfElseComponent).then(componentRef => { + return nsTestBedRender(NgIfElseComponent).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const component = componentRef.instance; const componentRoot = component.elementRef.nativeElement; - testApp.appRef.tick(); + fixture.detectChanges(); assert.equal( "(ProxyViewContainer " + @@ -416,12 +426,13 @@ describe("Renderer E2E", () => { }); it("ngIfElse show 'else' template when condition is false", () => { - return testApp.loadComponent(NgIfElseComponent).then(componentRef => { + return nsTestBedRender(NgIfElseComponent).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const component = componentRef.instance; const componentRoot = component.elementRef.nativeElement; component.show = false; - testApp.appRef.tick(); + fixture.detectChanges(); assert.equal( "(ProxyViewContainer " + "(StackLayout " + @@ -434,11 +445,12 @@ describe("Renderer E2E", () => { }); it("ngIfThenElse show 'then' template when condition is true", () => { - return testApp.loadComponent(NgIfThenElseComponent).then(componentRef => { + return nsTestBedRender(NgIfThenElseComponent).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const component = componentRef.instance; const componentRoot = component.elementRef.nativeElement; - testApp.appRef.tick(); + fixture.detectChanges(); assert.equal( "(ProxyViewContainer " + "(StackLayout " + @@ -452,12 +464,13 @@ describe("Renderer E2E", () => { it("ngIfThenElse show 'else' template when condition is false", () => { - return testApp.loadComponent(NgIfThenElseComponent).then(componentRef => { + return nsTestBedRender(NgIfThenElseComponent).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const component = componentRef.instance; const componentRoot = component.elementRef.nativeElement; component.show = false; - testApp.appRef.tick(); + fixture.detectChanges(); assert.equal( "(ProxyViewContainer " + "(StackLayout " + @@ -470,7 +483,8 @@ describe("Renderer E2E", () => { }); it("ngFor creates element for each item", () => { - return testApp.loadComponent(NgForLabel).then((componentRef) => { + return nsTestBedRender(NgForLabel).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const componentRoot = componentRef.instance.elementRef.nativeElement; assert.equal( "(ProxyViewContainer (Label[text=one]), (Label[text=two]), (Label[text=three]))", @@ -479,12 +493,13 @@ describe("Renderer E2E", () => { }); it("ngFor updates when item is removed", () => { - return testApp.loadComponent(NgForLabel).then((componentRef) => { + return nsTestBedRender(NgForLabel).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const component = componentRef.instance; const componentRoot = component.elementRef.nativeElement; component.items.splice(1, 1); - testApp.appRef.tick(); + fixture.detectChanges(); assert.equal( "(ProxyViewContainer (Label[text=one]), (Label[text=three]))", @@ -493,12 +508,13 @@ describe("Renderer E2E", () => { }); it("ngFor updates when item is inserted", () => { - return testApp.loadComponent(NgForLabel).then((componentRef) => { + return nsTestBedRender(NgForLabel).then((fixture) => { + const componentRef: ComponentRef = fixture.componentRef; const component = componentRef.instance; const componentRoot = component.elementRef.nativeElement; component.items.splice(1, 0, "new"); - testApp.appRef.tick(); + fixture.detectChanges(); assert.equal( "(ProxyViewContainer " + @@ -510,20 +526,17 @@ describe("Renderer E2E", () => { }); describe("Renderer createElement", () => { - let testApp: TestApp = null; let renderer: Renderer2 = null; - - before(() => { - return TestApp.create().then((app) => { - testApp = app; - renderer = testApp.renderer; + beforeEach(nsTestBedBeforeEach([ZonedRenderer])); + afterEach(nsTestBedAfterEach(false)); + beforeEach(() => { + return nsTestBedRender(ZonedRenderer).then((fixture: ComponentFixture) => { + fixture.ngZone.run(() => { + renderer = fixture.componentInstance.renderer; + }); }); }); - after(() => { - testApp.dispose(); - }); - it("creates element from CamelCase", () => { const result = renderer.createElement("StackLayout"); assert.instanceOf(result, StackLayout, "Renderer should create StackLayout form 'StackLayout'"); @@ -546,20 +559,17 @@ describe("Renderer createElement", () => { }); describe("Renderer attach/detach", () => { - let testApp: TestApp = null; let renderer: Renderer2 = null; - - before(() => { - return TestApp.create().then((app) => { - testApp = app; - renderer = testApp.renderer; + beforeEach(nsTestBedBeforeEach([ZonedRenderer])); + afterEach(nsTestBedAfterEach(false)); + beforeEach(() => { + return nsTestBedRender(ZonedRenderer).then((fixture: ComponentFixture) => { + fixture.ngZone.run(() => { + renderer = fixture.componentInstance.renderer; + }); }); }); - after(() => { - testApp.dispose(); - }); - it("createElement element with parent attaches element to content view", () => { const parent = renderer.createElement("ContentView"); const button =