Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

feat(scope): digest asynchronously in scope.apply #70

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions lib/scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,22 @@ class Scope implements Map {
}


bool _rootDigestScheduled = false;
_scheduleRootDigest() {
if ($root._rootDigestScheduled) return;
$root._rootDigestScheduled = true;
async.runAsync(() {
$root._rootDigestScheduled = false;
try {
$root.$digest();
} catch (e, s) {
// Sadly, this stack trace is mostly useless now.
_exceptionHandler(e, s);
throw e;
}
});
}

$apply([expr]) {
try {
_beginPhase('\$apply');
Expand All @@ -290,12 +306,7 @@ class Scope implements Map {
_exceptionHandler(e, s);
} finally {
_clearPhase();
try {
$root.$digest();
} catch (e, s) {
_exceptionHandler(e, s);
throw e;
}
_scheduleRootDigest();
}
}

Expand Down
31 changes: 21 additions & 10 deletions test/compiler_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -267,39 +267,46 @@ main() {
expect(renderedText(element)).toEqual('inside ');
})));

it('should create a component with IO', inject(() {
it('should create a component with IO', async(inject(() {
var element = $(r'<div><io attr="A" expr="name" ondone="done=true"></io></div>');
$compile(element)(injector, element);
$rootScope.name = 'misko';
$rootScope.$apply();
nextTurn(true);

var component = $rootScope.ioComponent;
expect(component.scope.name).toEqual(null);
expect(component.scope.attr).toEqual('A');
expect(component.scope.expr).toEqual('misko');
component.scope.expr = 'angular';
$rootScope.$apply();
nextTurn(true);

expect($rootScope.name).toEqual('angular');
expect($rootScope.done).toEqual(null);
component.scope.ondone();
expect($rootScope.done).toEqual(true);
}));
})));

it('should create a component with IO and "=" binding value should be available', inject(() {
it('should create a component with IO and "=" binding value should be available', async(inject(() {
$rootScope.name = 'misko';
var element = $(r'<div><io attr="A" expr="name" ondone="done=true"></io></div>');
$compile(element)(injector, element);
var component = $rootScope.ioComponent;
expect(component.scope.expr).toEqual('misko');
$rootScope.$apply();
nextTurn(true);

component.scope.expr = 'angular';
$rootScope.$apply();
nextTurn(true);

expect($rootScope.name).toEqual('angular');
}));
})));

it('should expose mapped attributes as camel case', inject(() {
var element = $('<camel-case-map camel-case=6></camel-case-map>');
$compile(element)(injector, element);
$rootScope.$apply();
var componentScope = $rootScope.camelCase;
expect(componentScope.camelCase).toEqual('6');
}));
Expand All @@ -315,8 +322,8 @@ main() {
var element = $(r'<div><publish-me></publish-me></div>');
$compile(element)(injector, element);
$rootScope.$apply();

nextTurn(true);

expect(element.textWithShadow()).toEqual('WORKED');
})));

Expand All @@ -327,12 +334,14 @@ main() {
toBe(PublishTypesAttrDirective._injector.get(PublishTypesDirectiveSuperType));
}));

it('should allow repeaters over controllers', inject((Logger logger) {
it('should allow repeaters over controllers', async(inject((Logger logger) {
var element = $(r'<log ng-repeat="i in [1, 2]"></log>');
$compile(element)(injector, element);
$rootScope.$apply();
nextTurn(true);

expect(logger.length).toEqual(2);
}));
})));
});

describe('controller scoping', () {
Expand All @@ -343,12 +352,14 @@ main() {
expect(log.result()).toEqual('TabComponent-0; LocalAttrDirective-0; PaneComponent-1; LocalAttrDirective-0; PaneComponent-2; LocalAttrDirective-0');
}));

it('should reuse controllers for transclusions', inject((Compiler $compile, Scope $rootScope, Log log, Injector injector) {
it('should reuse controllers for transclusions', async(inject((Compiler $compile, Scope $rootScope, Log log, Injector injector) {
var element = $('<div simple-transclude-in-attach include-transclude>block</div>');
$compile(element)(injector, element);
$rootScope.$apply();
nextTurn(true);

expect(log.result()).toEqual('IncludeTransclude; SimpleTransclude');
}));
})));

});
});
Expand Down
7 changes: 5 additions & 2 deletions test/directives/ng_bind_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,26 @@ main() {
}));


it('should bind to non string values', inject((Scope scope) {
it('should bind to non string values', async(inject((Scope scope) {
var element = _.compile('<div ng-bind="value"></div>');

scope.$apply(() {
scope['value'] = null;
});
nextTurn(true);
expect(element.text()).toEqual('');

scope.$apply(() {
scope['value'] = true;
});
nextTurn(true);
expect(element.text()).toEqual('true');

scope.$apply(() {
scope['value'] = 1;
});
nextTurn(true);
expect(element.text()).toEqual('1');
}));
})));
});
}
34 changes: 24 additions & 10 deletions test/directives/ng_class_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,41 @@ main() {
}));


