diff --git a/gulpfile.js b/gulpfile.js index 61865525fb..c374d95368 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -382,7 +382,7 @@ function proxyServeDart() { // ------------------ // web servers -gulp.task('serve.js.dev', ['build.js.dev'], function(neverDone) { +gulp.task('serve.js.dev', ['build.js.dev', 'build.js.cjs'], function(neverDone) { var watch = require('./tools/build/watch'); watch('modules/**', {ignoreInitial: true}, '!broccoli.js.dev'); @@ -706,7 +706,7 @@ gulp.task('!build.payload.js.webpack', function() { .then(function() { // pad bundle with mandatory dependencies return new Promise(function(resolve, reject) { gulp.src([ - 'node_modules/zone.js/dist/zone-microtask.js', + 'node_modules/zone.js/dist/zone.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js', 'node_modules/reflect-metadata/Reflect.js', CASE_PATH + '/app-bundle.js' @@ -991,7 +991,7 @@ gulp.task('test.typings', ['!pre.test.typings.layoutNodeModule', '!pre.test.typings.copyTypingsSpec'], function() { var tsc = require('gulp-typescript'); - return gulp.src([tmpdir + '/*.ts']) + return gulp.src([tmpdir + '/*.ts', 'node_modules/zone.js/dist/zone.js.d.ts']) .pipe(tsc({ target: 'ES6', module: 'commonjs', @@ -1355,7 +1355,7 @@ gulp.task('!bundle.ng.polyfills', ['clean'], var JS_DEV_DEPS = [ licenseWrap('node_modules/zone.js/LICENSE', true), - 'node_modules/zone.js/dist/zone-microtask.js', + 'node_modules/zone.js/dist/zone.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js', licenseWrap('node_modules/reflect-metadata/LICENSE', true), 'node_modules/reflect-metadata/Reflect.js' diff --git a/karma-js.conf.js b/karma-js.conf.js index f2b1375d1b..0c3d96ac23 100644 --- a/karma-js.conf.js +++ b/karma-js.conf.js @@ -17,8 +17,7 @@ module.exports = function(config) { // include Angular v1 for upgrade module testing 'node_modules/angular/angular.min.js', - // zone-microtask must be included first as it contains a Promise monkey patch - 'node_modules/zone.js/dist/zone-microtask.js', + 'node_modules/zone.js/dist/zone.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js', 'node_modules/zone.js/dist/jasmine-patch.js', diff --git a/modules/angular2/src/core/application_ref.ts b/modules/angular2/src/core/application_ref.ts index 54b1a00d49..a84a2ef0d3 100644 --- a/modules/angular2/src/core/application_ref.ts +++ b/modules/angular2/src/core/application_ref.ts @@ -1,4 +1,4 @@ -import {NgZone} from 'angular2/src/core/zone/ng_zone'; +import {NgZone, NgZoneError} from 'angular2/src/core/zone/ng_zone'; import { Type, isBlank, @@ -254,7 +254,9 @@ export class PlatformRef_ extends PlatformRef { try { injector = this.injector.resolveAndCreateChild(providers); exceptionHandler = injector.get(ExceptionHandler); - zone.overrideOnErrorHandler((e, s) => exceptionHandler.call(e, s)); + ObservableWrapper.subscribe(zone.onError, (error: NgZoneError) => { + exceptionHandler.call(error.error, error.stackTrace); + }); } catch (e) { if (isPresent(exceptionHandler)) { exceptionHandler.call(e, e.stack); @@ -394,7 +396,7 @@ export class ApplicationRef_ extends ApplicationRef { constructor(private _platform: PlatformRef_, private _zone: NgZone, private _injector: Injector) { super(); if (isPresent(this._zone)) { - ObservableWrapper.subscribe(this._zone.onTurnDone, + ObservableWrapper.subscribe(this._zone.onMicrotaskEmpty, (_) => { this._zone.run(() => { this.tick(); }); }); } this._enforceNoNewChanges = assertionsEnabled(); @@ -434,16 +436,10 @@ export class ApplicationRef_ extends ApplicationRef { var tickResult = PromiseWrapper.then(compRefToken, tick); - // THIS MUST ONLY RUN IN DART. - // This is required to report an error when no components with a matching selector found. - // Otherwise the promise will never be completed. - // Doing this in JS causes an extra error message to appear. - if (IS_DART) { - PromiseWrapper.then(tickResult, (_) => {}); - } - - PromiseWrapper.then(tickResult, null, - (err, stackTrace) => completer.reject(err, stackTrace)); + PromiseWrapper.then(tickResult, null, (err, stackTrace) => { + completer.reject(err, stackTrace); + exceptionHandler.call(err, stackTrace); + }); } catch (e) { exceptionHandler.call(e, e.stack); completer.reject(e, e.stack); diff --git a/modules/angular2/src/core/testability/testability.ts b/modules/angular2/src/core/testability/testability.ts index d15690d15a..c8215e90c0 100644 --- a/modules/angular2/src/core/testability/testability.ts +++ b/modules/angular2/src/core/testability/testability.ts @@ -1,9 +1,9 @@ import {Injectable} from 'angular2/src/core/di'; import {Map, MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; -import {CONST, CONST_EXPR} from 'angular2/src/facade/lang'; -import {BaseException, WrappedException} from 'angular2/src/facade/exceptions'; +import {CONST, CONST_EXPR, scheduleMicroTask} from 'angular2/src/facade/lang'; +import {BaseException} from 'angular2/src/facade/exceptions'; import {NgZone} from '../zone/ng_zone'; -import {PromiseWrapper, ObservableWrapper} from 'angular2/src/facade/async'; +import {ObservableWrapper} from 'angular2/src/facade/async'; /** @@ -15,6 +15,7 @@ import {PromiseWrapper, ObservableWrapper} from 'angular2/src/facade/async'; export class Testability { /** @internal */ _pendingCount: number = 0; + _isZoneStable: boolean = true; /** * Whether any work was done since the last 'whenStable' callback. This is * useful to detect if this could have potentially destabilized another @@ -24,23 +25,22 @@ export class Testability { _didWork: boolean = false; /** @internal */ _callbacks: Function[] = []; - /** @internal */ - _isAngularEventPending: boolean = false; - constructor(_ngZone: NgZone) { this._watchAngularEvents(_ngZone); } + constructor(private _ngZone: NgZone) { this._watchAngularEvents(); } /** @internal */ - _watchAngularEvents(_ngZone: NgZone): void { - ObservableWrapper.subscribe(_ngZone.onTurnStart, (_) => { + _watchAngularEvents(): void { + ObservableWrapper.subscribe(this._ngZone.onUnstable, (_) => { this._didWork = true; - this._isAngularEventPending = true; + this._isZoneStable = false; }); - _ngZone.runOutsideAngular(() => { - ObservableWrapper.subscribe(_ngZone.onEventDone, (_) => { - if (!_ngZone.hasPendingTimers) { - this._isAngularEventPending = false; + this._ngZone.runOutsideAngular(() => { + ObservableWrapper.subscribe(this._ngZone.onStable, (_) => { + NgZone.assertNotInAngularZone(); + scheduleMicroTask(() => { + this._isZoneStable = true; this._runCallbacksIfReady(); - } + }); }); }); } @@ -60,22 +60,24 @@ export class Testability { return this._pendingCount; } - isStable(): boolean { return this._pendingCount == 0 && !this._isAngularEventPending; } + isStable(): boolean { + return this._isZoneStable && this._pendingCount == 0 && !this._ngZone.hasPendingMacrotasks; + } /** @internal */ _runCallbacksIfReady(): void { - if (!this.isStable()) { + if (this.isStable()) { + // Schedules the call backs in a new frame so that it is always async. + scheduleMicroTask(() => { + while (this._callbacks.length !== 0) { + (this._callbacks.pop())(this._didWork); + } + this._didWork = false; + }); + } else { + // Not Ready this._didWork = true; - return; // Not ready } - - // Schedules the call backs in a new frame so that it is always async. - PromiseWrapper.resolve(null).then((_) => { - while (this._callbacks.length !== 0) { - (this._callbacks.pop())(this._didWork); - } - this._didWork = false; - }); } whenStable(callback: Function): void { @@ -85,10 +87,6 @@ export class Testability { getPendingRequestCount(): number { return this._pendingCount; } - // This only accounts for ngZone, and not pending counts. Use `whenStable` to - // check for stability. - isAngularEventPending(): boolean { return this._isAngularEventPending; } - findBindings(using: any, provider: string, exactMatch: boolean): any[] { // TODO(juliemr): implement. return []; diff --git a/modules/angular2/src/core/zone.ts b/modules/angular2/src/core/zone.ts index 6f3b3f93e3..176d2a8f6b 100644 --- a/modules/angular2/src/core/zone.ts +++ b/modules/angular2/src/core/zone.ts @@ -1,2 +1,2 @@ // Public API for Zone -export {NgZone, ZeroArgFunction, ErrorHandlingFn, NgZoneError} from './zone/ng_zone'; +export {NgZone, NgZoneError} from './zone/ng_zone'; diff --git a/modules/angular2/src/core/zone/ng_zone.dart b/modules/angular2/src/core/zone/ng_zone.dart deleted file mode 100644 index de4ba2c034..0000000000 --- a/modules/angular2/src/core/zone/ng_zone.dart +++ /dev/null @@ -1,394 +0,0 @@ -library angular.zone; - -import 'dart:async'; -import 'package:stack_trace/stack_trace.dart' show Chain; - -typedef void ZeroArgFunction(); -typedef void ErrorHandlingFn(error, stackTrace); - -/** - * A `Timer` wrapper that lets you specify additional functions to call when it - * is cancelled. - */ -class WrappedTimer implements Timer { - Timer _timer; - ZeroArgFunction _onCancelCb; - - WrappedTimer(Timer timer) { - _timer = timer; - } - - void addOnCancelCb(ZeroArgFunction onCancelCb) { - if (this._onCancelCb != null) { - throw "On cancel cb already registered"; - } - this._onCancelCb = onCancelCb; - } - - void cancel() { - if (this._onCancelCb != null) { - this._onCancelCb(); - } - _timer.cancel(); - } - - bool get isActive => _timer.isActive; -} - -/** - * Stores error information; delivered via [NgZone.onError] stream. - */ -class NgZoneError { - /// Error object thrown. - final error; - /// Either long or short chain of stack traces. - final List stackTrace; - NgZoneError(this.error, this.stackTrace); -} - -/** - * A `Zone` wrapper that lets you schedule tasks after its private microtask queue is exhausted but - * before the next "VM turn", i.e. event loop iteration. - * - * This lets you freely schedule microtasks that prepare data, and set an {@link onTurnDone} handler that - * will consume that data after it's ready but before the browser has a chance to re-render. - * - * A VM turn consist of a single macrotask followed 0 to many microtasks. - * - * The wrapper maintains an "inner" and "mount" `Zone`. The application code will executes - * in the "inner" zone unless `runOutsideAngular` is explicitely called. - * - * A typical application will create a singleton `NgZone`. The mount zone is the `Zone` where the singleton has been - * instantiated. The default `onTurnDone` runs the Angular change detection. - */ -class NgZone { - ZeroArgFunction _onTurnStart; - ZeroArgFunction _onTurnDone; - ZeroArgFunction _onEventDone; - ErrorHandlingFn _onErrorHandler; - - final _onTurnStartCtrl = new StreamController.broadcast(sync: true); - final _onTurnDoneCtrl = new StreamController.broadcast(sync: true); - final _onEventDoneCtrl = new StreamController.broadcast(sync: true); - final _onErrorCtrl = - new StreamController.broadcast(sync: true); - - // Code executed in _mountZone does not trigger the onTurnDone. - Zone _mountZone; - // _innerZone is the child of _mountZone. Any code executed in this zone will trigger the - // onTurnDone hook at the end of the current VM turn. - Zone _innerZone; - - // Number of microtasks pending from _innerZone (& descendants) - int _pendingMicrotasks = 0; - // Whether some code has been executed in the _innerZone (& descendants) in the current turn - bool _hasExecutedCodeInInnerZone = false; - // _outerRun() call depth. 0 at the end of a macrotask - // zone.run(() => { // top-level call - // zone.run(() => {}); // nested call -> in-turn - // }); // we should only check for the end of a turn once the top-level run ends - int _nestedRun = 0; - - bool _inVmTurnDone = false; - - List _pendingTimers = []; - - /** - * Associates with this - * - * - a "mount" [Zone], which is a the one that instantiated this. - * - an "inner" [Zone], which is a child of the mount [Zone]. - * - * @param {bool} enableLongStackTrace whether to enable long stack trace. They should only be - * enabled in development mode as they significantly impact perf. - */ - NgZone({bool enableLongStackTrace}) { - _mountZone = Zone.current; - - if (enableLongStackTrace) { - _innerZone = Chain.capture(() => _createInnerZone(Zone.current), - onError: _onErrorWithLongStackTrace); - } else { - _innerZone = _createInnerZone(Zone.current, - handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, - error, StackTrace trace) => - _onErrorWithoutLongStackTrace(error, trace)); - } - } - - /** - * Sets the zone hook that is called just before Angular event turn starts. - * It is called once per browser event. - */ - @Deprecated('Use onTurnStart Stream instead') - void overrideOnTurnStart(ZeroArgFunction onTurnStartFn) { - _onTurnStart = onTurnStartFn; - } - - void _notifyOnTurnStart() { - this._onTurnStartCtrl.add(null); - } - - /** - * Notifies subscribers just before Angular event turn starts. - * - * Emits an event once per browser task that is handled by Angular. - */ - Stream get onTurnStart => _onTurnStartCtrl.stream; - - /** - * Sets the zone hook that is called immediately after Angular processes - * all pending microtasks. - */ - @Deprecated('Use onTurnDone Stream instead') - void overrideOnTurnDone(ZeroArgFunction onTurnDoneFn) { - _onTurnDone = onTurnDoneFn; - } - - /** - * Notifies subscribers immediately after the Angular zone is done processing - * the current turn and any microtasks scheduled from that turn. - * - * Used by Angular as a signal to kick off change-detection. - */ - Stream get onTurnDone => _onTurnDoneCtrl.stream; - - void _notifyOnTurnDone() { - this._onTurnDoneCtrl.add(null); - } - - /** - * Sets the zone hook that is called immediately after the last turn in - * an event completes. At this point Angular will no longer attempt to - * sync the UI. Any changes to the data model will not be reflected in the - * DOM. `onEventDoneFn` is executed outside Angular zone. - * - * This hook is useful for validating application state (e.g. in a test). - */ - @Deprecated('Use onEventDone Stream instead') - void overrideOnEventDone(ZeroArgFunction onEventDoneFn, - [bool waitForAsync = false]) { - _onEventDone = onEventDoneFn; - - if (waitForAsync) { - _onEventDone = () { - if (_pendingTimers.length == 0) { - onEventDoneFn(); - } - }; - } - } - - /** - * Notifies subscribers immediately after the final `onTurnDone` callback - * before ending VM event. - * - * This event is useful for validating application state (e.g. in a test). - */ - Stream get onEventDone => _onEventDoneCtrl.stream; - - void _notifyOnEventDone() { - this._onEventDoneCtrl.add(null); - } - - /** - * Whether there are any outstanding microtasks. - */ - bool get hasPendingMicrotasks => _pendingMicrotasks > 0; - - /** - * Whether there are any outstanding timers. - */ - bool get hasPendingTimers => _pendingTimers.isNotEmpty; - - /** - * Whether there are any outstanding asychnronous tasks of any kind that are - * scheduled to run within Angular zone. - * - * Useful as a signal of UI stability. For example, when a test reaches a - * point when [hasPendingAsyncTasks] is `false` it might be a good time to run - * test expectations. - */ - bool get hasPendingAsyncTasks => hasPendingMicrotasks || hasPendingTimers; - - /** - * Sets the zone hook that is called when an error is uncaught in the - * Angular zone. The first argument is the error. The second argument is - * the stack trace. - */ - @Deprecated('Use onError Stream instead') - void overrideOnErrorHandler(ErrorHandlingFn errorHandlingFn) { - _onErrorHandler = errorHandlingFn; - } - - /** - * Notifies subscribers whenever an error happens within the zone. - * - * Useful for logging. - */ - Stream get onError => _onErrorCtrl.stream; - - /** - * Runs `fn` in the inner zone and returns whatever it returns. - * - * In a typical app where the inner zone is the Angular zone, this allows one to make use of the - * Angular's auto digest mechanism. - * - * ``` - * NgZone zone = ; - * - * void functionCalledFromJS() { - * zone.run(() { - * // auto-digest will run after this function is called from JS - * }); - * } - * ``` - */ - dynamic run(fn()) { - // Using runGuarded() is required when executing sync code with Dart otherwise handleUncaughtError() - // would not be called on exceptions. - // see https://code.google.com/p/dart/issues/detail?id=19566 for details. - return _innerZone.runGuarded(fn); - } - - /** - * Runs `fn` in the mount zone and returns whatever it returns. - * - * In a typical app where the inner zone is the Angular zone, this allows one to escape Angular's - * auto-digest mechanism. - * - * ``` - * void myFunction(NgZone zone, Element element) { - * element.onClick.listen(() { - * // auto-digest will run after element click. - * }); - * zone.runOutsideAngular(() { - * element.onMouseMove.listen(() { - * // auto-digest will NOT run after mouse move - * }); - * }); - * } - * ``` - */ - dynamic runOutsideAngular(fn()) { - return _mountZone.run(fn); - } - - void _maybeStartVmTurn(ZoneDelegate parent) { - if (!_hasExecutedCodeInInnerZone) { - _hasExecutedCodeInInnerZone = true; - parent.run(_innerZone, _notifyOnTurnStart); - if (_onTurnStart != null) { - parent.run(_innerZone, _onTurnStart); - } - } - } - - dynamic _run(Zone self, ZoneDelegate parent, Zone zone, fn()) { - try { - _nestedRun++; - _maybeStartVmTurn(parent); - return parent.run(zone, fn); - } finally { - _nestedRun--; - // If there are no more pending microtasks and we are not in a recursive call, this is the end of a turn - if (_pendingMicrotasks == 0 && _nestedRun == 0 && !_inVmTurnDone) { - if (_hasExecutedCodeInInnerZone) { - // Trigger onTurnDone at the end of a turn if _innerZone has executed some code - try { - _inVmTurnDone = true; - _notifyOnTurnDone(); - if (_onTurnDone != null) { - parent.run(_innerZone, _onTurnDone); - } - } finally { - _inVmTurnDone = false; - _hasExecutedCodeInInnerZone = false; - } - } - - if (_pendingMicrotasks == 0) { - _notifyOnEventDone(); - if (_onEventDone != null) { - runOutsideAngular(_onEventDone); - } - } - } - } - } - - dynamic _runUnary(Zone self, ZoneDelegate parent, Zone zone, fn(arg), arg) => - _run(self, parent, zone, () => fn(arg)); - - dynamic _runBinary(Zone self, ZoneDelegate parent, Zone zone, fn(arg1, arg2), - arg1, arg2) => - _run(self, parent, zone, () => fn(arg1, arg2)); - - void _scheduleMicrotask(Zone self, ZoneDelegate parent, Zone zone, fn) { - _pendingMicrotasks++; - var microtask = () { - try { - fn(); - } finally { - _pendingMicrotasks--; - } - }; - parent.scheduleMicrotask(zone, microtask); - } - - // Called by Chain.capture() on errors when long stack traces are enabled - void _onErrorWithLongStackTrace(error, Chain chain) { - if (_onErrorHandler != null || _onErrorCtrl.hasListener) { - final traces = chain.terse.traces.map((t) => t.toString()).toList(); - if (_onErrorCtrl.hasListener) { - _onErrorCtrl.add(new NgZoneError(error, traces)); - } - if (_onErrorHandler != null) { - _onErrorHandler(error, traces); - } - } else { - throw error; - } - } - - // Outer zone handleUnchaughtError when long stack traces are not used - void _onErrorWithoutLongStackTrace(error, StackTrace trace) { - if (_onErrorHandler != null || _onErrorCtrl.hasListener) { - if (_onErrorHandler != null) { - _onErrorHandler(error, [trace.toString()]); - } - if (_onErrorCtrl.hasListener) { - _onErrorCtrl.add(new NgZoneError(error, [trace.toString()])); - } - } else { - throw error; - } - } - - Timer _createTimer( - Zone self, ZoneDelegate parent, Zone zone, Duration duration, fn()) { - WrappedTimer wrappedTimer; - var cb = () { - fn(); - _pendingTimers.remove(wrappedTimer); - }; - Timer timer = parent.createTimer(zone, duration, cb); - wrappedTimer = new WrappedTimer(timer); - wrappedTimer.addOnCancelCb(() => _pendingTimers.remove(wrappedTimer)); - - _pendingTimers.add(wrappedTimer); - return wrappedTimer; - } - - Zone _createInnerZone(Zone zone, {handleUncaughtError}) { - return zone.fork( - specification: new ZoneSpecification( - scheduleMicrotask: _scheduleMicrotask, - run: _run, - runUnary: _runUnary, - runBinary: _runBinary, - handleUncaughtError: handleUncaughtError, - createTimer: _createTimer), - zoneValues: {'_innerZone': true}); - } -} diff --git a/modules/angular2/src/core/zone/ng_zone.ts b/modules/angular2/src/core/zone/ng_zone.ts index abb086c3d8..c593fdf4ba 100644 --- a/modules/angular2/src/core/zone/ng_zone.ts +++ b/modules/angular2/src/core/zone/ng_zone.ts @@ -1,29 +1,8 @@ -import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; -import {normalizeBlank, isPresent, global, ZoneLike} from 'angular2/src/facade/lang'; -import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async'; -import {wtfLeave, wtfCreateScope, WtfScopeFn} from '../profile/profile'; +import {EventEmitter} from 'angular2/src/facade/async'; +import {NgZoneImpl, NgZoneError} from './ng_zone_impl'; +import {BaseException} from '../../facade/exceptions'; +export {NgZoneError} from './ng_zone_impl'; -export interface NgZoneZone extends ZoneLike { - /** @internal */ - _innerZone: boolean; -} - -/** - * Interface for a function with zero arguments. - */ -export interface ZeroArgFunction { (): void; } - -/** - * Function type for an error handler, which takes an error and a stack trace. - */ -export interface ErrorHandlingFn { (error: any, stackTrace: any): void; } - -/** - * Stores error information; delivered via [NgZone.onError] stream. - */ -export class NgZoneError { - constructor(public error: any, public stackTrace: any) {} -} /** * An injectable service for executing work inside or outside of the Angular zone. @@ -97,206 +76,120 @@ export class NgZoneError { * ``` */ export class NgZone { - /** @internal */ - _runScope: WtfScopeFn = wtfCreateScope(`NgZone#run()`); - /** @internal */ - _microtaskScope: WtfScopeFn = wtfCreateScope(`NgZone#microtask()`); + static isInAngularZone(): boolean { return NgZoneImpl.isInAngularZone(); } + static assertInAngularZone(): void { + if (!NgZoneImpl.isInAngularZone()) { + throw new BaseException('Expected to be in Angular Zone, but it is not!'); + } + } + static assertNotInAngularZone(): void { + if (NgZoneImpl.isInAngularZone()) { + throw new BaseException('Expected to not be in Angular Zone, but it is!'); + } + } - // Code executed in _mountZone does not trigger the onTurnDone. - /** @internal */ - _mountZone; - // _innerZone is the child of _mountZone. Any code executed in this zone will trigger the - // onTurnDone hook at the end of the current VM turn. - /** @internal */ - _innerZone; + private _zoneImpl: NgZoneImpl; + + private _hasPendingMicrotasks: boolean = false; + private _hasPendingMacrotasks: boolean = false; /** @internal */ - _onTurnStart: ZeroArgFunction; + private _isStable = true; /** @internal */ - _onTurnDone: ZeroArgFunction; + private _nesting = 0; /** @internal */ - _onEventDone: ZeroArgFunction; + private _onUnstable: EventEmitter = new EventEmitter(false); /** @internal */ - _onErrorHandler: ErrorHandlingFn; - + private _onMicrotaskEmpty: EventEmitter = new EventEmitter(false); /** @internal */ - _onTurnStartEvents: EventEmitter; + private _onStable: EventEmitter = new EventEmitter(false); /** @internal */ - _onTurnDoneEvents: EventEmitter; - /** @internal */ - _onEventDoneEvents: EventEmitter; - /** @internal */ - _onErrorEvents: EventEmitter; - - // Number of microtasks pending from _innerZone (& descendants) - /** @internal */ - _pendingMicrotasks: number = 0; - // Whether some code has been executed in the _innerZone (& descendants) in the current turn - /** @internal */ - _hasExecutedCodeInInnerZone: boolean = false; - // run() call depth in _mountZone. 0 at the end of a macrotask - // zone.run(() => { // top-level call - // zone.run(() => {}); // nested call -> in-turn - // }); - /** @internal */ - _nestedRun: number = 0; - - // TODO(vicb): implement this class properly for node.js environment - // This disabled flag is only here to please cjs tests - /** @internal */ - _disabled: boolean; - - /** @internal */ - _inVmTurnDone: boolean = false; - - /** @internal */ - _pendingTimeouts: number[] = []; + private _onErrorEvents: EventEmitter = new EventEmitter(false); /** * @param {bool} enableLongStackTrace whether to enable long stack trace. They should only be * enabled in development mode as they significantly impact perf. */ - constructor({enableLongStackTrace}) { - if (global.zone) { - this._disabled = false; - this._mountZone = global.zone; - this._innerZone = this._createInnerZone(this._mountZone, enableLongStackTrace); - } else { - this._disabled = true; - this._mountZone = null; - } - this._onTurnStartEvents = new EventEmitter(false); - this._onTurnDoneEvents = new EventEmitter(false); - this._onEventDoneEvents = new EventEmitter(false); - this._onErrorEvents = new EventEmitter(false); - } - - /** - * Sets the zone hook that is called just before a browser task that is handled by Angular - * executes. - * - * The hook is called once per browser task that is handled by Angular. - * - * Setting the hook overrides any previously set hook. - * - * @deprecated this API will be removed in the future. Use `onTurnStart` instead. - */ - overrideOnTurnStart(onTurnStartHook: ZeroArgFunction): void { - this._onTurnStart = normalizeBlank(onTurnStartHook); - } - - /** - * Notifies subscribers just before Angular event turn starts. - * - * Emits an event once per browser task that is handled by Angular. - */ - get onTurnStart(): /* Subject */ any { return this._onTurnStartEvents; } - - /** @internal */ - _notifyOnTurnStart(parentRun): void { - parentRun.call(this._innerZone, () => { this._onTurnStartEvents.emit(null); }); - } - - /** - * Sets the zone hook that is called immediately after Angular zone is done processing the current - * task and any microtasks scheduled from that task. - * - * This is where we typically do change-detection. - * - * The hook is called once per browser task that is handled by Angular. - * - * Setting the hook overrides any previously set hook. - * - * @deprecated this API will be removed in the future. Use `onTurnDone` instead. - */ - overrideOnTurnDone(onTurnDoneHook: ZeroArgFunction): void { - this._onTurnDone = normalizeBlank(onTurnDoneHook); - } - - /** - * Notifies subscribers immediately after Angular zone is done processing - * the current turn and any microtasks scheduled from that turn. - * - * Used by Angular as a signal to kick off change-detection. - */ - get onTurnDone() { return this._onTurnDoneEvents; } - - /** @internal */ - _notifyOnTurnDone(parentRun): void { - parentRun.call(this._innerZone, () => { this._onTurnDoneEvents.emit(null); }); - } - - /** - * Sets the zone hook that is called immediately after the `onTurnDone` callback is called and any - * microstasks scheduled from within that callback are drained. - * - * `onEventDoneFn` is executed outside Angular zone, which means that we will no longer attempt to - * sync the UI with any model changes that occur within this callback. - * - * This hook is useful for validating application state (e.g. in a test). - * - * Setting the hook overrides any previously set hook. - * - * @deprecated this API will be removed in the future. Use `onEventDone` instead. - */ - overrideOnEventDone(onEventDoneFn: ZeroArgFunction, opt_waitForAsync: boolean = false): void { - var normalizedOnEventDone = normalizeBlank(onEventDoneFn); - if (opt_waitForAsync) { - this._onEventDone = () => { - if (!this._pendingTimeouts.length) { - normalizedOnEventDone(); + constructor({enableLongStackTrace = false}) { + this._zoneImpl = new NgZoneImpl({ + trace: enableLongStackTrace, + onEnter: () => { + // console.log('ZONE.enter', this._nesting, this._isStable); + this._nesting++; + if (this._isStable) { + this._isStable = false; + this._onUnstable.emit(null); } - }; - } else { - this._onEventDone = normalizedOnEventDone; - } + }, + onLeave: () => { + this._nesting--; + // console.log('ZONE.leave', this._nesting, this._isStable); + this._checkStable(); + }, + setMicrotask: (hasMicrotasks: boolean) => { + this._hasPendingMicrotasks = hasMicrotasks; + this._checkStable(); + }, + setMacrotask: (hasMacrotasks: boolean) => { this._hasPendingMacrotasks = hasMacrotasks; }, + onError: (error: NgZoneError) => this._onErrorEvents.emit(error) + }); } + private _checkStable() { + if (this._nesting == 0) { + if (!this._hasPendingMicrotasks && !this._isStable) { + try { + // console.log('ZONE.microtaskEmpty'); + this._nesting++; + this._onMicrotaskEmpty.emit(null); + } finally { + this._nesting--; + if (!this._hasPendingMicrotasks) { + try { + // console.log('ZONE.stable', this._nesting, this._isStable); + this.runOutsideAngular(() => this._onStable.emit(null)); + } finally { + this._isStable = true; + } + } + } + } + } + }; + /** - * Notifies subscribers immediately after the final `onTurnDone` callback - * before ending VM event. - * - * This event is useful for validating application state (e.g. in a test). + * Notifies when code enters Angular Zone. This gets fired first on VM Turn. */ - get onEventDone() { return this._onEventDoneEvents; } + get onUnstable(): EventEmitter { return this._onUnstable; } - /** @internal */ - _notifyOnEventDone(): void { - this.runOutsideAngular(() => { this._onEventDoneEvents.emit(null); }); - } + /** + * Notifies when there is no more microtasks enqueue in the current VM Turn. + * This is a hint for Angular to do change detection, which may enqueue more microtasks. + * For this reason this event can fire multiple times per VM Turn. + */ + get onMicrotaskEmpty(): EventEmitter { return this._onMicrotaskEmpty; } + + /** + * Notifies when the last `onMicrotaskEmpty` has run and there are no more microtasks, which + * implies we are about to relinquish VM turn. + * This event gets called just once. + */ + get onStable(): EventEmitter { return this._onStable; } + + /** + * Notify that an error has been delivered. + */ + get onError(): EventEmitter { return this._onErrorEvents; } /** * Whether there are any outstanding microtasks. */ - get hasPendingMicrotasks(): boolean { return this._pendingMicrotasks > 0; } + get hasPendingMicrotasks(): boolean { return this._hasPendingMicrotasks; } /** - * Whether there are any outstanding timers. + * Whether there are any outstanding microtasks. */ - get hasPendingTimers(): boolean { return this._pendingTimeouts.length > 0; } - - /** - * Whether there are any outstanding asynchronous tasks of any kind that are - * scheduled to run within Angular zone. - * - * Useful as a signal of UI stability. For example, when a test reaches a - * point when [hasPendingAsyncTasks] is `false` it might be a good time to run - * test expectations. - */ - get hasPendingAsyncTasks(): boolean { return this.hasPendingMicrotasks || this.hasPendingTimers; } - - /** - * Sets the zone hook that is called when an error is thrown in the Angular zone. - * - * Setting the hook overrides any previously set hook. - * - * @deprecated this API will be removed in the future. Use `onError` instead. - */ - overrideOnErrorHandler(errorHandler: ErrorHandlingFn) { - this._onErrorHandler = normalizeBlank(errorHandler); - } - - get onError() { return this._onErrorEvents; } + get hasPendingMacrotasks(): boolean { return this._hasPendingMacrotasks; } /** * Executes the `fn` function synchronously within the Angular zone and returns value returned by @@ -308,18 +201,7 @@ export class NgZone { * Any future tasks or microtasks scheduled from within this function will continue executing from * within the Angular zone. */ - run(fn: () => any): any { - if (this._disabled) { - return fn(); - } else { - var s = this._runScope(); - try { - return this._innerZone.run(fn); - } finally { - wtfLeave(s); - } - } - } + run(fn: () => any): any { return this._zoneImpl.runInner(fn); } /** * Executes the `fn` function synchronously in Angular's parent zone and returns value returned by @@ -333,130 +215,5 @@ export class NgZone { * * Use {@link #run} to reenter the Angular zone and do work that updates the application model. */ - runOutsideAngular(fn: () => any): any { - if (this._disabled) { - return fn(); - } else { - return this._mountZone.run(fn); - } - } - - /** @internal */ - _createInnerZone(zone, enableLongStackTrace) { - var microtaskScope = this._microtaskScope; - var ngZone = this; - var errorHandling; - - if (enableLongStackTrace) { - errorHandling = - StringMapWrapper.merge(global.Zone.longStackTraceZone, - {onError: function(e) { ngZone._notifyOnError(this, e); }}); - } else { - errorHandling = {onError: function(e) { ngZone._notifyOnError(this, e); }}; - } - - return zone.fork(errorHandling) - .fork({ - '$run': function(parentRun) { - return function() { - try { - ngZone._nestedRun++; - if (!ngZone._hasExecutedCodeInInnerZone) { - ngZone._hasExecutedCodeInInnerZone = true; - ngZone._notifyOnTurnStart(parentRun); - if (ngZone._onTurnStart) { - parentRun.call(ngZone._innerZone, ngZone._onTurnStart); - } - } - return parentRun.apply(this, arguments); - } finally { - ngZone._nestedRun--; - // If there are no more pending microtasks, we are at the end of a VM turn (or in - // onTurnStart) - // _nestedRun will be 0 at the end of a macrotasks (it could be > 0 when there are - // nested calls - // to run()). - if (ngZone._pendingMicrotasks == 0 && ngZone._nestedRun == 0 && - !this._inVmTurnDone) { - if (ngZone._hasExecutedCodeInInnerZone) { - try { - this._inVmTurnDone = true; - ngZone._notifyOnTurnDone(parentRun); - if (ngZone._onTurnDone) { - parentRun.call(ngZone._innerZone, ngZone._onTurnDone); - } - } finally { - this._inVmTurnDone = false; - ngZone._hasExecutedCodeInInnerZone = false; - } - } - - if (ngZone._pendingMicrotasks === 0) { - ngZone._notifyOnEventDone(); - if (isPresent(ngZone._onEventDone)) { - ngZone.runOutsideAngular(ngZone._onEventDone); - } - } - } - } - }; - }, - '$scheduleMicrotask': function(parentScheduleMicrotask) { - return function(fn) { - ngZone._pendingMicrotasks++; - var microtask = function() { - var s = microtaskScope(); - try { - fn(); - } finally { - ngZone._pendingMicrotasks--; - wtfLeave(s); - } - }; - parentScheduleMicrotask.call(this, microtask); - }; - }, - '$setTimeout': function(parentSetTimeout) { - return function(fn: Function, delay: number, ...args) { - var id; - var cb = function() { - fn(); - ListWrapper.remove(ngZone._pendingTimeouts, id); - }; - id = parentSetTimeout.call(this, cb, delay, args); - ngZone._pendingTimeouts.push(id); - return id; - }; - }, - '$clearTimeout': function(parentClearTimeout) { - return function(id: number) { - parentClearTimeout.call(this, id); - ListWrapper.remove(ngZone._pendingTimeouts, id); - }; - }, - _innerZone: true - }); - } - - /** @internal */ - _notifyOnError(zone, e): void { - if (isPresent(this._onErrorHandler) || ObservableWrapper.hasSubscribers(this._onErrorEvents)) { - var trace = [normalizeBlank(e.stack)]; - - while (zone && zone.constructedAtException) { - trace.push(zone.constructedAtException.get()); - zone = zone.parent; - } - if (ObservableWrapper.hasSubscribers(this._onErrorEvents)) { - ObservableWrapper.callEmit(this._onErrorEvents, new NgZoneError(e, trace)); - } - if (isPresent(this._onErrorHandler)) { - this._onErrorHandler(e, trace); - } - } else { - console.log('## _notifyOnError ##'); - console.log(e.stack); - throw e; - } - } + runOutsideAngular(fn: () => any): any { return this._zoneImpl.runOuter(fn); } } diff --git a/modules/angular2/src/core/zone/ng_zone_impl.dart b/modules/angular2/src/core/zone/ng_zone_impl.dart new file mode 100644 index 0000000000..2ec1e31dfb --- /dev/null +++ b/modules/angular2/src/core/zone/ng_zone_impl.dart @@ -0,0 +1,222 @@ +library angular.zone; + +import 'dart:async'; +import 'package:stack_trace/stack_trace.dart' show Chain; + +typedef void ZeroArgFunction(); +typedef void ErrorHandlingFn(error, stackTrace); + +/** + * A `Timer` wrapper that lets you specify additional functions to call when it + * is cancelled. + */ +class WrappedTimer implements Timer { + Timer _timer; + ZeroArgFunction _onCancelCb; + + WrappedTimer(Timer timer) { + _timer = timer; + } + + void addOnCancelCb(ZeroArgFunction onCancelCb) { + if (this._onCancelCb != null) { + throw "On cancel cb already registered"; + } + this._onCancelCb = onCancelCb; + } + + void cancel() { + if (this._onCancelCb != null) { + this._onCancelCb(); + } + _timer.cancel(); + } + + bool get isActive => _timer.isActive; +} + +/** + * Stores error information; delivered via [NgZone.onError] stream. + */ +class NgZoneError { + /// Error object thrown. + final error; + /// Either long or short chain of stack traces. + final List stackTrace; + NgZoneError(this.error, this.stackTrace); +} + +/** + * A `Zone` wrapper that lets you schedule tasks after its private microtask queue is exhausted but + * before the next "VM turn", i.e. event loop iteration. + * + * This lets you freely schedule microtasks that prepare data, and set an {@link onMicrotaskEmpty} handler that + * will consume that data after it's ready but before the browser has a chance to re-render. + * + * A VM turn consist of a single macrotask followed 0 to many microtasks. + * + * The wrapper maintains an "inner" and "mount" `Zone`. The application code will executes + * in the "inner" zone unless `runOutsideAngular` is explicitely called. + * + * A typical application will create a singleton `NgZone`. The mount zone is the `Zone` where the singleton has been + * instantiated. The default `onMicrotaskEmpty` runs the Angular change detection. + */ +class NgZoneImpl { + static bool isInAngularZone() { + return Zone.current['isAngularZone'] == true; + } + + // Number of microtasks pending from _innerZone (& descendants) + int _pendingMicrotasks = 0; + List _pendingTimers = []; + Function onEnter; + Function onLeave; + Function setMicrotask; + Function setMacrotask; + Function onError; + + Zone _outerZone; + Zone _innerZone; + /** + * Associates with this + * + * - a "mount" [Zone], which is a the one that instantiated this. + * - an "inner" [Zone], which is a child of the mount [Zone]. + * + * @param {bool} trace whether to enable long stack trace. They should only be + * enabled in development mode as they significantly impact perf. + */ + NgZoneImpl({ + bool trace, + Function this.onEnter, + Function this.onLeave, + Function this.setMicrotask, + Function this.setMacrotask, + Function this.onError + }) { + _outerZone = Zone.current; + + if (trace) { + _innerZone = Chain.capture( + () => _createInnerZone(Zone.current), + onError: _onErrorWithLongStackTrace + ); + } else { + _innerZone = _createInnerZone( + Zone.current, + handleUncaughtError: _onErrorWithoutLongStackTrace + ); + } + } + + Zone _createInnerZone(Zone zone, {handleUncaughtError}) { + return zone.fork( + specification: new ZoneSpecification( + scheduleMicrotask: _scheduleMicrotask, + run: _run, + runUnary: _runUnary, + runBinary: _runBinary, + handleUncaughtError: handleUncaughtError, + createTimer: _createTimer), + zoneValues: {'isAngularZone': true} + ); + } + + dynamic runInner(fn()) { + return _innerZone.runGuarded(fn); + } + + /** + * Runs `fn` in the mount zone and returns whatever it returns. + * + * In a typical app where the inner zone is the Angular zone, this allows one to escape Angular's + * auto-digest mechanism. + * + * ``` + * void myFunction(NgZone zone, Element element) { + * element.onClick.listen(() { + * // auto-digest will run after element click. + * }); + * zone.runOutsideAngular(() { + * element.onMouseMove.listen(() { + * // auto-digest will NOT run after mouse move + * }); + * }); + * } + * ``` + */ + dynamic runOuter(fn()) { + return _outerZone.run(fn); + } + + dynamic _run(Zone self, ZoneDelegate parent, Zone zone, fn()) { + try { + onEnter(); + return parent.run(zone, fn); + } finally { + onLeave(); + } + } + + dynamic _runUnary(Zone self, ZoneDelegate parent, Zone zone, fn(arg), arg) => + _run(self, parent, zone, () => fn(arg)); + + dynamic _runBinary(Zone self, ZoneDelegate parent, Zone zone, fn(arg1, arg2), + arg1, arg2) => + _run(self, parent, zone, () => fn(arg1, arg2)); + + void _scheduleMicrotask(Zone self, ZoneDelegate parent, Zone zone, fn) { + if (_pendingMicrotasks == 0) { + setMicrotask(true); + } + _pendingMicrotasks++; + var microtask = () { + try { + fn(); + } finally { + _pendingMicrotasks--; + if (_pendingMicrotasks == 0) { + setMicrotask(false); + } + } + }; + parent.scheduleMicrotask(zone, microtask); + } + + // Called by Chain.capture() on errors when long stack traces are enabled + void _onErrorWithLongStackTrace(error, Chain chain) { + final traces = chain.terse.traces.map((t) => t.toString()).toList(); + onError(new NgZoneError(error, traces)); + } + + // Outer zone handleUnchaughtError when long stack traces are not used + void _onErrorWithoutLongStackTrace(Zone self, ZoneDelegate parent, Zone zone, + error, StackTrace trace) + { + onError(new NgZoneError(error, [trace.toString()])); + } + + Timer _createTimer( + Zone self, ZoneDelegate parent, Zone zone, Duration duration, fn()) { + WrappedTimer wrappedTimer; + var cb = () { + try { + fn(); + } finally { + _pendingTimers.remove(wrappedTimer); + setMacrotask(_pendingTimers.isNotEmpty); + } + }; + Timer timer = parent.createTimer(zone, duration, cb); + wrappedTimer = new WrappedTimer(timer); + wrappedTimer.addOnCancelCb(() { + _pendingTimers.remove(wrappedTimer); + setMacrotask(_pendingTimers.isNotEmpty); + }); + + _pendingTimers.add(wrappedTimer); + setMacrotask(true); + return wrappedTimer; + } + +} diff --git a/modules/angular2/src/core/zone/ng_zone_impl.ts b/modules/angular2/src/core/zone/ng_zone_impl.ts new file mode 100644 index 0000000000..8445eb9adf --- /dev/null +++ b/modules/angular2/src/core/zone/ng_zone_impl.ts @@ -0,0 +1,96 @@ +import {global} from 'angular2/src/facade/lang'; + +/** + * Stores error information; delivered via [NgZone.onError] stream. + */ +export class NgZoneError { + constructor(public error: any, public stackTrace: any) {} +} + + +export class NgZoneImpl implements ZoneSpec { + static isInAngularZone(): boolean { return Zone.current.get('isAngularZone') === true; } + + public name: string = 'angular'; + public properties: {[k: string]: string} = {'isAngularZone': true}; + + private outer: Zone; + private inner: Zone; + + private onEnter: () => void; + private onLeave: () => void; + private setMicrotask: (hasMicrotasks: boolean) => void; + private setMacrotask: (hasMacrotasks: boolean) => void; + private onError: (error: NgZoneError) => void; + + constructor({trace, onEnter, onLeave, setMicrotask, setMacrotask, onError}: { + trace: boolean, + onEnter: () => void, + onLeave: () => void, + setMicrotask: (hasMicrotasks: boolean) => void, + setMacrotask: (hasMacrotasks: boolean) => void, + onError: (error: NgZoneError) => void + }) { + this.onEnter = onEnter; + this.onLeave = onLeave; + this.setMicrotask = setMicrotask; + this.setMacrotask = setMacrotask; + this.onError = onError; + + if (global.Zone) { + this.outer = this.inner = Zone.current; + if (Zone['wtfZoneSpec']) { + this.inner = this.inner.fork(Zone['wtfZoneSpec']); + } + if (trace) { + this.inner = this.inner.fork(Zone['longStackTraceZoneSpec']); + } + this.inner = this.inner.fork(this); + } else { + throw new Error('Angular2 needs to be run with Zone.js polyfill.'); + } + } + + onInvokeTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any, + applyArgs: any): any { + try { + this.onEnter(); + return delegate.invokeTask(target, task, applyThis, applyArgs); + } finally { + this.onLeave(); + } + }; + + + onInvoke(delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function, applyThis: any, + applyArgs: any[], source: string): any { + try { + this.onEnter(); + return delegate.invoke(target, callback, applyThis, applyArgs, source); + } finally { + this.onLeave(); + } + } + + onHasTask(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) { + delegate.hasTask(target, hasTaskState); + if (current == target) { + // We are only interested in hasTask events which originate from our zone + // (A child hasTask event is not interesting to us) + if (hasTaskState.change == 'microTask') { + this.setMicrotask(hasTaskState.microTask); + } else if (hasTaskState.change == 'macroTask') { + this.setMacrotask(hasTaskState.macroTask); + } + } + } + + onHandleError(delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean { + delegate.handleError(target, error); + this.onError(new NgZoneError(error, error.stack)); + return false; + } + + runInner(fn: () => any): any { return this.inner.runGuarded(fn); }; + runOuter(fn: () => any): any { return this.outer.run(fn); }; +} diff --git a/modules/angular2/src/facade/lang.dart b/modules/angular2/src/facade/lang.dart index 2822c27433..bab4f285c1 100644 --- a/modules/angular2/src/facade/lang.dart +++ b/modules/angular2/src/facade/lang.dart @@ -3,7 +3,7 @@ library angular.core.facade.lang; export 'dart:core' show Type, RegExp, print, DateTime; import 'dart:math' as math; import 'dart:convert' as convert; -import 'dart:async' show Future; +import 'dart:async' show Future, Zone; String getTypeNameForDebugging(Type type) => type.toString(); @@ -20,6 +20,10 @@ class CONST { const IS_DART = true; +scheduleMicroTask(Function fn) { + Zone.current.scheduleMicrotask(fn); +} + bool isPresent(Object obj) => obj != null; bool isBlank(Object obj) => obj == null; bool isString(Object obj) => obj is String; diff --git a/modules/angular2/src/facade/lang.ts b/modules/angular2/src/facade/lang.ts index 8d409f7776..bbe947ee18 100644 --- a/modules/angular2/src/facade/lang.ts +++ b/modules/angular2/src/facade/lang.ts @@ -1,14 +1,3 @@ -// Zones are TC-39 standards-track so users could choose a different implementation -// Rather than import {Zone} from 'zone.js' we define an interface -// so that any library that structurally matches may be used with Angular 2. -export interface ZoneLike { - fork(locals?: any): ZoneLike; - run(fn: any, applyTo?: any, applyWith?: any): any; -} -export interface ZoneLikeConstructor { - longStackTraceZone: { [key: string]: any; }; -} - export interface BrowserNodeGlobal { Object: typeof Object; Array: typeof Array; @@ -20,8 +9,7 @@ export interface BrowserNodeGlobal { Math: any; // typeof Math; assert(condition: any): void; Reflect: any; - zone: ZoneLike; - Zone: ZoneLikeConstructor; + Zone: typeof Zone; getAngularTestability: Function; getAllAngularTestabilities: Function; getAllAngularRootElements: Function; @@ -46,6 +34,10 @@ if (typeof window === 'undefined') { globalScope = window; } +export function scheduleMicroTask(fn: Function) { + Zone.current.scheduleMicroTask('scheduleMicrotask', fn); +} + export const IS_DART = false; // Need to declare a new variable for global here since TypeScript diff --git a/modules/angular2/src/mock/ng_zone_mock.ts b/modules/angular2/src/mock/ng_zone_mock.ts index 443db975f6..628527c05f 100644 --- a/modules/angular2/src/mock/ng_zone_mock.ts +++ b/modules/angular2/src/mock/ng_zone_mock.ts @@ -8,18 +8,15 @@ import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; @Injectable() export class MockNgZone extends NgZone { /** @internal */ - _mockOnEventDone: EventEmitter; + private _mockOnStable: EventEmitter = new EventEmitter(false); - constructor() { - super({enableLongStackTrace: false}); - this._mockOnEventDone = new EventEmitter(false); - } + constructor() { super({enableLongStackTrace: false}); } - get onEventDone() { return this._mockOnEventDone; } + get onStable() { return this._mockOnStable; } run(fn: Function): any { return fn(); } runOutsideAngular(fn: Function): any { return fn(); } - simulateZoneExit(): void { ObservableWrapper.callNext(this.onEventDone, null); } + simulateZoneExit(): void { ObservableWrapper.callNext(this.onStable, null); } } diff --git a/modules/angular2/src/router/instruction.ts b/modules/angular2/src/router/instruction.ts index 86f900da51..d3aa798893 100644 --- a/modules/angular2/src/router/instruction.ts +++ b/modules/angular2/src/router/instruction.ts @@ -271,9 +271,9 @@ export class UnresolvedInstruction extends Instruction { if (isPresent(this.component)) { return PromiseWrapper.resolve(this.component); } - return this._resolver().then((resolution: Instruction) => { - this.child = resolution.child; - return this.component = resolution.component; + return this._resolver().then((instruction: Instruction) => { + this.child = isPresent(instruction) ? instruction.child : null; + return this.component = isPresent(instruction) ? instruction.component : null; }); } } diff --git a/modules/angular2/src/testing/fake_async.ts b/modules/angular2/src/testing/fake_async.ts index 49cc35dc54..fca8d06467 100644 --- a/modules/angular2/src/testing/fake_async.ts +++ b/modules/angular2/src/testing/fake_async.ts @@ -1,15 +1,58 @@ import {global} from 'angular2/src/facade/lang'; -import {BaseException, WrappedException} from 'angular2/src/facade/exceptions'; +import {BaseException} from 'angular2/src/facade/exceptions'; import {ListWrapper} from 'angular2/src/facade/collection'; -import {NgZoneZone} from 'angular2/src/core/zone/ng_zone'; var _scheduler; var _microtasks: Function[] = []; var _pendingPeriodicTimers: number[] = []; var _pendingTimers: number[] = []; -interface FakeAsyncZone extends NgZoneZone { - _inFakeAsyncZone: boolean; +class FakeAsyncZoneSpec implements ZoneSpec { + static assertInZone(): void { + if (!Zone.current.get('inFakeAsyncZone')) { + throw new Error('The code should be running in the fakeAsync zone to call this function'); + } + } + + name: string = 'fakeAsync'; + + properties: {[key: string]: any} = {'inFakeAsyncZone': true}; + + onScheduleTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task): Task { + switch (task.type) { + case 'microTask': + _microtasks.push(task.invoke); + break; + case 'macroTask': + switch (task.source) { + case 'setTimeout': + task.data['handleId'] = _setTimeout(task.invoke, task.data['delay'], task.data['args']); + break; + case 'setInterval': + task.data['handleId'] = + _setInterval(task.invoke, task.data['delay'], task.data['args']); + break; + default: + task = delegate.scheduleTask(target, task); + } + break; + case 'eventTask': + task = delegate.scheduleTask(target, task); + break; + } + return task; + } + + onCancelTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task): any { + switch (task.source) { + case 'setTimeout': + return _clearTimeout(task.data['handleId']); + case 'setInterval': + return _clearInterval(task.data['handleId']); + default: + return delegate.scheduleTask(target, task); + } + } } /** @@ -27,18 +70,11 @@ interface FakeAsyncZone extends NgZoneZone { * @returns {Function} The function wrapped to be executed in the fakeAsync zone */ export function fakeAsync(fn: Function): Function { - if ((global.zone)._inFakeAsyncZone) { + if (Zone.current.get('inFakeAsyncZone')) { throw new Error('fakeAsync() calls can not be nested'); } - var fakeAsyncZone = global.zone.fork({ - setTimeout: _setTimeout, - clearTimeout: _clearTimeout, - setInterval: _setInterval, - clearInterval: _clearInterval, - scheduleMicrotask: _scheduleMicrotask, - _inFakeAsyncZone: true - }); + var fakeAsyncZone = Zone.current.fork(new FakeAsyncZoneSpec()); return function(...args) { // TODO(tbosch): This class should already be part of the jasmine typings but it is not... @@ -97,7 +133,7 @@ export function clearPendingTimers(): void { * @param {number} millis Number of millisecond, defaults to 0 */ export function tick(millis: number = 0): void { - _assertInFakeAsyncZone(); + FakeAsyncZoneSpec.assertInZone(); flushMicrotasks(); _scheduler.tick(millis); } @@ -106,14 +142,14 @@ export function tick(millis: number = 0): void { * Flush any pending microtasks. */ export function flushMicrotasks(): void { - _assertInFakeAsyncZone(); + FakeAsyncZoneSpec.assertInZone(); while (_microtasks.length > 0) { var microtask = ListWrapper.removeAt(_microtasks, 0); microtask(); } } -function _setTimeout(fn: Function, delay: number, ...args): number { +function _setTimeout(fn: Function, delay: number, args: any[]): number { var cb = _fnAndFlush(fn); var id = _scheduler.scheduleFunction(cb, delay, args); _pendingTimers.push(id); @@ -145,16 +181,6 @@ function _fnAndFlush(fn: Function): Function { } } -function _scheduleMicrotask(microtask: Function): void { - _microtasks.push(microtask); -} - function _dequeueTimer(id: number): Function { return function() { ListWrapper.remove(_pendingTimers, id); } } - -function _assertInFakeAsyncZone(): void { - if (!global.zone || !(global.zone)._inFakeAsyncZone) { - throw new Error('The code should be running in the fakeAsync zone to call this function'); - } -} diff --git a/modules/angular2/src/testing/testing_internal.dart b/modules/angular2/src/testing/testing_internal.dart index 679ec83089..413852e9fb 100644 --- a/modules/angular2/src/testing/testing_internal.dart +++ b/modules/angular2/src/testing/testing_internal.dart @@ -15,6 +15,8 @@ export 'testing_internal_core.dart' xdescribe; import 'package:angular2/platform/testing/browser.dart'; +import 'package:angular2/src/facade/collection.dart' show StringMapWrapper; +import "package:angular2/src/core/zone/ng_zone.dart" show NgZone; export 'test_injector.dart' show inject; @@ -62,4 +64,6 @@ void ddescribe(name, fn) { void xdescribe(name, fn) { testSetup(); core.xdescribe(name, fn); -} \ No newline at end of file +} + +bool isInInnerZone() => NgZone.isInAngularZone(); diff --git a/modules/angular2/src/testing/testing_internal.ts b/modules/angular2/src/testing/testing_internal.ts index 43a7e1c19b..582da830ca 100644 --- a/modules/angular2/src/testing/testing_internal.ts +++ b/modules/angular2/src/testing/testing_internal.ts @@ -1,12 +1,11 @@ -import {DOM} from 'angular2/src/platform/dom/dom_adapter'; import {StringMapWrapper} from 'angular2/src/facade/collection'; import {global, isFunction, Math} from 'angular2/src/facade/lang'; -import {NgZoneZone} from 'angular2/src/core/zone/ng_zone'; import {provide} from 'angular2/core'; -import {TestInjector, getTestInjector, FunctionWithParamTokens, inject} from './test_injector'; +import {getTestInjector, FunctionWithParamTokens, inject} from './test_injector'; import {browserDetection} from './utils'; +import {NgZone} from 'angular2/src/core/zone/ng_zone'; export {inject} from './test_injector'; @@ -256,7 +255,3 @@ export class SpyObject { return newSpy; } } - -export function isInInnerZone(): boolean { - return (global.zone)._innerZone === true; -} diff --git a/modules/angular2/src/testing/utils.ts b/modules/angular2/src/testing/utils.ts index 57200c7598..741da95efb 100644 --- a/modules/angular2/src/testing/utils.ts +++ b/modules/angular2/src/testing/utils.ts @@ -5,22 +5,21 @@ import {isPresent, isString, RegExpWrapper, StringWrapper, RegExp} from 'angular @Injectable() export class Log { - /** @internal */ - _result: any[]; + logItems: any[]; - constructor() { this._result = []; } + constructor() { this.logItems = []; } - add(value): void { this._result.push(value); } + add(value): void { this.logItems.push(value); } fn(value) { return (a1: any = null, a2: any = null, a3: any = null, a4: any = null, a5: any = null) => { - this._result.push(value); + this.logItems.push(value); } } - clear(): void { this._result = []; } + clear(): void { this.logItems = []; } - result(): string { return this._result.join("; "); } + result(): string { return this.logItems.join("; "); } } export var browserDetection: BrowserDetection = null; diff --git a/modules/angular2/src/upgrade/upgrade_adapter.ts b/modules/angular2/src/upgrade/upgrade_adapter.ts index 2e417f06b3..be3074ff0c 100644 --- a/modules/angular2/src/upgrade/upgrade_adapter.ts +++ b/modules/angular2/src/upgrade/upgrade_adapter.ts @@ -338,7 +338,7 @@ export class UpgradeAdapter { '$rootScope', (injector: angular.IInjectorService, rootScope: angular.IRootScopeService) => { ng1Injector = injector; - ObservableWrapper.subscribe(ngZone.onTurnDone, + ObservableWrapper.subscribe(ngZone.onMicrotaskEmpty, (_) => ngZone.runOutsideAngular(() => rootScope.$apply())); ng1compilePromise = UpgradeNg1ComponentAdapterBuilder.resolve(this.downgradedComponents, injector); diff --git a/modules/angular2/src/web_workers/shared/generic_message_bus.dart b/modules/angular2/src/web_workers/shared/generic_message_bus.dart index 3cfe1fc754..221844f1ea 100644 --- a/modules/angular2/src/web_workers/shared/generic_message_bus.dart +++ b/modules/angular2/src/web_workers/shared/generic_message_bus.dart @@ -46,7 +46,7 @@ abstract class GenericMessageBusSink implements MessageBusSink { void attachToZone(NgZone zone) { _zone = zone; _zone.runOutsideAngular(() { - _zone.onEventDone.listen((_) { + _zone.onStable.listen((_) { if (_messageBuffer.length > 0) { sendMessages(_messageBuffer); _messageBuffer.clear(); diff --git a/modules/angular2/src/web_workers/shared/post_message_bus.ts b/modules/angular2/src/web_workers/shared/post_message_bus.ts index a39095705a..0c1896d4f6 100644 --- a/modules/angular2/src/web_workers/shared/post_message_bus.ts +++ b/modules/angular2/src/web_workers/shared/post_message_bus.ts @@ -22,7 +22,7 @@ export class PostMessageBusSink implements MessageBusSink { attachToZone(zone: NgZone): void { this._zone = zone; this._zone.runOutsideAngular(() => { - ObservableWrapper.subscribe(this._zone.onEventDone, (_) => { this._handleOnEventDone(); }); + ObservableWrapper.subscribe(this._zone.onStable, (_) => { this._handleOnEventDone(); }); }); } diff --git a/modules/angular2/test/common/pipes/async_pipe_spec.ts b/modules/angular2/test/common/pipes/async_pipe_spec.ts index 25ebfbe491..cd30502f7d 100644 --- a/modules/angular2/test/common/pipes/async_pipe_spec.ts +++ b/modules/angular2/test/common/pipes/async_pipe_spec.ts @@ -93,7 +93,7 @@ export function main() { TimerWrapper.setTimeout(() => { expect(ref.spy('markForCheck')).toHaveBeenCalled(); async.done(); - }, 0) + }, 10) })); }); @@ -121,7 +121,7 @@ export function main() { var completer: PromiseCompleter; var ref: SpyChangeDetectorRef; // adds longer timers for passing tests in IE - var timer = (!isBlank(DOM) && browserDetection.isIE) ? 50 : 0; + var timer = (!isBlank(DOM) && browserDetection.isIE) ? 50 : 10; beforeEach(() => { completer = PromiseWrapper.completer(); @@ -174,11 +174,12 @@ export function main() { it("should request a change detection check upon receiving a new value", inject([AsyncTestCompleter], (async) => { + var markForCheck = ref.spy('markForCheck'); pipe.transform(completer.promise); completer.resolve(message); TimerWrapper.setTimeout(() => { - expect(ref.spy('markForCheck')).toHaveBeenCalled(); + expect(markForCheck).toHaveBeenCalled(); async.done(); }, timer) })); diff --git a/modules/angular2/test/core/application_ref_spec.ts b/modules/angular2/test/core/application_ref_spec.ts index e11463fa83..3496db1fa3 100644 --- a/modules/angular2/test/core/application_ref_spec.ts +++ b/modules/angular2/test/core/application_ref_spec.ts @@ -20,6 +20,8 @@ import {Injector, Provider, APP_INITIALIZER} from "angular2/core"; import {ChangeDetectorRef_} from "angular2/src/core/change_detection/change_detector_ref"; import {PromiseWrapper, PromiseCompleter, TimerWrapper} from "angular2/src/facade/async"; import {ListWrapper} from "angular2/src/facade/collection"; +import {ExceptionHandler} from 'angular2/src/facade/exception_handler'; +import {DOM} from 'angular2/src/platform/dom/dom_adapter'; export function main() { describe("ApplicationRef", () => { @@ -33,6 +35,8 @@ export function main() { }); describe("PlatformRef", () => { + var exceptionHandler = + new Provider(ExceptionHandler, {useValue: new ExceptionHandler(DOM, true)}); describe("asyncApplication", () => { function expectProviders(injector: Injector, providers: Array): void { for (let i = 0; i < providers.length; i++) { @@ -44,7 +48,7 @@ export function main() { it("should merge syncronous and asyncronous providers", inject([AsyncTestCompleter, Injector], (async, injector) => { let ref = new PlatformRef_(injector, null); - let ASYNC_PROVIDERS = [new Provider(Foo, {useValue: new Foo()})]; + let ASYNC_PROVIDERS = [new Provider(Foo, {useValue: new Foo()}), exceptionHandler]; let SYNC_PROVIDERS = [new Provider(Bar, {useValue: new Bar()})]; ref.asyncApplication((zone) => PromiseWrapper.resolve(ASYNC_PROVIDERS), SYNC_PROVIDERS) .then((appRef) => { @@ -57,7 +61,7 @@ export function main() { it("should allow function to be null", inject([AsyncTestCompleter, Injector], (async, injector) => { let ref = new PlatformRef_(injector, null); - let SYNC_PROVIDERS = [new Provider(Bar, {useValue: new Bar()})]; + let SYNC_PROVIDERS = [new Provider(Bar, {useValue: new Bar()}), exceptionHandler]; ref.asyncApplication(null, SYNC_PROVIDERS) .then((appRef) => { expectProviders(appRef.injector, SYNC_PROVIDERS); @@ -86,7 +90,7 @@ export function main() { new Provider(APP_INITIALIZER, {useValue: mockAsyncAppInitializer(completer), multi: true}) ]; - ref.asyncApplication(null, SYNC_PROVIDERS) + ref.asyncApplication(null, [SYNC_PROVIDERS, exceptionHandler]) .then((appRef) => { expectProviders(appRef.injector, SYNC_PROVIDERS.slice(0, SYNC_PROVIDERS.length - 1)); @@ -109,7 +113,8 @@ export function main() { deps: [Injector] }) ]; - ref.asyncApplication((zone) => PromiseWrapper.resolve(ASYNC_PROVIDERS), SYNC_PROVIDERS) + ref.asyncApplication((zone) => PromiseWrapper.resolve(ASYNC_PROVIDERS), + [SYNC_PROVIDERS, exceptionHandler]) .then((appRef) => { expectProviders(appRef.injector, SYNC_PROVIDERS.slice(0, SYNC_PROVIDERS.length - 1)); @@ -123,7 +128,7 @@ export function main() { let ref = new PlatformRef_(injector, null); let appInitializer = new Provider( APP_INITIALIZER, {useValue: () => PromiseWrapper.resolve([]), multi: true}); - expect(() => ref.application([appInitializer])) + expect(() => ref.application([appInitializer, exceptionHandler])) .toThrowError( "Cannot use asyncronous app initializers with application. Use asyncApplication instead."); })); diff --git a/modules/angular2/test/core/change_detection/differs/default_iterable_differ_spec.ts b/modules/angular2/test/core/change_detection/differs/default_iterable_differ_spec.ts index 0d469119f9..1668d114a0 100644 --- a/modules/angular2/test/core/change_detection/differs/default_iterable_differ_spec.ts +++ b/modules/angular2/test/core/change_detection/differs/default_iterable_differ_spec.ts @@ -128,7 +128,7 @@ export function main() { })); }); - it('should handle swapping element', () => { + it('should handle incremental swapping element', () => { let l = ['a', 'b', 'c']; differ.check(l); diff --git a/modules/angular2/test/core/testability/testability_spec.ts b/modules/angular2/test/core/testability/testability_spec.ts index 1f1862f935..8896127db7 100644 --- a/modules/angular2/test/core/testability/testability_spec.ts +++ b/modules/angular2/test/core/testability/testability_spec.ts @@ -14,36 +14,43 @@ import { } from 'angular2/testing_internal'; import {Testability} from 'angular2/src/core/testability/testability'; import {NgZone} from 'angular2/src/core/zone/ng_zone'; -import {normalizeBlank} from 'angular2/src/facade/lang'; +import {normalizeBlank, scheduleMicroTask} from 'angular2/src/facade/lang'; import {PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; // Schedules a microtasks (using a resolved promise .then()) function microTask(fn: Function): void { - PromiseWrapper.resolve(null).then((_) => { fn(); }); + scheduleMicroTask(() => { + // We do double dispatch so that we can wait for scheduleMicrotas in the Testability when + // NgZone becomes stable. + scheduleMicroTask(fn); + }); } @Injectable() class MockNgZone extends NgZone { - _onTurnStartStream: EventEmitter; - get onTurnStart() { return this._onTurnStartStream; } + _onUnstableStream: EventEmitter; + get onUnstable() { return this._onUnstableStream; } - _onEventDoneStream: EventEmitter; - get onEventDone() { return this._onEventDoneStream; } + _onStableStream: EventEmitter; + get onStable() { return this._onStableStream; } constructor() { super({enableLongStackTrace: false}); - this._onTurnStartStream = new EventEmitter(false); - this._onEventDoneStream = new EventEmitter(false); + this._onUnstableStream = new EventEmitter(false); + this._onStableStream = new EventEmitter(false); } - start(): void { ObservableWrapper.callEmit(this._onTurnStartStream, null); } + unstable(): void { ObservableWrapper.callEmit(this._onUnstableStream, null); } - finish(): void { ObservableWrapper.callEmit(this._onEventDoneStream, null); } + stable(): void { ObservableWrapper.callEmit(this._onStableStream, null); } } export function main() { describe('Testability', () => { - var testability, execute, execute2, ngZone; + var testability: Testability; + var execute: any; + var execute2: any; + var ngZone: MockNgZone; beforeEach(() => { ngZone = new MockNgZone(); @@ -142,13 +149,10 @@ export function main() { }); describe('NgZone callback logic', () => { - it('should start being ready', - () => { expect(testability.isAngularEventPending()).toEqual(false); }); - it('should fire whenstable callback if event is already finished', inject([AsyncTestCompleter], (async) => { - ngZone.start(); - ngZone.finish(); + ngZone.unstable(); + ngZone.stable(); testability.whenStable(execute); microTask(() => { @@ -158,8 +162,8 @@ export function main() { })); it('should not fire whenstable callbacks synchronously if event is already finished', () => { - ngZone.start(); - ngZone.finish(); + ngZone.unstable(); + ngZone.stable(); testability.whenStable(execute); expect(execute).not.toHaveBeenCalled(); @@ -167,12 +171,12 @@ export function main() { it('should fire whenstable callback when event finishes', inject([AsyncTestCompleter], (async) => { - ngZone.start(); + ngZone.unstable(); testability.whenStable(execute); microTask(() => { expect(execute).not.toHaveBeenCalled(); - ngZone.finish(); + ngZone.stable(); microTask(() => { expect(execute).toHaveBeenCalled(); @@ -182,16 +186,16 @@ export function main() { })); it('should not fire whenstable callbacks synchronously when event finishes', () => { - ngZone.start(); + ngZone.unstable(); testability.whenStable(execute); - ngZone.finish(); + ngZone.stable(); expect(execute).not.toHaveBeenCalled(); }); it('should not fire whenstable callback when event did not finish', inject([AsyncTestCompleter], (async) => { - ngZone.start(); + ngZone.unstable(); testability.increasePendingRequestCount(); testability.whenStable(execute); @@ -201,7 +205,7 @@ export function main() { microTask(() => { expect(execute).not.toHaveBeenCalled(); - ngZone.finish(); + ngZone.stable(); microTask(() => { expect(execute).toHaveBeenCalled(); @@ -213,14 +217,14 @@ export function main() { it('should not fire whenstable callback when there are pending counts', inject([AsyncTestCompleter], (async) => { - ngZone.start(); + ngZone.unstable(); testability.increasePendingRequestCount(); testability.increasePendingRequestCount(); testability.whenStable(execute); microTask(() => { expect(execute).not.toHaveBeenCalled(); - ngZone.finish(); + ngZone.stable(); microTask(() => { expect(execute).not.toHaveBeenCalled(); @@ -241,8 +245,8 @@ export function main() { it('should fire whenstable callback with didWork if event is already finished', inject([AsyncTestCompleter], (async) => { - ngZone.start(); - ngZone.finish(); + ngZone.unstable(); + ngZone.stable(); testability.whenStable(execute); microTask(() => { @@ -258,11 +262,11 @@ export function main() { it('should fire whenstable callback with didwork when event finishes', inject([AsyncTestCompleter], (async) => { - ngZone.start(); + ngZone.unstable(); testability.whenStable(execute); microTask(() => { - ngZone.finish(); + ngZone.stable(); microTask(() => { expect(execute).toHaveBeenCalledWith(true); diff --git a/modules/angular2/test/core/zone/ng_zone_DEPRECATED_spec.ts b/modules/angular2/test/core/zone/ng_zone_DEPRECATED_spec.ts deleted file mode 100644 index 358b34aa0a..0000000000 --- a/modules/angular2/test/core/zone/ng_zone_DEPRECATED_spec.ts +++ /dev/null @@ -1,655 +0,0 @@ -// TODO(yjbanov): this file tests the deprecated NgZone API. Delete it when -// the old API is cleaned up. -import { - AsyncTestCompleter, - beforeEach, - ddescribe, - describe, - expect, - iit, - inject, - it, - xdescribe, - xit, - Log, - isInInnerZone, - browserDetection -} from 'angular2/testing_internal'; - -import {PromiseCompleter, PromiseWrapper, TimerWrapper} from 'angular2/src/facade/async'; -import {BaseException} from 'angular2/src/facade/exceptions'; - -import {NgZone} from 'angular2/src/core/zone/ng_zone'; - -var needsLongerTimers = browserDetection.isSlow || browserDetection.isEdge; -var resultTimer = 1000; -var testTimeout = browserDetection.isEdge ? 1200 : 100; -// Schedules a macrotask (using a timer) -function macroTask(fn: (...args: any[]) => void, timer = 1): void { - // adds longer timers for passing tests in IE and Edge - _zone.runOutsideAngular(() => TimerWrapper.setTimeout(fn, needsLongerTimers ? timer : 1)); -} - -// Schedules a microtasks (using a resolved promise .then()) -function microTask(fn: Function): void { - PromiseWrapper.resolve(null).then((_) => { fn(); }); -} - -var _log; -var _errors: any[]; -var _traces: any[]; -var _zone; - -function logError(error, stackTrace) { - _errors.push(error); - _traces.push(stackTrace); -} - -export function main() { - describe("NgZone", () => { - - function createZone(enableLongStackTrace) { - var zone = new NgZone({enableLongStackTrace: enableLongStackTrace}); - zone.overrideOnTurnStart(_log.fn('onTurnStart')); - zone.overrideOnTurnDone(_log.fn('onTurnDone')); - return zone; - } - - beforeEach(() => { - _log = new Log(); - _errors = []; - _traces = []; - }); - - describe('long stack trace', () => { - beforeEach(() => { _zone = createZone(true); }); - - commonTests(); - - it('should produce long stack traces', inject([AsyncTestCompleter], (async) => { - macroTask(() => { - _zone.overrideOnErrorHandler(logError); - var c: PromiseCompleter = PromiseWrapper.completer(); - - _zone.run(() => { - TimerWrapper.setTimeout(() => { - TimerWrapper.setTimeout(() => { - c.resolve(null); - throw new BaseException('ccc'); - }, 0); - }, 0); - }); - - c.promise.then((_) => { - expect(_traces.length).toBe(1); - expect(_traces[0].length).toBeGreaterThan(1); - async.done(); - }); - }); - }), testTimeout); - - it('should produce long stack traces (when using microtasks)', - inject([AsyncTestCompleter], (async) => { - macroTask(() => { - _zone.overrideOnErrorHandler(logError); - var c: PromiseCompleter = PromiseWrapper.completer(); - - _zone.run(() => { - microTask(() => { - microTask(() => { - c.resolve(null); - throw new BaseException("ddd"); - }); - }); - }); - - c.promise.then((_) => { - expect(_traces.length).toBe(1); - expect(_traces[0].length).toBeGreaterThan(1); - async.done(); - }); - }); - }), testTimeout); - }); - - describe('short stack trace', () => { - beforeEach(() => { _zone = createZone(false); }); - - commonTests(); - - it('should disable long stack traces', inject([AsyncTestCompleter], (async) => { - macroTask(() => { - _zone.overrideOnErrorHandler(logError); - var c: PromiseCompleter = PromiseWrapper.completer(); - - _zone.run(() => { - TimerWrapper.setTimeout(() => { - TimerWrapper.setTimeout(() => { - c.resolve(null); - throw new BaseException('ccc'); - }, 0); - }, 0); - }); - - c.promise.then((_) => { - expect(_traces.length).toBe(1); - expect(_traces[0].length).toEqual(1); - async.done(); - }); - }); - }), testTimeout); - }); - }); -} - -function commonTests() { - describe('isInInnerZone', - () => {it('should return whether the code executes in the inner zone', () => { - expect(isInInnerZone()).toEqual(false); - _zone.run(() => { expect(isInInnerZone()).toEqual(true); }); - }, testTimeout)}); - - describe('run', () => { - it('should return the body return value from run', inject([AsyncTestCompleter], (async) => { - macroTask(() => { expect(_zone.run(() => { return 6; })).toEqual(6); }); - - macroTask(() => { async.done(); }); - }), testTimeout); - - it('should call onTurnStart and onTurnDone', inject([AsyncTestCompleter], (async) => { - macroTask(() => { _zone.run(_log.fn('run')); }); - - macroTask(() => { - expect(_log.result()).toEqual('onTurnStart; run; onTurnDone'); - async.done(); - }); - }), testTimeout); - - it('should call onEventDone once at the end of event', inject([AsyncTestCompleter], (async) => { - // The test is set up in a way that causes the zone loop to run onTurnDone twice - // then verified that onEventDone is only called once at the end - _zone.overrideOnTurnStart(null); - _zone.overrideOnEventDone(() => { _log.add('onEventDone'); }); - - var times = 0; - _zone.overrideOnTurnDone(() => { - times++; - _log.add(`onTurnDone ${times}`); - if (times < 2) { - // Scheduling a microtask causes a second digest - microTask(() => {}); - } - }); - - macroTask(() => { _zone.run(_log.fn('run')); }); - - macroTask(() => { - expect(_log.result()).toEqual('run; onTurnDone 1; onTurnDone 2; onEventDone'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should call standalone onEventDone', inject([AsyncTestCompleter], (async) => { - _zone.overrideOnTurnStart(null); - _zone.overrideOnEventDone(() => { _log.add('onEventDone'); }); - - _zone.overrideOnTurnDone(null); - - macroTask(() => { _zone.run(_log.fn('run')); }); - - macroTask(() => { - expect(_log.result()).toEqual('run; onEventDone'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should not allow onEventDone to cause further digests', - inject([AsyncTestCompleter], (async) => { - _zone.overrideOnTurnStart(null); - - var eventDone = false; - _zone.overrideOnEventDone(() => { - if (eventDone) throw 'Should not call this more than once'; - _log.add('onEventDone'); - // If not implemented correctly, this microtask will cause another digest, - // which is not what we want. - microTask(() => {}); - eventDone = true; - }); - - _zone.overrideOnTurnDone(() => { _log.add('onTurnDone'); }); - - macroTask(() => { _zone.run(_log.fn('run')); }); - - macroTask(() => { - expect(_log.result()).toEqual('run; onTurnDone; onEventDone'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should run async tasks scheduled inside onEventDone outside Angular zone', - inject([AsyncTestCompleter], (async) => { - _zone.overrideOnTurnStart(null); - - _zone.overrideOnEventDone(() => { - _log.add('onEventDone'); - // If not implemented correctly, this time will cause another digest, - // which is not what we want. - TimerWrapper.setTimeout(() => { _log.add('asyncTask'); }, 5); - }); - - _zone.overrideOnTurnDone(() => { _log.add('onTurnDone'); }); - - macroTask(() => { _zone.run(_log.fn('run')); }); - - macroTask(() => { - TimerWrapper.setTimeout(() => { - expect(_log.result()).toEqual('run; onTurnDone; onEventDone; asyncTask'); - async.done(); - }, 50); - }); - }), testTimeout); - - it('should call onTurnStart once before a turn and onTurnDone once after the turn', - inject([AsyncTestCompleter], (async) => { - - macroTask(() => { - _zone.run(() => { - _log.add('run start'); - microTask(_log.fn('async')); - _log.add('run end'); - }); - }); - - macroTask(() => { - // The microtask (async) is executed after the macrotask (run) - expect(_log.result()).toEqual('onTurnStart; run start; run end; async; onTurnDone'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should not run onTurnStart and onTurnDone for nested Zone.run', - inject([AsyncTestCompleter], (async) => { - macroTask(() => { - _zone.run(() => { - _log.add('start run'); - _zone.run(() => { - _log.add('nested run'); - microTask(_log.fn('nested run microtask')); - }); - _log.add('end run'); - }); - }); - - macroTask(() => { - expect(_log.result()) - .toEqual( - 'onTurnStart; start run; nested run; end run; nested run microtask; onTurnDone'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should not run onTurnStart and onTurnDone for nested Zone.run invoked from onTurnDone', - inject([AsyncTestCompleter], (async) => { - _zone.overrideOnTurnStart(null); - _zone.overrideOnTurnDone(() => { - _log.add('onTurnDone:started'); - _zone.run(() => _log.add('nested run')); - _log.add('onTurnDone:finished'); - }); - - macroTask(() => { _zone.run(() => { _log.add('start run'); }); }); - - macroTask(() => { - expect(_log.result()) - .toEqual('start run; onTurnDone:started; nested run; onTurnDone:finished'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should call onTurnStart and onTurnDone before and after each top-level run', - inject([AsyncTestCompleter], (async) => { - macroTask(() => { _zone.run(_log.fn('run1')); }); - - macroTask(() => { _zone.run(_log.fn('run2')); }); - - macroTask(() => { - expect(_log.result()) - .toEqual('onTurnStart; run1; onTurnDone; onTurnStart; run2; onTurnDone'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should call onTurnStart and onTurnDone before and after each turn', - inject([AsyncTestCompleter], (async) => { - var a: PromiseCompleter; - var b: PromiseCompleter; - - macroTask(() => { - _zone.run(() => { - a = PromiseWrapper.completer(); - b = PromiseWrapper.completer(); - - _log.add('run start'); - a.promise.then(_log.fn('a then')); - b.promise.then(_log.fn('b then')); - }); - }); - - macroTask(() => { - _zone.run(() => { - a.resolve('a'); - b.resolve('b'); - }); - }); - - macroTask(() => { - expect(_log.result()) - .toEqual( - 'onTurnStart; run start; onTurnDone; onTurnStart; a then; b then; onTurnDone'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should run a function outside of the angular zone', - inject([AsyncTestCompleter], (async) => { - macroTask(() => { _zone.runOutsideAngular(_log.fn('run')); }); - - macroTask(() => { - expect(_log.result()).toEqual('run'); - async.done() - }); - }), testTimeout); - - it('should call onTurnStart and onTurnDone when an inner microtask is scheduled from outside angular', - inject([AsyncTestCompleter], (async) => { - var completer: PromiseCompleter; - - macroTask( - () => { _zone.runOutsideAngular(() => { completer = PromiseWrapper.completer(); }); }); - - macroTask( - () => { _zone.run(() => { completer.promise.then(_log.fn('executedMicrotask')); }); }); - - macroTask(() => { - _zone.runOutsideAngular(() => { - _log.add('scheduling a microtask'); - completer.resolve(null); - }); - }); - - macroTask(() => { - expect(_log.result()) - .toEqual( - // First VM turn => setup Promise then - 'onTurnStart; onTurnDone; ' + - // Second VM turn (outside of anguler) - 'scheduling a microtask; ' + - // Third VM Turn => execute the microtask (inside angular) - 'onTurnStart; executedMicrotask; onTurnDone'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should call onTurnStart before executing a microtask scheduled in onTurnDone as well as ' + - 'onTurnDone after executing the task', - inject([AsyncTestCompleter], (async) => { - var ran = false; - _zone.overrideOnTurnStart(_log.fn('onTurnStart')); - _zone.overrideOnTurnDone(() => { - _log.add('onTurnDone(begin)'); - if (!ran) { - microTask(() => { - ran = true; - _log.add('executedMicrotask'); - }); - } - - _log.add('onTurnDone(end)'); - }); - - macroTask(() => { _zone.run(_log.fn('run')); }); - - macroTask(() => { - expect(_log.result()) - .toEqual( - // First VM turn => 'run' macrotask - 'onTurnStart; run; onTurnDone(begin); onTurnDone(end); ' + - // Second VM Turn => microtask enqueued from onTurnDone - 'onTurnStart; executedMicrotask; onTurnDone(begin); onTurnDone(end)'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should call onTurnStart and onTurnDone for a scheduleMicrotask in onTurnDone triggered by ' + - 'a scheduleMicrotask in run', - inject([AsyncTestCompleter], (async) => { - var ran = false; - _zone.overrideOnTurnStart(_log.fn('onTurnStart')); - _zone.overrideOnTurnDone(() => { - _log.add('onTurnDone(begin)'); - if (!ran) { - _log.add('onTurnDone(scheduleMicrotask)'); - microTask(() => { - ran = true; - _log.add('onTurnDone(executeMicrotask)'); - }); - } - _log.add('onTurnDone(end)'); - }); - - macroTask(() => { - _zone.run(() => { - _log.add('scheduleMicrotask'); - microTask(_log.fn('run(executeMicrotask)')); - }); - }); - - macroTask(() => { - expect(_log.result()) - .toEqual( - // First VM Turn => a macrotask + the microtask it enqueues - 'onTurnStart; scheduleMicrotask; run(executeMicrotask); onTurnDone(begin); onTurnDone(scheduleMicrotask); onTurnDone(end); ' + - // Second VM Turn => the microtask enqueued from onTurnDone - 'onTurnStart; onTurnDone(executeMicrotask); onTurnDone(begin); onTurnDone(end)'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should execute promises scheduled in onTurnStart before promises scheduled in run', - inject([AsyncTestCompleter], (async) => { - var donePromiseRan = false; - var startPromiseRan = false; - - _zone.overrideOnTurnStart(() => { - _log.add('onTurnStart(begin)'); - if (!startPromiseRan) { - _log.add('onTurnStart(schedulePromise)'); - microTask(_log.fn('onTurnStart(executePromise)')); - startPromiseRan = true; - } - _log.add('onTurnStart(end)'); - }); - _zone.overrideOnTurnDone(() => { - _log.add('onTurnDone(begin)'); - if (!donePromiseRan) { - _log.add('onTurnDone(schedulePromise)'); - microTask(_log.fn('onTurnDone(executePromise)')); - donePromiseRan = true; - } - _log.add('onTurnDone(end)'); - }); - - macroTask(() => { - _zone.run(() => { - _log.add('run start'); - PromiseWrapper.resolve(null) - .then((_) => { - _log.add('promise then'); - PromiseWrapper.resolve(null).then(_log.fn('promise foo')); - return PromiseWrapper.resolve(null); - }) - .then(_log.fn('promise bar')); - _log.add('run end'); - }); - }); - - macroTask(() => { - expect(_log.result()) - .toEqual( - // First VM turn: enqueue a microtask in onTurnStart - 'onTurnStart(begin); onTurnStart(schedulePromise); onTurnStart(end); ' + - // First VM turn: execute the macrotask which enqueues microtasks - 'run start; run end; ' + - // First VM turn: execute enqueued microtasks - 'onTurnStart(executePromise); promise then; promise foo; promise bar; ' + - // First VM turn: onTurnEnd, enqueue a microtask - 'onTurnDone(begin); onTurnDone(schedulePromise); onTurnDone(end); ' + - // Second VM turn: execute the microtask from onTurnEnd - 'onTurnStart(begin); onTurnStart(end); onTurnDone(executePromise); onTurnDone(begin); onTurnDone(end)'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should call onTurnStart and onTurnDone before and after each turn, respectively', - inject([AsyncTestCompleter], (async) => { - var completerA: PromiseCompleter; - var completerB: PromiseCompleter; - - macroTask(() => { - _zone.run(() => { - completerA = PromiseWrapper.completer(); - completerB = PromiseWrapper.completer(); - completerA.promise.then(_log.fn('a then')); - completerB.promise.then(_log.fn('b then')); - _log.add('run start'); - }); - }); - - macroTask(() => { _zone.run(() => { completerA.resolve(null); }); }, 20); - - - macroTask(() => { _zone.run(() => { completerB.resolve(null); }); }, 500); - - macroTask(() => { - expect(_log.result()) - .toEqual( - // First VM turn - 'onTurnStart; run start; onTurnDone; ' + - // Second VM turn - 'onTurnStart; a then; onTurnDone; ' + - // Third VM turn - 'onTurnStart; b then; onTurnDone'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should call onTurnStart and onTurnDone before and after (respectively) all turns in a chain', - inject([AsyncTestCompleter], (async) => { - macroTask(() => { - _zone.run(() => { - _log.add('run start'); - microTask(() => { - _log.add('async1'); - microTask(_log.fn('async2')); - }); - _log.add('run end'); - }); - }); - - macroTask(() => { - expect(_log.result()) - .toEqual('onTurnStart; run start; run end; async1; async2; onTurnDone'); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should call onTurnStart and onTurnDone for promises created outside of run body', - inject([AsyncTestCompleter], (async) => { - var promise: Promise; - - macroTask(() => { - _zone.runOutsideAngular(() => { - promise = PromiseWrapper.resolve(4).then((x) => PromiseWrapper.resolve(x)); - }); - - _zone.run(() => { - promise.then(_log.fn('promise then')); - _log.add('zone run'); - }); - }); - - macroTask(() => { - expect(_log.result()) - .toEqual('onTurnStart; zone run; onTurnDone; onTurnStart; promise then; onTurnDone'); - async.done(); - }, resultTimer); - }), testTimeout); - }); - - describe('exceptions', () => { - it('should call the on error callback when it is defined', - inject([AsyncTestCompleter], (async) => { - macroTask(() => { - _zone.overrideOnErrorHandler(logError); - - var exception = new BaseException('sync'); - - _zone.run(() => { throw exception; }); - - expect(_errors.length).toBe(1); - expect(_errors[0]).toBe(exception); - async.done(); - }); - }), testTimeout); - - it('should call onError for errors from microtasks', inject([AsyncTestCompleter], (async) => { - _zone.overrideOnErrorHandler(logError); - - var exception = new BaseException('async'); - - macroTask(() => { _zone.run(() => { microTask(() => { throw exception; }); }); }); - - macroTask(() => { - expect(_errors.length).toBe(1); - expect(_errors[0]).toEqual(exception); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should call onError when onTurnDone throws and the zone is sync', - inject([AsyncTestCompleter], (async) => { - var exception = new BaseException('fromOnTurnDone'); - - _zone.overrideOnErrorHandler(logError); - _zone.overrideOnTurnDone(() => { throw exception; }); - - macroTask(() => { _zone.run(() => {}); }); - - macroTask(() => { - expect(_errors.length).toBe(1); - expect(_errors[0]).toEqual(exception); - async.done(); - }, resultTimer); - }), testTimeout); - - it('should call onError when onTurnDone throws and the zone is async', - inject([AsyncTestCompleter], (async) => { - var asyncRan = false; - - var exception = new BaseException('fromOnTurnDone'); - - _zone.overrideOnErrorHandler(logError); - _zone.overrideOnTurnDone(() => { throw exception; }); - - macroTask(() => { _zone.run(() => { microTask(() => { asyncRan = true; }); }); }); - - macroTask(() => { - expect(asyncRan).toBe(true); - expect(_errors.length).toBe(1); - expect(_errors[0]).toEqual(exception); - async.done(); - }, resultTimer); - }), testTimeout); - }); -} diff --git a/modules/angular2/test/core/zone/ng_zone_spec.ts b/modules/angular2/test/core/zone/ng_zone_spec.ts index 1c011cc37d..56829bdb02 100644 --- a/modules/angular2/test/core/zone/ng_zone_spec.ts +++ b/modules/angular2/test/core/zone/ng_zone_spec.ts @@ -10,7 +10,6 @@ import { xdescribe, xit, Log, - isInInnerZone, browserDetection } from 'angular2/testing_internal'; @@ -18,31 +17,26 @@ import { PromiseCompleter, PromiseWrapper, TimerWrapper, - ObservableWrapper, - EventEmitter + ObservableWrapper } from 'angular2/src/facade/async'; import {BaseException} from 'angular2/src/facade/exceptions'; +import {IS_DART, scheduleMicroTask, isPresent} from 'angular2/src/facade/lang'; import {NgZone, NgZoneError} from 'angular2/src/core/zone/ng_zone'; var needsLongerTimers = browserDetection.isSlow || browserDetection.isEdge; var resultTimer = 1000; -var testTimeout = browserDetection.isEdge ? 1200 : 100; +var testTimeout = browserDetection.isEdge ? 1200 : 500; // Schedules a macrotask (using a timer) function macroTask(fn: (...args: any[]) => void, timer = 1): void { // adds longer timers for passing tests in IE and Edge - _zone.runOutsideAngular(() => TimerWrapper.setTimeout(fn, needsLongerTimers ? timer : 1)); + TimerWrapper.setTimeout(fn, needsLongerTimers ? timer : 1); } -// Schedules a microtasks (using a resolved promise .then()) -function microTask(fn: Function): void { - PromiseWrapper.resolve(null).then((_) => { fn(); }); -} - -var _log; +var _log: Log; var _errors: any[]; var _traces: any[]; -var _zone; +var _zone: NgZone; function logOnError() { ObservableWrapper.subscribe(_zone.onError, (ngErr: NgZoneError) => { @@ -51,16 +45,26 @@ function logOnError() { }); } -function logOnTurnStart() { - ObservableWrapper.subscribe(_zone.onTurnStart, _log.fn('onTurnStart')); +function logOnUnstable() { + ObservableWrapper.subscribe(_zone.onUnstable, _log.fn('onUnstable')); } -function logOnTurnDone() { - ObservableWrapper.subscribe(_zone.onTurnDone, _log.fn('onTurnDone')); +function logOnMicrotaskEmpty() { + ObservableWrapper.subscribe(_zone.onMicrotaskEmpty, _log.fn('onMicrotaskEmpty')); } -function logOnEventDone() { - ObservableWrapper.subscribe(_zone.onEventDone, _log.fn('onEventDone')); +function logOnStable() { + ObservableWrapper.subscribe(_zone.onStable, _log.fn('onStable')); +} + +function runNgZoneNoLog(fn: () => any) { + var length = _log.logItems.length; + try { + return _zone.run(fn); + } finally { + // delete anything which may have gotten logged. + _log.logItems.length = length; + } } export function main() { @@ -77,13 +81,18 @@ export function main() { }); describe('long stack trace', () => { - beforeEach(() => { _zone = createZone(true); }); + beforeEach(() => { + _zone = createZone(true); + logOnUnstable(); + logOnMicrotaskEmpty(); + logOnStable(); + logOnError(); + }); commonTests(); it('should produce long stack traces', inject([AsyncTestCompleter], (async) => { macroTask(() => { - logOnError(); var c: PromiseCompleter = PromiseWrapper.completer(); _zone.run(() => { @@ -106,12 +115,11 @@ export function main() { it('should produce long stack traces (when using microtasks)', inject([AsyncTestCompleter], (async) => { macroTask(() => { - logOnError(); var c: PromiseCompleter = PromiseWrapper.completer(); _zone.run(() => { - microTask(() => { - microTask(() => { + scheduleMicroTask(() => { + scheduleMicroTask(() => { c.resolve(null); throw new BaseException("ddd"); }); @@ -128,13 +136,18 @@ export function main() { }); describe('short stack trace', () => { - beforeEach(() => { _zone = createZone(false); }); + beforeEach(() => { + _zone = createZone(false); + logOnUnstable(); + logOnMicrotaskEmpty(); + logOnStable(); + logOnError(); + }); commonTests(); it('should disable long stack traces', inject([AsyncTestCompleter], (async) => { macroTask(() => { - logOnError(); var c: PromiseCompleter = PromiseWrapper.completer(); _zone.run(() => { @@ -148,7 +161,10 @@ export function main() { c.promise.then((_) => { expect(_traces.length).toBe(1); - expect(_traces[0].length).toEqual(1); + if (isPresent(_traces[0])) { + // some browsers don't have stack traces. + expect(_traces[0].indexOf('---')).toEqual(-1); + } async.done(); }); }); @@ -162,38 +178,38 @@ function commonTests() { it('should be false', () => { expect(_zone.hasPendingMicrotasks).toBe(false); }); it('should be true', () => { - _zone.run(() => { microTask(() => {}); }); + runNgZoneNoLog(() => { scheduleMicroTask(() => {}); }); expect(_zone.hasPendingMicrotasks).toBe(true); }); }); describe('hasPendingTimers', () => { - it('should be false', () => { expect(_zone.hasPendingTimers).toBe(false); }); + it('should be false', () => { expect(_zone.hasPendingMacrotasks).toBe(false); }); it('should be true', () => { - _zone.run(() => { TimerWrapper.setTimeout(() => {}, 0); }); - expect(_zone.hasPendingTimers).toBe(true); + runNgZoneNoLog(() => { TimerWrapper.setTimeout(() => {}, 0); }); + expect(_zone.hasPendingMacrotasks).toBe(true); }); }); describe('hasPendingAsyncTasks', () => { - it('should be false', () => { expect(_zone.hasPendingAsyncTasks).toBe(false); }); + it('should be false', () => { expect(_zone.hasPendingMicrotasks).toBe(false); }); it('should be true when microtask is scheduled', () => { - _zone.run(() => { microTask(() => {}); }); - expect(_zone.hasPendingAsyncTasks).toBe(true); + runNgZoneNoLog(() => { scheduleMicroTask(() => {}); }); + expect(_zone.hasPendingMicrotasks).toBe(true); }); it('should be true when timer is scheduled', () => { - _zone.run(() => { TimerWrapper.setTimeout(() => {}, 0); }); - expect(_zone.hasPendingAsyncTasks).toBe(true); + runNgZoneNoLog(() => { TimerWrapper.setTimeout(() => {}, 0); }); + expect(_zone.hasPendingMacrotasks).toBe(true); }); }); describe('isInInnerZone', () => { it('should return whether the code executes in the inner zone', () => { - expect(isInInnerZone()).toEqual(false); - _zone.run(() => { expect(isInInnerZone()).toEqual(true); }); + expect(NgZone.isInAngularZone()).toEqual(false); + runNgZoneNoLog(() => { expect(NgZone.isInAngularZone()).toEqual(true); }); }, testTimeout); }); @@ -204,47 +220,43 @@ function commonTests() { macroTask(() => { async.done(); }); }), testTimeout); - it('should call onTurnStart and onTurnDone', inject([AsyncTestCompleter], (async) => { - logOnTurnStart(); - logOnTurnDone(); - macroTask(() => { _zone.run(_log.fn('run')); }); - + it('should call onUnstable and onMicrotaskEmpty', inject([AsyncTestCompleter], (async) => { + runNgZoneNoLog(() => macroTask(_log.fn('run'))); macroTask(() => { - expect(_log.result()).toEqual('onTurnStart; run; onTurnDone'); + expect(_log.result()).toEqual('onUnstable; run; onMicrotaskEmpty; onStable'); async.done(); }); }), testTimeout); - it('should call onEventDone once at the end of event', inject([AsyncTestCompleter], (async) => { - // The test is set up in a way that causes the zone loop to run onTurnDone twice - // then verified that onEventDone is only called once at the end - logOnEventDone(); + it('should call onStable once at the end of event', inject([AsyncTestCompleter], (async) => { + // The test is set up in a way that causes the zone loop to run onMicrotaskEmpty twice + // then verified that onStable is only called once at the end + + runNgZoneNoLog(() => macroTask(_log.fn('run'))); var times = 0; - ObservableWrapper.subscribe(_zone.onTurnDone, (_) => { + ObservableWrapper.subscribe(_zone.onMicrotaskEmpty, (_) => { times++; - _log.add(`onTurnDone ${times}`); + _log.add(`onMicrotaskEmpty ${times}`); if (times < 2) { // Scheduling a microtask causes a second digest - _zone.run(() => { microTask(() => {}); }); + runNgZoneNoLog(() => { scheduleMicroTask(() => {}); }); } }); - macroTask(() => { _zone.run(_log.fn('run')); }); - macroTask(() => { - expect(_log.result()).toEqual('run; onTurnDone 1; onTurnDone 2; onEventDone'); + expect(_log.result()) + .toEqual('onUnstable; run; onMicrotaskEmpty; onMicrotaskEmpty 1; ' + + 'onMicrotaskEmpty; onMicrotaskEmpty 2; onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should call standalone onEventDone', inject([AsyncTestCompleter], (async) => { - logOnEventDone(); - - macroTask(() => { _zone.run(_log.fn('run')); }); + it('should call standalone onStable', inject([AsyncTestCompleter], (async) => { + runNgZoneNoLog(() => macroTask(_log.fn('run'))); macroTask(() => { - expect(_log.result()).toEqual('run; onEventDone'); + expect(_log.result()).toEqual('onUnstable; run; onMicrotaskEmpty; onStable'); async.done(); }, resultTimer); }), testTimeout); @@ -255,108 +267,101 @@ function commonTests() { // then verifies that those microtasks do not cause additional digests. var turnStart = false; - ObservableWrapper.subscribe(_zone.onTurnStart, (_) => { + ObservableWrapper.subscribe(_zone.onUnstable, (_) => { if (turnStart) throw 'Should not call this more than once'; - _log.add('onTurnStart'); - microTask(() => {}); + _log.add('onUnstable'); + scheduleMicroTask(() => {}); turnStart = true; }); var turnDone = false; - ObservableWrapper.subscribe(_zone.onTurnDone, (_) => { + ObservableWrapper.subscribe(_zone.onMicrotaskEmpty, (_) => { if (turnDone) throw 'Should not call this more than once'; - _log.add('onTurnDone'); - microTask(() => {}); + _log.add('onMicrotaskEmpty'); + scheduleMicroTask(() => {}); turnDone = true; }); var eventDone = false; - ObservableWrapper.subscribe(_zone.onEventDone, (_) => { + ObservableWrapper.subscribe(_zone.onStable, (_) => { if (eventDone) throw 'Should not call this more than once'; - _log.add('onEventDone'); - microTask(() => {}); + _log.add('onStable'); + scheduleMicroTask(() => {}); eventDone = true; }); macroTask(() => { _zone.run(_log.fn('run')); }); macroTask(() => { - expect(_log.result()).toEqual('onTurnStart; run; onTurnDone; onEventDone'); + expect(_log.result()).toEqual('onUnstable; run; onMicrotaskEmpty; onStable'); async.done(); }, resultTimer); }), testTimeout); it('should run subscriber listeners in the subscription zone (inside)', inject([AsyncTestCompleter], (async) => { + runNgZoneNoLog(() => macroTask(_log.fn('run'))); + // the only practical use-case to run a callback inside the zone is - // change detection after "onTurnDone". That's the only case tested. + // change detection after "onMicrotaskEmpty". That's the only case tested. var turnDone = false; - ObservableWrapper.subscribe(_zone.onTurnDone, (_) => { - _log.add('onTurnDone'); + ObservableWrapper.subscribe(_zone.onMicrotaskEmpty, (_) => { + _log.add('onMyMicrotaskEmpty'); if (turnDone) return; - _zone.run(() => { microTask(() => {}); }); + _zone.run(() => { scheduleMicroTask(() => {}); }); turnDone = true; }); - macroTask(() => { _zone.run(_log.fn('run')); }); - macroTask(() => { - expect(_log.result()).toEqual('run; onTurnDone; onTurnDone'); + expect(_log.result()) + .toEqual('onUnstable; run; onMicrotaskEmpty; onMyMicrotaskEmpty; ' + + 'onMicrotaskEmpty; onMyMicrotaskEmpty; onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should run async tasks scheduled inside onEventDone outside Angular zone', + it('should run async tasks scheduled inside onStable outside Angular zone', inject([AsyncTestCompleter], (async) => { - ObservableWrapper.subscribe(_zone.onEventDone, (_) => { - _log.add('onEventDone'); - // If not implemented correctly, this time will cause another digest, - // which is not what we want. - TimerWrapper.setTimeout(() => { _log.add('asyncTask'); }, 5); + runNgZoneNoLog(() => macroTask(_log.fn('run'))); + + ObservableWrapper.subscribe(_zone.onStable, (_) => { + NgZone.assertNotInAngularZone(); + _log.add('onMyTaskDone'); }); - logOnTurnDone(); - - macroTask(() => { _zone.run(_log.fn('run')); }); - macroTask(() => { - TimerWrapper.setTimeout(() => { - expect(_log.result()).toEqual('run; onTurnDone; onEventDone; asyncTask'); - async.done(); - }, 50); + expect(_log.result()) + .toEqual('onUnstable; run; onMicrotaskEmpty; onStable; onMyTaskDone'); + async.done(); }); }), testTimeout); - it('should call onTurnStart once before a turn and onTurnDone once after the turn', + it('should call onUnstable once before a turn and onMicrotaskEmpty once after the turn', inject([AsyncTestCompleter], (async) => { - logOnTurnStart(); - logOnTurnDone(); - - macroTask(() => { - _zone.run(() => { + runNgZoneNoLog(() => { + macroTask(() => { _log.add('run start'); - microTask(_log.fn('async')); + scheduleMicroTask(_log.fn('async')); _log.add('run end'); }); }); macroTask(() => { // The microtask (async) is executed after the macrotask (run) - expect(_log.result()).toEqual('onTurnStart; run start; run end; async; onTurnDone'); + expect(_log.result()) + .toEqual('onUnstable; run start; run end; async; onMicrotaskEmpty; onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should not run onTurnStart and onTurnDone for nested Zone.run', + it('should not run onUnstable and onMicrotaskEmpty for nested Zone.run', inject([AsyncTestCompleter], (async) => { - logOnTurnStart(); - logOnTurnDone(); - macroTask(() => { - _zone.run(() => { + runNgZoneNoLog(() => { + macroTask(() => { _log.add('start run'); _zone.run(() => { _log.add('nested run'); - microTask(_log.fn('nested run microtask')); + scheduleMicroTask(_log.fn('nested run microtask')); }); _log.add('end run'); }); @@ -365,54 +370,49 @@ function commonTests() { macroTask(() => { expect(_log.result()) .toEqual( - 'onTurnStart; start run; nested run; end run; nested run microtask; onTurnDone'); + 'onUnstable; start run; nested run; end run; nested run microtask; onMicrotaskEmpty; onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should not run onTurnStart and onTurnDone for nested Zone.run invoked from onTurnDone', + it('should not run onUnstable and onMicrotaskEmpty for nested Zone.run invoked from onMicrotaskEmpty', inject([AsyncTestCompleter], (async) => { - ObservableWrapper.subscribe(_zone.onTurnDone, (_) => { - _log.add('onTurnDone:started'); + runNgZoneNoLog(() => macroTask(_log.fn('start run'))); + + ObservableWrapper.subscribe(_zone.onMicrotaskEmpty, (_) => { + _log.add('onMicrotaskEmpty:started'); _zone.run(() => _log.add('nested run')); - _log.add('onTurnDone:finished'); + _log.add('onMicrotaskEmpty:finished'); }); - macroTask(() => { _zone.run(() => { _log.add('start run'); }); }); - macroTask(() => { expect(_log.result()) - .toEqual('start run; onTurnDone:started; nested run; onTurnDone:finished'); + .toEqual( + 'onUnstable; start run; onMicrotaskEmpty; onMicrotaskEmpty:started; nested run; onMicrotaskEmpty:finished; onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should call onTurnStart and onTurnDone before and after each top-level run', + it('should call onUnstable and onMicrotaskEmpty before and after each top-level run', inject([AsyncTestCompleter], (async) => { - logOnTurnStart(); - logOnTurnDone(); - - macroTask(() => { _zone.run(_log.fn('run1')); }); - - macroTask(() => { _zone.run(_log.fn('run2')); }); + runNgZoneNoLog(() => macroTask(_log.fn('run1'))); + runNgZoneNoLog(() => macroTask(_log.fn('run2'))); macroTask(() => { expect(_log.result()) - .toEqual('onTurnStart; run1; onTurnDone; onTurnStart; run2; onTurnDone'); + .toEqual( + 'onUnstable; run1; onMicrotaskEmpty; onStable; onUnstable; run2; onMicrotaskEmpty; onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should call onTurnStart and onTurnDone before and after each turn', + it('should call onUnstable and onMicrotaskEmpty before and after each turn', inject([AsyncTestCompleter], (async) => { - logOnTurnStart(); - logOnTurnDone(); - var a: PromiseCompleter; var b: PromiseCompleter; - macroTask(() => { - _zone.run(() => { + runNgZoneNoLog(() => { + macroTask(() => { a = PromiseWrapper.completer(); b = PromiseWrapper.completer(); @@ -422,8 +422,8 @@ function commonTests() { }); }); - macroTask(() => { - _zone.run(() => { + runNgZoneNoLog(() => { + macroTask(() => { a.resolve('a'); b.resolve('b'); }); @@ -432,16 +432,13 @@ function commonTests() { macroTask(() => { expect(_log.result()) .toEqual( - 'onTurnStart; run start; onTurnDone; onTurnStart; a then; b then; onTurnDone'); + 'onUnstable; run start; onMicrotaskEmpty; onStable; onUnstable; a then; b then; onMicrotaskEmpty; onStable'); async.done(); }, resultTimer); }), testTimeout); it('should run a function outside of the angular zone', inject([AsyncTestCompleter], (async) => { - logOnTurnStart(); - logOnTurnDone(); - macroTask(() => { _zone.runOutsideAngular(_log.fn('run')); }); macroTask(() => { @@ -450,138 +447,114 @@ function commonTests() { }); }), testTimeout); - it('should call onTurnStart and onTurnDone when an inner microtask is scheduled from outside angular', + it('should call onUnstable and onMicrotaskEmpty when an inner microtask is scheduled from outside angular', inject([AsyncTestCompleter], (async) => { - logOnTurnStart(); - logOnTurnDone(); - var completer: PromiseCompleter; - macroTask( - () => { _zone.runOutsideAngular(() => { completer = PromiseWrapper.completer(); }); }); + macroTask(() => { + NgZone.assertNotInAngularZone(); + completer = PromiseWrapper.completer(); + }); - macroTask( - () => { _zone.run(() => { completer.promise.then(_log.fn('executedMicrotask')); }); }); + runNgZoneNoLog(() => { + macroTask(() => { + NgZone.assertInAngularZone(); + completer.promise.then(_log.fn('executedMicrotask')); + }); + }); macroTask(() => { - _zone.runOutsideAngular(() => { - _log.add('scheduling a microtask'); - completer.resolve(null); - }); + NgZone.assertNotInAngularZone(); + _log.add('scheduling a microtask'); + completer.resolve(null); }); macroTask(() => { expect(_log.result()) .toEqual( // First VM turn => setup Promise then - 'onTurnStart; onTurnDone; ' + - // Second VM turn (outside of anguler) - 'scheduling a microtask; ' + + 'onUnstable; onMicrotaskEmpty; onStable; ' + + // Second VM turn (outside of angular) + 'scheduling a microtask; onUnstable; ' + // Third VM Turn => execute the microtask (inside angular) - 'onTurnStart; executedMicrotask; onTurnDone'); + // No onUnstable; because we don't own the task which started the turn. + 'executedMicrotask; onMicrotaskEmpty; onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should call onTurnStart before executing a microtask scheduled in onTurnDone as well as ' + - 'onTurnDone after executing the task', + it('should call onUnstable only before executing a microtask scheduled in onMicrotaskEmpty ' + + 'and not onMicrotaskEmpty after executing the task', inject([AsyncTestCompleter], (async) => { - var ran = false; - logOnTurnStart(); + runNgZoneNoLog(() => macroTask(_log.fn('run'))); - ObservableWrapper.subscribe(_zone.onTurnDone, (_) => { - _log.add('onTurnDone(begin)'); + var ran = false; + ObservableWrapper.subscribe(_zone.onMicrotaskEmpty, (_) => { + _log.add('onMicrotaskEmpty(begin)'); if (!ran) { _zone.run(() => { - microTask(() => { + scheduleMicroTask(() => { ran = true; _log.add('executedMicrotask'); }); }); } - _log.add('onTurnDone(end)'); + _log.add('onMicrotaskEmpty(end)'); }); - macroTask(() => { _zone.run(_log.fn('run')); }); - macroTask(() => { expect(_log.result()) .toEqual( // First VM turn => 'run' macrotask - 'onTurnStart; run; onTurnDone(begin); onTurnDone(end); ' + - // Second VM Turn => microtask enqueued from onTurnDone - 'onTurnStart; executedMicrotask; onTurnDone(begin); onTurnDone(end)'); + 'onUnstable; run; onMicrotaskEmpty; onMicrotaskEmpty(begin); onMicrotaskEmpty(end); ' + + // Second microtaskDrain Turn => microtask enqueued from onMicrotaskEmpty + 'executedMicrotask; onMicrotaskEmpty; onMicrotaskEmpty(begin); onMicrotaskEmpty(end); onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should call onTurnStart and onTurnDone for a scheduleMicrotask in onTurnDone triggered by ' + - 'a scheduleMicrotask in run', + it('should call onUnstable and onMicrotaskEmpty for a scheduleMicroTask in onMicrotaskEmpty triggered by ' + + 'a scheduleMicroTask in run', inject([AsyncTestCompleter], (async) => { - var ran = false; - logOnTurnStart(); + runNgZoneNoLog(() => { + macroTask(() => { + _log.add('scheduleMicroTask'); + scheduleMicroTask(_log.fn('run(executeMicrotask)')); + }); + }); - ObservableWrapper.subscribe(_zone.onTurnDone, (_) => { - _log.add('onTurnDone(begin)'); + var ran = false; + ObservableWrapper.subscribe(_zone.onMicrotaskEmpty, (_) => { + _log.add('onMicrotaskEmpty(begin)'); if (!ran) { - _log.add('onTurnDone(scheduleMicrotask)'); + _log.add('onMicrotaskEmpty(scheduleMicroTask)'); _zone.run(() => { - microTask(() => { + scheduleMicroTask(() => { ran = true; - _log.add('onTurnDone(executeMicrotask)'); + _log.add('onMicrotaskEmpty(executeMicrotask)'); }); }); } - _log.add('onTurnDone(end)'); - }); - - macroTask(() => { - _zone.run(() => { - _log.add('scheduleMicrotask'); - microTask(_log.fn('run(executeMicrotask)')); - }); + _log.add('onMicrotaskEmpty(end)'); }); macroTask(() => { expect(_log.result()) .toEqual( // First VM Turn => a macrotask + the microtask it enqueues - 'onTurnStart; scheduleMicrotask; run(executeMicrotask); onTurnDone(begin); onTurnDone(scheduleMicrotask); onTurnDone(end); ' + - // Second VM Turn => the microtask enqueued from onTurnDone - 'onTurnStart; onTurnDone(executeMicrotask); onTurnDone(begin); onTurnDone(end)'); + 'onUnstable; scheduleMicroTask; run(executeMicrotask); onMicrotaskEmpty; onMicrotaskEmpty(begin); onMicrotaskEmpty(scheduleMicroTask); onMicrotaskEmpty(end); ' + + // Second VM Turn => the microtask enqueued from onMicrotaskEmpty + 'onMicrotaskEmpty(executeMicrotask); onMicrotaskEmpty; onMicrotaskEmpty(begin); onMicrotaskEmpty(end); onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should execute promises scheduled in onTurnStart before promises scheduled in run', + it('should execute promises scheduled in onUnstable before promises scheduled in run', inject([AsyncTestCompleter], (async) => { - var donePromiseRan = false; - var startPromiseRan = false; - - ObservableWrapper.subscribe(_zone.onTurnStart, (_) => { - _log.add('onTurnStart(begin)'); - if (!startPromiseRan) { - _log.add('onTurnStart(schedulePromise)'); - _zone.run(() => { microTask(_log.fn('onTurnStart(executePromise)')); }); - startPromiseRan = true; - } - _log.add('onTurnStart(end)'); - }); - - ObservableWrapper.subscribe(_zone.onTurnDone, (_) => { - _log.add('onTurnDone(begin)'); - if (!donePromiseRan) { - _log.add('onTurnDone(schedulePromise)'); - _zone.run(() => { microTask(_log.fn('onTurnDone(executePromise)')); }); - donePromiseRan = true; - } - _log.add('onTurnDone(end)'); - }); - - macroTask(() => { - _zone.run(() => { + runNgZoneNoLog(() => { + macroTask(() => { _log.add('run start'); PromiseWrapper.resolve(null) .then((_) => { @@ -594,33 +567,53 @@ function commonTests() { }); }); + var donePromiseRan = false; + var startPromiseRan = false; + + ObservableWrapper.subscribe(_zone.onUnstable, (_) => { + _log.add('onUnstable(begin)'); + if (!startPromiseRan) { + _log.add('onUnstable(schedulePromise)'); + _zone.run(() => { scheduleMicroTask(_log.fn('onUnstable(executePromise)')); }); + startPromiseRan = true; + } + _log.add('onUnstable(end)'); + }); + + ObservableWrapper.subscribe(_zone.onMicrotaskEmpty, (_) => { + _log.add('onMicrotaskEmpty(begin)'); + if (!donePromiseRan) { + _log.add('onMicrotaskEmpty(schedulePromise)'); + _zone.run(() => { scheduleMicroTask(_log.fn('onMicrotaskEmpty(executePromise)')); }); + donePromiseRan = true; + } + _log.add('onMicrotaskEmpty(end)'); + }); + macroTask(() => { expect(_log.result()) .toEqual( - // First VM turn: enqueue a microtask in onTurnStart - 'onTurnStart(begin); onTurnStart(schedulePromise); onTurnStart(end); ' + + // First VM turn: enqueue a microtask in onUnstable + 'onUnstable; onUnstable(begin); onUnstable(schedulePromise); onUnstable(end); ' + // First VM turn: execute the macrotask which enqueues microtasks 'run start; run end; ' + // First VM turn: execute enqueued microtasks - 'onTurnStart(executePromise); promise then; promise foo; promise bar; ' + + 'onUnstable(executePromise); promise then; promise foo; promise bar; onMicrotaskEmpty; ' + // First VM turn: onTurnEnd, enqueue a microtask - 'onTurnDone(begin); onTurnDone(schedulePromise); onTurnDone(end); ' + + 'onMicrotaskEmpty(begin); onMicrotaskEmpty(schedulePromise); onMicrotaskEmpty(end); ' + // Second VM turn: execute the microtask from onTurnEnd - 'onTurnStart(begin); onTurnStart(end); onTurnDone(executePromise); onTurnDone(begin); onTurnDone(end)'); + 'onMicrotaskEmpty(executePromise); onMicrotaskEmpty; onMicrotaskEmpty(begin); onMicrotaskEmpty(end); onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should call onTurnStart and onTurnDone before and after each turn, respectively', + it('should call onUnstable and onMicrotaskEmpty before and after each turn, respectively', inject([AsyncTestCompleter], (async) => { var completerA: PromiseCompleter; var completerB: PromiseCompleter; - logOnTurnStart(); - logOnTurnDone(); - - macroTask(() => { - _zone.run(() => { + runNgZoneNoLog(() => { + macroTask(() => { completerA = PromiseWrapper.completer(); completerB = PromiseWrapper.completer(); completerA.promise.then(_log.fn('a then')); @@ -629,34 +622,31 @@ function commonTests() { }); }); - macroTask(() => { _zone.run(() => { completerA.resolve(null); }); }, 20); + runNgZoneNoLog(() => { macroTask(() => { completerA.resolve(null); }, 10); }); - macroTask(() => { _zone.run(() => { completerB.resolve(null); }); }, 500); + runNgZoneNoLog(() => { macroTask(() => { completerB.resolve(null); }, 20); }); macroTask(() => { expect(_log.result()) .toEqual( // First VM turn - 'onTurnStart; run start; onTurnDone; ' + + 'onUnstable; run start; onMicrotaskEmpty; onStable; ' + // Second VM turn - 'onTurnStart; a then; onTurnDone; ' + + 'onUnstable; a then; onMicrotaskEmpty; onStable; ' + // Third VM turn - 'onTurnStart; b then; onTurnDone'); + 'onUnstable; b then; onMicrotaskEmpty; onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should call onTurnStart and onTurnDone before and after (respectively) all turns in a chain', + it('should call onUnstable and onMicrotaskEmpty before and after (respectively) all turns in a chain', inject([AsyncTestCompleter], (async) => { - logOnTurnStart(); - logOnTurnDone(); - - macroTask(() => { - _zone.run(() => { + runNgZoneNoLog(() => { + macroTask(() => { _log.add('run start'); - microTask(() => { + scheduleMicroTask(() => { _log.add('async1'); - microTask(_log.fn('async2')); + scheduleMicroTask(_log.fn('async2')); }); _log.add('run end'); }); @@ -664,24 +654,22 @@ function commonTests() { macroTask(() => { expect(_log.result()) - .toEqual('onTurnStart; run start; run end; async1; async2; onTurnDone'); + .toEqual( + 'onUnstable; run start; run end; async1; async2; onMicrotaskEmpty; onStable'); async.done(); }, resultTimer); }), testTimeout); - it('should call onTurnStart and onTurnDone for promises created outside of run body', + it('should call onUnstable and onMicrotaskEmpty for promises created outside of run body', inject([AsyncTestCompleter], (async) => { - logOnTurnStart(); - logOnTurnDone(); - var promise: Promise; - macroTask(() => { - _zone.runOutsideAngular(() => { - promise = PromiseWrapper.resolve(4).then((x) => PromiseWrapper.resolve(x)); - }); + runNgZoneNoLog(() => { + macroTask(() => { + _zone.runOutsideAngular(() => { + promise = PromiseWrapper.resolve(4).then((x) => PromiseWrapper.resolve(x)); + }); - _zone.run(() => { promise.then(_log.fn('promise then')); _log.add('zone run'); }); @@ -689,7 +677,8 @@ function commonTests() { macroTask(() => { expect(_log.result()) - .toEqual('onTurnStart; zone run; onTurnDone; onTurnStart; promise then; onTurnDone'); + .toEqual('onUnstable; zone run; onMicrotaskEmpty; onStable; ' + + 'onUnstable; promise then; onMicrotaskEmpty; onStable'); async.done(); }, resultTimer); }), testTimeout); @@ -699,8 +688,6 @@ function commonTests() { it('should call the on error callback when it is defined', inject([AsyncTestCompleter], (async) => { macroTask(() => { - logOnError(); - var exception = new BaseException('sync'); _zone.run(() => { throw exception; }); @@ -712,11 +699,9 @@ function commonTests() { }), testTimeout); it('should call onError for errors from microtasks', inject([AsyncTestCompleter], (async) => { - logOnError(); - var exception = new BaseException('async'); - macroTask(() => { _zone.run(() => { microTask(() => { throw exception; }); }); }); + macroTask(() => { _zone.run(() => { scheduleMicroTask(() => { throw exception; }); }); }); macroTask(() => { expect(_errors.length).toBe(1); diff --git a/modules/angular2/test/platform/browser/bootstrap_spec.ts b/modules/angular2/test/platform/browser/bootstrap_spec.ts index 5ff9cf929c..cafe68ca89 100644 --- a/modules/angular2/test/platform/browser/bootstrap_spec.ts +++ b/modules/angular2/test/platform/browser/bootstrap_spec.ts @@ -20,10 +20,10 @@ import {Component, Directive, OnDestroy, platform} from 'angular2/core'; import {BROWSER_PROVIDERS, BROWSER_APP_PROVIDERS} from 'angular2/platform/browser'; import {DOM} from 'angular2/src/platform/dom/dom_adapter'; import {DOCUMENT} from 'angular2/src/platform/dom/dom_tokens'; -import {PromiseWrapper} from 'angular2/src/facade/async'; +import {PromiseWrapper, TimerWrapper} from 'angular2/src/facade/async'; import {provide, Inject, Injector, PLATFORM_INITIALIZER, APP_INITIALIZER} from 'angular2/core'; import {disposePlatform} from 'angular2/src/core/application_ref'; -import {ExceptionHandler} from 'angular2/src/facade/exceptions'; +import {ExceptionHandler, BaseException} from 'angular2/src/facade/exceptions'; import {Testability, TestabilityRegistry} from 'angular2/src/core/testability/testability'; import {ComponentRef_, ComponentRef} from "angular2/src/core/linker/dynamic_component_loader"; @@ -119,13 +119,12 @@ export function main() { `Could not compile '${stringify(HelloRootDirectiveIsNotCmp)}' because it is not a component.`); expect(logger.res.join("")).toContain("Could not compile"); async.done(); - return null; }); })); it('should throw if no element is found', inject([AsyncTestCompleter], (async) => { var logger = new _ArrayLogger(); - var exceptionHandler = new ExceptionHandler(logger, !IS_DART); + var exceptionHandler = new ExceptionHandler(logger, false); var refPromise = bootstrap(HelloRootCmp, [provide(ExceptionHandler, {useValue: exceptionHandler})]); @@ -137,10 +136,25 @@ export function main() { })); if (DOM.supportsDOMEvents()) { + it('should forward the error to promise when bootstrap fails', + inject([AsyncTestCompleter], (async) => { + // Skip for dart since it causes a confusing error message in console when test passes. + var logger = new _ArrayLogger(); + var exceptionHandler = new ExceptionHandler(logger, false); + + var refPromise = + bootstrap(HelloRootCmp, [provide(ExceptionHandler, {useValue: exceptionHandler})]); + PromiseWrapper.then(refPromise, null, (reason: BaseException) => { + expect(reason.message) + .toContain('The selector "hello-app" did not match any elements'); + async.done(); + }); + })); + it('should invoke the default exception handler when bootstrap fails', inject([AsyncTestCompleter], (async) => { var logger = new _ArrayLogger(); - var exceptionHandler = new ExceptionHandler(logger, !IS_DART); + var exceptionHandler = new ExceptionHandler(logger, false); var refPromise = bootstrap(HelloRootCmp, [provide(ExceptionHandler, {useValue: exceptionHandler})]); diff --git a/modules/angular2/test/public_api_spec.ts b/modules/angular2/test/public_api_spec.ts index 3129ec35c0..8b14a3b587 100644 --- a/modules/angular2/test/public_api_spec.ts +++ b/modules/angular2/test/public_api_spec.ts @@ -162,7 +162,6 @@ var NG_CORE = [ 'DirectiveResolver', 'DynamicComponentLoader', 'ElementRef', - 'ErrorHandlingFn:dart', 'Output', 'EmbeddedViewRef', 'EventEmitter', @@ -244,7 +243,6 @@ var NG_CORE = [ 'ViewResolver', 'WrappedException', 'WrappedValue', - 'ZeroArgFunction:dart', 'asNativeElements', 'bind', 'provide', diff --git a/modules/angular2/test/router/route_config/route_config_spec.ts b/modules/angular2/test/router/route_config/route_config_spec.ts index 33e5b47a22..7da0123fff 100644 --- a/modules/angular2/test/router/route_config/route_config_spec.ts +++ b/modules/angular2/test/router/route_config/route_config_spec.ts @@ -51,7 +51,7 @@ export function main() { el = DOM.createElement('app-cmp', fakeDoc); DOM.appendChild(fakeDoc.body, el); var logger = new _ArrayLogger(); - var exceptionHandler = new ExceptionHandler(logger, !IS_DART); + var exceptionHandler = new ExceptionHandler(logger, false); testBindings = [ ROUTER_PROVIDERS, provide(LocationStrategy, {useClass: MockLocationStrategy}), diff --git a/modules/angular2/tsd.json b/modules/angular2/tsd.json index b551a87bba..5c497290f2 100644 --- a/modules/angular2/tsd.json +++ b/modules/angular2/tsd.json @@ -23,9 +23,6 @@ "selenium-webdriver/selenium-webdriver.d.ts": { "commit": "6eebd5e90a1cbd6b47b0705ba72dbcd5baf846f3" }, - "zone.js/zone.js.d.ts": { - "commit": "6eebd5e90a1cbd6b47b0705ba72dbcd5baf846f3" - }, "angular-protractor/angular-protractor.d.ts": { "commit": "6eebd5e90a1cbd6b47b0705ba72dbcd5baf846f3" } diff --git a/npm-shrinkwrap.clean.json b/npm-shrinkwrap.clean.json index c9d504c470..a0c8f45b34 100644 --- a/npm-shrinkwrap.clean.json +++ b/npm-shrinkwrap.clean.json @@ -5835,7 +5835,7 @@ } }, "zone.js": { - "version": "0.5.15" + "version": "0.6.4" } }, "name": "angular-srcs", diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 878526ea0b..543639bb3b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -9309,9 +9309,8 @@ } }, "zone.js": { - "version": "0.5.15", - "from": "zone.js@0.5.15", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.5.15.tgz" + "version": "0.6.4", + "from": "zone.js@0.6.4" } } } diff --git a/package.json b/package.json index bca74a333e..b8dcf98cd7 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "es6-shim": "^0.33.3", "reflect-metadata": "0.1.2", "rxjs": "5.0.0-beta.2", - "zone.js": "0.5.15" + "zone.js": "^0.6.4" }, "devDependencies": { "angular": "^1.5.0", diff --git a/tools/broccoli/broccoli-typescript.ts b/tools/broccoli/broccoli-typescript.ts index 1913d02c50..859b51dd10 100644 --- a/tools/broccoli/broccoli-typescript.ts +++ b/tools/broccoli/broccoli-typescript.ts @@ -331,6 +331,8 @@ class CustomLanguageServiceHost implements ts.LanguageServiceHost { absoluteTsFilePath = path.resolve(tsFilePath); } else if (tsFilePath.match(/^rxjs/)) { absoluteTsFilePath = path.resolve('node_modules', tsFilePath); + } else if (tsFilePath.match(/^node_modules/)) { + absoluteTsFilePath = path.resolve('node_modules/../', tsFilePath); } else { absoluteTsFilePath = path.join(this.treeInputPath, tsFilePath); } diff --git a/tools/broccoli/html-replace/SCRIPTS.html b/tools/broccoli/html-replace/SCRIPTS.html index 2195c9ac9e..9bacb6fcbd 100644 --- a/tools/broccoli/html-replace/SCRIPTS.html +++ b/tools/broccoli/html-replace/SCRIPTS.html @@ -27,7 +27,7 @@ loadRuntimePackages.push('angular2'); scriptUrls = [ 'Reflect.js', - 'zone-microtask.js', + 'zone.js', 'long-stack-trace-zone.js' ]; } @@ -45,5 +45,5 @@ diff --git a/tools/broccoli/html-replace/SCRIPTS_benchmarks.html b/tools/broccoli/html-replace/SCRIPTS_benchmarks.html index f18b58e349..a6f234761e 100644 --- a/tools/broccoli/html-replace/SCRIPTS_benchmarks.html +++ b/tools/broccoli/html-replace/SCRIPTS_benchmarks.html @@ -28,7 +28,7 @@ loadRuntimePackages.push('angular2'); scriptUrls = [ 'Reflect.js', - 'zone-microtask.js', + 'zone.js', 'long-stack-trace-zone.js' ]; } @@ -46,5 +46,5 @@ diff --git a/tools/broccoli/html-replace/SCRIPTS_benchmarks_external.html b/tools/broccoli/html-replace/SCRIPTS_benchmarks_external.html index 369576ce56..1c13a637eb 100644 --- a/tools/broccoli/html-replace/SCRIPTS_benchmarks_external.html +++ b/tools/broccoli/html-replace/SCRIPTS_benchmarks_external.html @@ -1,5 +1,5 @@ - + @@ -10,5 +10,5 @@ defaultJSExtensions: true }); var filename = '@@PATH/@@FILENAME'; - System.import(filename).then(function(m) { m.main(); }, console.error.bind(console)); + System.import(filename).then(function(m) { m.main && m.main(); }, console.error.bind(console)); diff --git a/tools/broccoli/js-replace/SCRIPTS.js b/tools/broccoli/js-replace/SCRIPTS.js index 7ef18d10ad..371769eff9 100644 --- a/tools/broccoli/js-replace/SCRIPTS.js +++ b/tools/broccoli/js-replace/SCRIPTS.js @@ -1,2 +1,2 @@ -importScripts("es6-shim.js", "zone-microtask.js", "long-stack-trace-zone.js", "system.src.js", +importScripts("es6-shim.js", "zone.js", "long-stack-trace-zone.js", "system.src.js", "Reflect.js"); diff --git a/tools/broccoli/trees/browser_tree.ts b/tools/broccoli/trees/browser_tree.ts index abdaa6c3f0..2665a5a971 100644 --- a/tools/broccoli/trees/browser_tree.ts +++ b/tools/broccoli/trees/browser_tree.ts @@ -174,6 +174,7 @@ module.exports = function makeBrowserTree(options, destinationPath) { let ambientTypings = [ 'angular2/typings/hammerjs/hammerjs.d.ts', 'angular2/typings/node/node.d.ts', + 'node_modules/zone.js/dist/zone.js.d.ts', 'angular2/manual_typings/globals.d.ts', 'angular2/typings/es6-collections/es6-collections.d.ts', 'angular2/typings/es6-promise/es6-promise.d.ts' @@ -197,7 +198,7 @@ module.exports = function makeBrowserTree(options, destinationPath) { var vendorScriptsTree = flatten(new Funnel('.', { files: [ 'node_modules/es6-shim/es6-shim.js', - 'node_modules/zone.js/dist/zone-microtask.js', + 'node_modules/zone.js/dist/zone.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js', 'node_modules/systemjs/dist/system.src.js', 'node_modules/base64-js/lib/b64.js', diff --git a/tools/broccoli/trees/node_tree.ts b/tools/broccoli/trees/node_tree.ts index d3d39097f3..5df590a57a 100644 --- a/tools/broccoli/trees/node_tree.ts +++ b/tools/broccoli/trees/node_tree.ts @@ -36,6 +36,7 @@ module.exports = function makeNodeTree(projects, destinationPath) { let ambientTypings = [ 'angular2/typings/hammerjs/hammerjs.d.ts', 'angular2/typings/node/node.d.ts', + 'node_modules/zone.js/dist/zone.js.d.ts', 'angular2/manual_typings/globals.d.ts', 'angular2/typings/es6-collections/es6-collections.d.ts', 'angular2/typings/es6-promise/es6-promise.d.ts' diff --git a/tools/cjs-jasmine/index.js b/tools/cjs-jasmine/index.js index 92cbb9536e..72aa161059 100644 --- a/tools/cjs-jasmine/index.js +++ b/tools/cjs-jasmine/index.js @@ -3,6 +3,8 @@ var glob = require('glob'); var JasmineRunner = require('jasmine'); var path = require('path'); +require('zone.js/dist/zone-node.js'); +require('zone.js/dist/long-stack-trace-zone.js'); require('es6-shim/es6-shim.js'); require('reflect-metadata/Reflect'); diff --git a/tools/public_api_guard/public_api_spec.ts b/tools/public_api_guard/public_api_spec.ts index 3edbe530a9..1de862efad 100644 --- a/tools/public_api_guard/public_api_spec.ts +++ b/tools/public_api_guard/public_api_spec.ts @@ -182,7 +182,6 @@ const CORE = [ 'EmbeddedViewRef.hasLocal(variableName:string):boolean', 'EmbeddedViewRef.rootNodes:any[]', 'EmbeddedViewRef.setLocal(variableName:string, value:any):void', - 'ErrorHandlingFn', 'EventEmitter.constructor(isAsync:boolean)', 'EventEmitter.emit(value:T):any', 'EventEmitter.next(value:any):any', @@ -282,18 +281,16 @@ const CORE = [ 'KeyValueDiffers.extend(factories:KeyValueDifferFactory[]):Provider', 'KeyValueDiffers.find(kv:Object):KeyValueDifferFactory', 'NgZone', - 'NgZone.constructor({enableLongStackTrace}:any)', - 'NgZone.hasPendingAsyncTasks:boolean', + 'NgZone.constructor({enableLongStackTrace=false}:any)', + 'NgZone.assertInAngularZone():void', + 'NgZone.assertNotInAngularZone():void', + 'NgZone.isInAngularZone():boolean', + 'NgZone.hasPendingMacrotasks:boolean', 'NgZone.hasPendingMicrotasks:boolean', - 'NgZone.hasPendingTimers:boolean', - 'NgZone.onError:any', - 'NgZone.onEventDone:any', - 'NgZone.onTurnDone:any', - 'NgZone.onTurnStart:any', - 'NgZone.overrideOnErrorHandler(errorHandler:ErrorHandlingFn):any', - 'NgZone.overrideOnEventDone(onEventDoneFn:ZeroArgFunction, opt_waitForAsync:boolean):void', - 'NgZone.overrideOnTurnDone(onTurnDoneHook:ZeroArgFunction):void', - 'NgZone.overrideOnTurnStart(onTurnStartHook:ZeroArgFunction):void', + 'NgZone.onError:EventEmitter', + 'NgZone.onStable:EventEmitter', + 'NgZone.onMicrotaskEmpty:EventEmitter', + 'NgZone.onUnstable:EventEmitter', 'NgZone.run(fn:() => any):any', 'NgZone.runOutsideAngular(fn:() => any):any', 'NgZoneError', @@ -442,7 +439,6 @@ const CORE = [ 'Testability.findProviders(using:any, provider:string, exactMatch:boolean):any[]', 'Testability.getPendingRequestCount():number', 'Testability.increasePendingRequestCount():number', - 'Testability.isAngularEventPending():boolean', 'Testability.isStable():boolean', 'Testability.whenStable(callback:Function):void', 'TestabilityRegistry', @@ -510,7 +506,6 @@ const CORE = [ 'WrappedValue', 'WrappedValue.constructor(wrapped:any)', 'WrappedValue.wrap(value:any):WrappedValue', - 'ZeroArgFunction', 'bind(token:any):ProviderBuilder', 'const APPLICATION_COMMON_PROVIDERS:Array', 'const APP_COMPONENT:OpaqueToken',