blob: a50b1c9a0802ff376b144d609ffe135809f91649 [file] [log] [blame]
library zone_spec;
import '../_specs.dart';
import 'dart:async';
main() => describe('zone', () {
var zone;
var exceptionHandler;
beforeEach(module((Module module) {
exceptionHandler = new LoggingExceptionHandler();
module.value(ExceptionHandler, exceptionHandler);
}));
beforeEach(inject((Logger log, ExceptionHandler eh) {
zone = new NgZone();
zone.onTurnDone = () {
log('onTurnDone');
};
zone.onError = (e, s, ls) => eh(e, s);
}));
describe('exceptions', () {
it('should rethrow exceptions from the body and call onError', () {
var error;
zone.onError = (e, s, l) => error = e;
expect(() {
zone.run(() {
throw ['hello'];
});
}).toThrow('hello');
expect(error).toEqual(['hello']);
});
it('should call onError for errors from scheduleMicrotask', async(inject(() {
zone.run(() {
scheduleMicrotask(() {
throw ["async exception"];
});
});
expect(exceptionHandler.errors.length).toEqual(1);
expect(exceptionHandler.errors[0].error).toEqual(["async exception"]);
})));
it('should allow executing code outside the zone', inject(() {
var zone = new NgZone();
var outerZone = Zone.current;
var ngZone;
var outsideZone;
zone.run(() {
ngZone = Zone.current;
zone.runOutsideAngular(() {
outsideZone = Zone.current;
});
});
expect(outsideZone).toEqual(outerZone);
expect(ngZone.parent).toEqual((outerZone));
}));
it('should rethrow exceptions from the onTurnDone and call onError when the zone is sync', () {
zone.onTurnDone = () {
throw ["fromOnTurnDone"];
};
expect(() {
zone.run(() { });
}).toThrow('fromOnTurnDone');
expect(exceptionHandler.errors.length).toEqual(1);
expect(exceptionHandler.errors[0].error).toEqual(["fromOnTurnDone"]);
});
it('should rethrow exceptions from the onTurnDone and call onError when the zone is async', () {
var asyncRan = false;
zone.onTurnDone = () {
throw ["fromOnTurnDone"];
};
expect(() {
zone.run(() {
scheduleMicrotask(() {
asyncRan = true;
});
});
}).toThrow('fromOnTurnDone');
expect(asyncRan).toBeTruthy();
expect(exceptionHandler.errors.length).toEqual(1);
expect(exceptionHandler.errors[0].error).toEqual(["fromOnTurnDone"]);
});
});
xdescribe('long stack traces', () {
it('should have nice error when crossing scheduleMicrotask boundries', async(inject(() {
var error;
var stack;
var longStacktrace;
zone.onError = (e, s, f) {
error = e;
stack = s;
longStacktrace = f;
};
var FRAME = new RegExp(r'.*\(.*\:(\d+):\d+\)');
var line = ((){ try {throw [];} catch(e, s) { return int.parse(FRAME.firstMatch('$s')[1]);}})();
var throwFn = () { throw ['double zonned']; };
var inner = () => zone.run(throwFn);
var middle = () => scheduleMicrotask(inner);
var outer = () => scheduleMicrotask(middle);
zone.run(outer);
microLeap();
expect(error).toEqual(['double zonned']);
// Not in dart2js..
if ('$stack'.contains('.dart.js')) {
return;
}
expect('$stack').toContain('zone_spec.dart:${line+1}');
expect('$stack').toContain('zone_spec.dart:${line+2}');
expect('$longStacktrace').toContain('zone_spec.dart:${line+3}');
expect('$longStacktrace').toContain('zone_spec.dart:${line+4}');
expect('$longStacktrace').toContain('zone_spec.dart:${line+5}');
})));
});
it('should call onTurnDone after a synchronous block', inject((Logger log) {
zone.run(() {
log('run');
});
expect(log.result()).toEqual('run; onTurnDone');
}));
it('should return the body return value from run', () {
expect(zone.run(() { return 6; })).toEqual(6);
});
it('should call onTurnDone for a scheduleMicrotask in onTurnDone', async(inject((Logger log) {
var ran = false;
zone.onTurnDone = () {
if (!ran) {
scheduleMicrotask(() { ran = true; log('onTurnAsync'); });
}
log('onTurnDone');
};
zone.run(() {
log('run');
});
microLeap();
expect(log.result()).toEqual('run; onTurnDone; onTurnAsync; onTurnDone');
})));
it('should call onTurnDone for a scheduleMicrotask in onTurnDone triggered by a scheduleMicrotask in run', async(inject((Logger log) {
var ran = false;
zone.onTurnDone = () {
if (!ran) {
scheduleMicrotask(() { ran = true; log('onTurnAsync'); });
}
log('onTurnDone');
};
zone.run(() {
scheduleMicrotask(() { log('scheduleMicrotask'); });
log('run');
});
microLeap();
expect(log.result()).toEqual('run; scheduleMicrotask; onTurnDone; onTurnAsync; onTurnDone');
})));
it('should call onTurnDone once after a turn', async(inject((Logger log) {
zone.run(() {
log('run start');
scheduleMicrotask(() {
log('async');
});
log('run end');
});
microLeap();
expect(log.result()).toEqual('run start; run end; async; onTurnDone');
})));
it('should work for Future.value as well', async(inject((Logger log) {
var futureRan = false;
zone.onTurnDone = () {
if (!futureRan) {
new Future.value(null).then((_) { log('onTurn future'); });
futureRan = true;
}
log('onTurnDone');
};
zone.run(() {
log('run start');
new Future.value(null)
.then((_) {
log('future then');
new Future.value(null)
.then((_) { log('future ?'); });
return new Future.value(null);
})
.then((_) {
log('future ?');
});
log('run end');
});
microLeap();
expect(log.result()).toEqual('run start; run end; future then; future ?; future ?; onTurnDone; onTurn future; onTurnDone');
})));
it('should call onTurnDone after each turn', async(inject((Logger log) {
Completer a, b;
zone.run(() {
a = new Completer();
b = new Completer();
a.future.then((_) => log('a then'));
b.future.then((_) => log('b then'));
log('run start');
});
microLeap();
zone.run(() {
a.complete(null);
});
microLeap();
zone.run(() {
b.complete(null);
});
microLeap();
expect(log.result()).toEqual('run start; onTurnDone; a then; onTurnDone; b then; onTurnDone');
})));
it('should call onTurnDone after each turn in a chain', async(inject((Logger log) {
zone.run(() {
log('run start');
scheduleMicrotask(() {
log('async1');
scheduleMicrotask(() {
log('async2');
});
});
log('run end');
});
microLeap();
expect(log.result()).toEqual('run start; run end; async1; async2; onTurnDone');
})));
it('should call onTurnDone for futures created outside of run body', async(inject((Logger log) {
var future = new Future.value(4).then((x) => new Future.value(x));
zone.run(() {
future.then((_) => log('future then'));
log('zone run');
});
microLeap();
expect(log.result()).toEqual('zone run; onTurnDone; future then; onTurnDone');
})));
it('should call onTurnDone even if there was an exception in body', async(inject((Logger log) {
zone.onError = (e, s, l) => log('onError');
expect(() => zone.run(() {
log('zone run');
throw 'zoneError';
})).toThrow('zoneError');
expect(() => zone.assertInTurn()).toThrow();
expect(log.result()).toEqual('zone run; onError; onTurnDone');
})));
it('should call onTurnDone even if there was an exception in scheduleMicrotask', async(inject((Logger log) {
zone.onError = (e, s, l) => log('onError');
zone.run(() {
log('zone run');
scheduleMicrotask(() {
log('scheduleMicrotask');
throw new Error();
});
});
microLeap();
expect(() => zone.assertInTurn()).toThrow();
expect(log.result()).toEqual('zone run; scheduleMicrotask; onError; onTurnDone');
})));
it('should support assertInZone', async(() {
var calls = '';
zone.onTurnDone = () {
zone.assertInZone();
calls += 'done;';
};
zone.run(() {
zone.assertInZone();
calls += 'sync;';
scheduleMicrotask(() {
zone.assertInZone();
calls += 'async;';
});
});
microLeap();
expect(calls).toEqual('sync;async;done;');
}));
it('should throw outside of the zone', () {
expect(async(() {
zone.assertInZone();
microLeap();
})).toThrow();
});
it('should support assertInTurn', async(() {
var calls = '';
zone.onTurnDone = () {
calls += 'done;';
zone.assertInTurn();
};
zone.run(() {
calls += 'sync;';
zone.assertInTurn();
scheduleMicrotask(() {
calls += 'async;';
zone.assertInTurn();
});
});
microLeap();
expect(calls).toEqual('sync;async;done;');
}));
it('should assertInTurn outside of the zone', () {
expect(async(() {
zone.assertInTurn();
microLeap();
})).toThrow('ssertion'); // Support both dart2js and the VM with half a word.
});
});