it('should add and remove class dynamically', () {
it('should add and remove class dynamically', async(() {
compile('<div ng-class="active"></div>');

rootScope.$apply(() {
rootScope['active'] = 'active';
});
nextTurn(true);

expect(element.hasClass('active')).toBe(true);

rootScope.$apply(() {
rootScope['active'] = 'inactive';
});
nextTurn(true);

expect(element.hasClass('active')).toBe(false);
expect(element.hasClass('inactive')).toBe(true);
});
}));


it('should preserve originally defined classes', () {
it('should preserve originally defined classes', async(() {
compile('<div class="original" ng-class="active"></div>');

expect(element.hasClass('original')).toBe(true);

rootScope.$apply(() {
rootScope['active'] = 'something';
});
nextTurn(true);

expect(element.hasClass('original')).toBe(true);
});
}));


it('should preserve classes that has been added after compilation', () {
it('should preserve classes that has been added after compilation', async(() {
compile('<div ng-class="active"></div>');

element[0].classes.add('after-compile');
Expand All @@ -54,34 +60,42 @@ main() {
rootScope.$apply(() {
rootScope['active'] = 'something';
});
nextTurn(true);

expect(element.hasClass('after-compile')).toBe(true);
});
}));


it('should allow multiple classes separated by a space', () {
it('should allow multiple classes separated by a space', async(() {
compile('<div class="original" ng-class="active"></div>');

rootScope.$apply(() {
rootScope['active'] = 'first second';
});
nextTurn(true);

expect(element).toHaveClass('first');
expect(element).toHaveClass('second');
expect(element).toHaveClass('original');

rootScope.$apply(() {
rootScope['active'] = 'third first';
});
nextTurn(true);

expect(element).toHaveClass('first');
expect(element).not.toHaveClass('second');
expect(element).toHaveClass('third');
expect(element).toHaveClass('original');
});
}));


it('should update value that was set before compilation', () {
it('should update value that was set before compilation', async(() {
rootScope['active'] = 'something';
compile('<div ng-class="active"></div>');
nextTurn(true);

expect(element).toHaveClass('something');
});
}));
});
}
13 changes: 7 additions & 6 deletions test/directives/ng_controller_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,30 @@ main() {
rootScope = scope;
compiler(element)(injector, element);
scope.$apply(applyFn);
nextTurn(true);
};
}));


it('should instantiate controller', () {
it('should instantiate controller', async(() {
compile('<div><div ng-controller="Main" class="controller">Hi {{name}}</div></div>');
expect(element.find('.controller').text()).toEqual('Hi Vojta');
});
}));


it('should create a new scope', () {
it('should create a new scope', async(() {
compile('<div><div ng-controller="Main" class="controller">Hi {{name}}</div><div class="siblink">{{name}}</div></div>', () {
rootScope['name'] = 'parent';
});

expect(element.find('.controller').text()).toEqual('Hi Vojta');
expect(element.find('.siblink').text()).toEqual('parent');
});
}));


it('should export controller', () {
it('should export controller', async(() {
compile('<div><div ng-controller="Main as main" class="controller">Hi {{main.name}}</div></div>');
expect(element.find('.controller').text()).toEqual('Hi name on controller');
});
}));
});
}
18 changes: 14 additions & 4 deletions test/directives/ng_disabled_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,47 @@ describe('ng-disabled', () {

beforeEach(beforeEachTestBed((tb) => _ = tb));

it('should disable/enable the element based on the model', inject((Scope scope) {
it('should disable/enable the element based on the model', async(inject((Scope scope) {
var element = _.compile('<button ng-disabled="isDisabled">x</button>');

scope.$apply(() {
scope['isDisabled'] = true;
});
nextTurn(true);

expect(element[0].disabled).toBe(true);

scope.$apply(() {
scope['isDisabled'] = false;
});
nextTurn(true);

expect(element[0].disabled).toBe(false);
}));
})));


it('should accept non boolean values', inject((Scope scope) {
it('should accept non boolean values', async(inject((Scope scope) {
var element = _.compile('<button ng-disabled="isDisabled">x</button>');

scope.$apply(() {
scope['isDisabled'] = null;
});
nextTurn(true);

expect(element[0].disabled).toBe(false);

scope.$apply(() {
scope['isDisabled'] = 1;
});
nextTurn(true);

expect(element[0].disabled).toBe(true);

scope.$apply(() {
scope['isDisabled'] = 0;
});
nextTurn(true);

expect(element[0].disabled).toBe(false);
}));
})));
});
9 changes: 7 additions & 2 deletions test/directives/ng_hide_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,29 @@ main() {
rootScope = scope;
compiler(element)(injector, element);
scope.$apply(applyFn);
nextTurn(true);
};
}));


it('should add/remove ng-hide class', () {
it('should add/remove ng-hide class', async(() {
compile('<div ng-hide="isHidden"></div>');

expect(element).not.toHaveClass('ng-hide');

rootScope.$apply(() {
rootScope['isHidden'] = true;
});
nextTurn(true);

expect(element).toHaveClass('ng-hide');

rootScope.$apply(() {
rootScope['isHidden'] = false;
});
nextTurn(true);

expect(element).not.toHaveClass('ng-hide');
});
}));
});
}
Loading