Skip to content

Commit 382489b

Browse files
Merge pull request #108 from angular/master
Update upstream
2 parents e9572d5 + a42f8a0 commit 382489b

File tree

7 files changed

+150
-7
lines changed

7 files changed

+150
-7
lines changed

src/Angular.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1909,7 +1909,7 @@ function bindJQuery() {
19091909
jqLite.cleanData = function(elems) {
19101910
var events;
19111911
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1912-
events = jqLite._data(elem).events;
1912+
events = (jqLite._data(elem) || {}).events;
19131913
if (events && events.$destroy) {
19141914
jqLite(elem).triggerHandler('$destroy');
19151915
}

src/ng/directive/ngEventDirs.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ forEach(
5050
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
5151
function(eventName) {
5252
var directiveName = directiveNormalize('ng-' + eventName);
53-
ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
53+
ngEventDirectives[directiveName] = ['$parse', '$rootScope', '$exceptionHandler', function($parse, $rootScope, $exceptionHandler) {
5454
return {
5555
restrict: 'A',
5656
compile: function($element, attr) {
@@ -64,10 +64,17 @@ forEach(
6464
var callback = function() {
6565
fn(scope, {$event: event});
6666
};
67-
if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
67+
68+
if (!$rootScope.$$phase) {
69+
scope.$apply(callback);
70+
} else if (forceAsyncEvents[eventName]) {
6871
scope.$evalAsync(callback);
6972
} else {
70-
scope.$apply(callback);
73+
try {
74+
callback();
75+
} catch (error) {
76+
$exceptionHandler(error);
77+
}
7178
}
7279
});
7380
};

src/ngMock/angular-mocks.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ angular.mock.$Browser.prototype = {
262262
*
263263
* <div class="alert alert-info">
264264
* Periodic tasks scheduled via {@link $interval} use a different queue and are not flushed by
265-
* `$flushPendingTasks()`. Use {@link ngMock.$interval#flush $interval.flush([millis])} instead.
265+
* `$flushPendingTasks()`. Use {@link ngMock.$interval#flush $interval.flush(millis)} instead.
266266
* </div>
267267
*
268268
* @param {number=} delay - The number of milliseconds to flush.
@@ -650,7 +650,7 @@ angular.mock.$IntervalProvider = function() {
650650
*
651651
* Runs interval tasks scheduled to be run in the next `millis` milliseconds.
652652
*
653-
* @param {number=} millis maximum timeout amount to flush up until.
653+
* @param {number} millis maximum timeout amount to flush up until.
654654
*
655655
* @return {number} The amount of time moved forward.
656656
*/

src/ngMock/browserTrigger.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
* - `charcode`: [charCode](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/charcode)
2929
* for keyboard events (keydown, keypress, and keyup).
3030
*
31+
* - `data`: [data](https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent/data) for
32+
* [CompositionEvents](https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent).
33+
*
3134
* - `elapsedTime`: the elapsedTime for
3235
* [TransitionEvent](https://developer.mozilla.org/docs/Web/API/TransitionEvent)
3336
* and [AnimationEvent](https://developer.mozilla.org/docs/Web/API/AnimationEvent).

src/ngRoute/route.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ function $RouteProvider() {
185185
* route definition, will cause the latter to be ignored.
186186
*
187187
* - `[reloadOnUrl=true]` - `{boolean=}` - reload route when any part of the URL changes
188-
* (inluding the path) even if the new URL maps to the same route.
188+
* (including the path) even if the new URL maps to the same route.
189189
*
190190
* If the option is set to `false` and the URL in the browser changes, but the new URL maps
191191
* to the same route, then a `$routeUpdate` event is broadcasted on the root scope (without

test/jqLiteSpec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,12 @@ describe('jqLite', function() {
501501
expect(jqLite(c).data('prop')).toBeUndefined();
502502
});
503503

504+
it('should not break on cleanData(), if element has no data', function() {
505+
var selected = jqLite([a, b, c]);
506+
spyOn(jqLite, '_data').and.returnValue(undefined);
507+
expect(function() { jqLite.cleanData(selected); }).not.toThrow();
508+
});
509+
504510

505511
it('should add and remove data on SVGs', function() {
506512
var svg = jqLite('<svg><rect></rect></svg>');

test/ng/directive/ngEventDirsSpec.js

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,133 @@ describe('event directives', function() {
148148
expect($rootScope.blur).toHaveBeenCalledOnce();
149149
expect(element.val()).toBe('newValue');
150150
}));
151+
});
152+
153+
154+
it('should call the listener synchronously if the event is triggered inside of a digest',
155+
inject(function($rootScope, $compile) {
156+
var watchedVal;
157+
158+
element = $compile('<button type="button" ng-click="click()">Button</button>')($rootScope);
159+
$rootScope.$watch('value', function(newValue) {
160+
watchedVal = newValue;
161+
});
162+
$rootScope.click = jasmine.createSpy('click').and.callFake(function() {
163+
$rootScope.value = 'newValue';
164+
});
165+
166+
$rootScope.$apply(function() {
167+
element.triggerHandler('click');
168+
});
169+
170+
expect($rootScope.click).toHaveBeenCalledOnce();
171+
expect(watchedVal).toEqual('newValue');
172+
}));
173+
174+
175+
it('should call the listener synchronously if the event is triggered outside of a digest',
176+
inject(function($rootScope, $compile) {
177+
var watchedVal;
178+
179+
element = $compile('<button type="button" ng-click="click()">Button</button>')($rootScope);
180+
$rootScope.$watch('value', function(newValue) {
181+
watchedVal = newValue;
182+
});
183+
$rootScope.click = jasmine.createSpy('click').and.callFake(function() {
184+
$rootScope.value = 'newValue';
185+
});
186+
187+
element.triggerHandler('click');
188+
189+
expect($rootScope.click).toHaveBeenCalledOnce();
190+
expect(watchedVal).toEqual('newValue');
191+
}));
192+
193+
194+
describe('throwing errors in event handlers', function() {
195+
196+
it('should not stop execution if the event is triggered outside a digest', function() {
197+
198+
module(function($exceptionHandlerProvider) {
199+
$exceptionHandlerProvider.mode('log');
200+
});
201+
202+
inject(function($rootScope, $compile, $exceptionHandler, $log) {
203+
204+
element = $compile('<button ng-click="click()">Click</button>')($rootScope);
205+
expect($log.assertEmpty());
206+
$rootScope.click = function() {
207+
throw new Error('listener error');
208+
};
209+
210+
$rootScope.do = function() {
211+
element.triggerHandler('click');
212+
$log.log('done');
213+
};
214+
215+
$rootScope.do();
151216

217+
expect($exceptionHandler.errors).toEqual([Error('listener error')]);
218+
expect($log.log.logs).toEqual([['done']]);
219+
$log.reset();
220+
});
221+
});
222+
223+
224+
it('should not stop execution if the event is triggered inside a digest', function() {
225+
226+
module(function($exceptionHandlerProvider) {
227+
$exceptionHandlerProvider.mode('log');
228+
});
229+
230+
inject(function($rootScope, $compile, $exceptionHandler, $log) {
231+
232+
element = $compile('<button ng-click="click()">Click</button>')($rootScope);
233+
expect($log.assertEmpty());
234+
$rootScope.click = function() {
235+
throw new Error('listener error');
236+
};
237+
238+
$rootScope.do = function() {
239+
element.triggerHandler('click');
240+
$log.log('done');
241+
};
242+
243+
$rootScope.$apply(function() {
244+
$rootScope.do();
245+
});
246+
247+
expect($exceptionHandler.errors).toEqual([Error('listener error')]);
248+
expect($log.log.logs).toEqual([['done']]);
249+
$log.reset();
250+
});
251+
});
252+
253+
254+
it('should not stop execution if the event is triggered in a watch expression function', function() {
255+
256+
module(function($exceptionHandlerProvider) {
257+
$exceptionHandlerProvider.mode('log');
258+
});
259+
260+
inject(function($rootScope, $compile, $exceptionHandler, $log) {
261+
262+
element = $compile('<button ng-click="click()">Click</button>')($rootScope);
263+
$rootScope.click = function() {
264+
throw new Error('listener error');
265+
};
266+
267+
$rootScope.$watch(function() {
268+
element.triggerHandler('click');
269+
$log.log('done');
270+
});
271+
272+
$rootScope.$digest();
273+
274+
expect($exceptionHandler.errors).toEqual([Error('listener error'), Error('listener error')]);
275+
expect($log.log.logs).toEqual([['done'], ['done']]);
276+
$log.reset();
277+
});
278+
});
152279
});
153280
});

0 commit comments

Comments
 (0)