diff --git a/config/test-utils.js b/config/test-utils.js
index 3491b9b79b9..b042f0b918a 100644
--- a/config/test-utils.js
+++ b/config/test-utils.js
@@ -29,20 +29,30 @@ beforeEach(function() {
* Create a fake version of $$rAF that does things synchronously
*/
module('ng', function($provide) {
- $provide.value('$$rAF', mockRaf);
+ /**
+ * Add throttle() and wrap .flush() to catch `no callbacks present`
+ * errors
+ */
+ $provide.decorator('$$rAF', function throttleInjector($delegate){
- function mockRaf(cb) {
- cb();
- }
- mockRaf.throttle = function(cb) {
- return function() {
- cb.apply(this, arguments);
+ $delegate.throttle = function(cb) {
+ return function() {
+ cb.apply(this, arguments);
+ };
};
- };
- mockRaf.flush = angular.noop;
+
+ var ngFlush = $delegate.flush;
+ $delegate.flush = function() {
+ try { ngFlush(); }
+ catch(e) { ; }
+ };
+
+ return $delegate;
+ });
});
+
jasmine.addMatchers({
toHaveClass: function() {
@@ -120,3 +130,28 @@ beforeEach(function() {
});
});
+
+
+beforeEach(function() {
+
+ /**
+ * Create a fake version of $$rAF that does things synchronously
+ */
+ module('material.core', function($provide) {
+
+ /**
+ * Intercept to make .expectWithText() to be synchronous
+ */
+ $provide.decorator('$mdAria', function($delegate){
+
+ $delegate.expectWithText = function(element, attrName){
+ $delegate.expect(element, attrName, element.text().trim());
+ };
+
+ return $delegate;
+ });
+
+ });
+
+
+});
diff --git a/src/components/checkbox/checkbox.js b/src/components/checkbox/checkbox.js
index 06488d43087..640b7f387e1 100644
--- a/src/components/checkbox/checkbox.js
+++ b/src/components/checkbox/checkbox.js
@@ -54,6 +54,7 @@ function MdCheckboxDirective(inputDirective, $mdInkRipple, $mdAria, $mdConstant,
restrict: 'E',
transclude: true,
require: '?ngModel',
+ priority:210, // Run before ngAria
template:
'
' +
'
' +
diff --git a/src/components/checkbox/checkbox.spec.js b/src/components/checkbox/checkbox.spec.js
index 533650ce044..4d48e92cc35 100644
--- a/src/components/checkbox/checkbox.spec.js
+++ b/src/components/checkbox/checkbox.spec.js
@@ -1,39 +1,52 @@
describe('mdCheckbox', function() {
var CHECKED_CSS = 'md-checked';
+ var $compile, $rootScope;
- beforeEach(module('material.components.checkbox'));
beforeEach(module('ngAria'));
+ beforeEach(module('material.components.checkbox'));
+
+ beforeEach( inject(function(_$compile_, _$rootScope_){
+ $compile = _$compile_;
+ $rootScope = _$rootScope_;
+ }));
+
+ function buildInstance (template, scope){
+ var element = $compile(template)(scope || $rootScope);
+ $rootScope.$apply();
+
+ return element;
+ }
it('should warn developers they need a label', inject(function($compile, $rootScope, $log){
spyOn($log, "warn");
- var element = $compile('
' +
+ var element = buildInstance('
' +
'' +
'' +
- '
')($rootScope);
+ '
');
expect($log.warn).toHaveBeenCalled();
}));
it('should copy text content to aria-label', inject(function($compile, $rootScope){
- var element = $compile('
' +
+ var element = buildInstance('
' +
'' +
'Some text' +
'' +
- '
')($rootScope);
+ '
');
var cbElements = element.find('md-checkbox');
expect(cbElements.eq(0).attr('aria-label')).toBe('Some text');
}));
it('should set checked css class and aria-checked attributes', inject(function($compile, $rootScope) {
- var element = $compile('
' +
+ var element = buildInstance('
' +
'' +
'' +
'' +
'' +
- '
')($rootScope);
+ '
');
$rootScope.$apply(function(){
$rootScope.blue = false;
@@ -50,14 +63,14 @@ describe('mdCheckbox', function() {
}));
it('should be disabled with ngDisabled attr', inject(function($compile, $rootScope) {
- var element = $compile('
' +
+ var element = buildInstance('
' +
'' +
'' +
- '
')($rootScope);
+ '
');
var checkbox = element.find('md-checkbox');
- $rootScope.$apply('isDisabled = true');
+ $rootScope.$apply('isDisabled = true');
$rootScope.$apply('blue = false');
checkbox.triggerHandler('click');
@@ -70,20 +83,20 @@ describe('mdCheckbox', function() {
}));
it('should preserve existing tabindex', inject(function($compile, $rootScope) {
- var element = $compile('
' +
+ var element = buildInstance('
' +
'' +
'' +
- '
')($rootScope);
+ '
');
var checkbox = element.find('md-checkbox');
expect(checkbox.attr('tabindex')).toBe('2');
}));
it('should disable with tabindex=-1', inject(function($compile, $rootScope) {
- var element = $compile('
' +
+ var element = buildInstance('
' +
'' +
'' +
- '
')($rootScope);
+ '
');
var checkbox = element.find('md-checkbox');
@@ -95,15 +108,15 @@ describe('mdCheckbox', function() {
}));
it('should not set focus state on mousedown', inject(function($compile, $rootScope) {
- var checkbox = $compile('
')($rootScope.$new());
- $rootScope.$apply();
+ var checkbox = buildInstance('',$rootScope.$new());
+
checkbox.triggerHandler('mousedown');
expect(checkbox[0]).not.toHaveClass('md-focused');
}));
it('should set focus state on focus and remove on blur', inject(function($compile, $rootScope) {
- var checkbox = $compile('')($rootScope.$new());
- $rootScope.$apply();
+ var checkbox = buildInstance('',$rootScope.$new());
+
checkbox.triggerHandler('focus');
expect(checkbox[0]).toHaveClass('md-focused');
checkbox.triggerHandler('blur');
@@ -111,8 +124,8 @@ describe('mdCheckbox', function() {
}));
it('should set focus state on keyboard interaction after clicking', inject(function($compile, $rootScope, $mdConstant) {
- var checkbox = $compile('')($rootScope.$new());
- $rootScope.$apply();
+ var checkbox = buildInstance('',$rootScope.$new());
+
checkbox.triggerHandler('mousedown');
checkbox.triggerHandler({
type: 'keypress',
@@ -123,89 +136,76 @@ describe('mdCheckbox', function() {
describe('ng core checkbox tests', function() {
- var inputElm;
- var scope;
- var $compile;
-
- beforeEach(inject(function(_$compile_, _$rootScope_) {
- scope = _$rootScope_;
- $compile = _$compile_;
- }));
-
- function compileInput(html) {
- inputElm = $compile(html)(scope);
- }
-
function isChecked(cbEl) {
return cbEl.hasClass(CHECKED_CSS);
}
it('should format booleans', function() {
- compileInput('');
+ var inputElm = buildInstance('');
- scope.$apply("name = false");
+ $rootScope.$apply("name = false");
expect(isChecked(inputElm)).toBe(false);
- scope.$apply("name = true");
+ $rootScope.$apply("name = true");
expect(isChecked(inputElm)).toBe(true);
});
it('should support type="checkbox" with non-standard capitalization', function() {
- compileInput('');
+ var inputElm = buildInstance('');
inputElm.triggerHandler('click');
- expect(scope.checkbox).toBe(true);
+ expect($rootScope.checkbox).toBe(true);
inputElm.triggerHandler('click');
- expect(scope.checkbox).toBe(false);
+ expect($rootScope.checkbox).toBe(false);
});
it('should allow custom enumeration', function() {
- compileInput('');
- scope.$apply("name = 'y'");
+ $rootScope.$apply("name = 'y'");
expect(isChecked(inputElm)).toBe(true);
- scope.$apply("name = 'n'");
+ $rootScope.$apply("name = 'n'");
expect(isChecked(inputElm)).toBe(false);
- scope.$apply("name = 'something else'");
+ $rootScope.$apply("name = 'something else'");
expect(isChecked(inputElm)).toBe(false);
inputElm.triggerHandler('click');
- expect(scope.name).toEqual('y');
+ expect($rootScope.name).toEqual('y');
inputElm.triggerHandler('click');
- expect(scope.name).toEqual('n');
+ expect($rootScope.name).toEqual('n');
});
it('should throw if ngTrueValue is present and not a constant expression', function() {
expect(function() {
- compileInput('');
+ buildInstance('');
}).toThrow();
});
it('should throw if ngFalseValue is present and not a constant expression', function() {
expect(function() {
- compileInput('');
+ buildInstance('');
}).toThrow();
});
it('should not throw if ngTrueValue or ngFalseValue are not present', function() {
expect(function() {
- compileInput('');
+ buildInstance('');
}).not.toThrow();
});
it('should be required if false', function() {
- compileInput('');
+ var inputElm = buildInstance('');
inputElm.triggerHandler('click');
expect(isChecked(inputElm)).toBe(true);
diff --git a/src/components/dialog/dialog.spec.js b/src/components/dialog/dialog.spec.js
index 0a8af4f05e2..078f3e82f63 100644
--- a/src/components/dialog/dialog.spec.js
+++ b/src/components/dialog/dialog.spec.js
@@ -485,7 +485,7 @@ describe('$mdDialog', function() {
expect(dialog.attr('role')).toBe('dialog');
}));
- it('should create an ARIA label if one is missing', inject(function($mdDialog, $rootScope) {
+ it('should create an ARIA label if one is missing', inject(function($mdDialog, $rootScope, $$rAF) {
var template = 'Hello';
var parent = angular.element('');
@@ -497,6 +497,7 @@ describe('$mdDialog', function() {
$rootScope.$apply();
angular.element(parent[0].querySelector('.md-dialog-container')).triggerHandler('transitionend');
$rootScope.$apply();
+ $$rAF.flush();
var dialog = angular.element(parent[0].querySelector('md-dialog'));
expect(dialog.attr('aria-label')).toEqual(dialog.text());
diff --git a/src/components/select/select.spec.js b/src/components/select/select.spec.js
index c29160c502d..8a7d98bba88 100755
--- a/src/components/select/select.spec.js
+++ b/src/components/select/select.spec.js
@@ -2,11 +2,9 @@ describe('
', function() {
beforeEach(module('material.components.select', 'ngAnimateMock'));
- beforeEach(inject(function($mdUtil, $q) {
+ beforeEach(inject(function($mdUtil, $$q) {
$mdUtil.transitionEndPromise = function() {
- var deferred = $q.defer();
- deferred.resolve();
- return deferred.promise;
+ return $$q.when(true);
};
}));
@@ -68,12 +66,10 @@ describe('', function() {
function pressKey(el, code) {
- inject(function($rootScope, $animate, $timeout) {
el.triggerHandler({
type: 'keydown',
keyCode: code
});
- });
}
function waitForSelectOpen() {
@@ -86,10 +82,13 @@ describe('', function() {
}
function waitForSelectClose() {
- inject(function($rootScope, $animate) {
- $rootScope.$digest();
- $animate.triggerCallbacks();
- });
+ try {
+ inject(function($rootScope, $animate ) {
+ $rootScope.$apply();
+ $animate.triggerCallbacks();
+
+ });
+ } catch(e) { }
}
it('should preserve tabindex', inject(function($document) {
@@ -105,25 +104,25 @@ describe('', function() {
it('supports disabled state', inject(function($document) {
var select = setupSelect('disabled="disabled", ng-model="val"');
openSelect(select);
- waitForSelectOpen();
expect($document.find('md-select-menu').length).toBe(0);
expect(select.attr('aria-disabled')).toBe('true');
}));
- it('closes the menu if the element is destroyed', inject(function($document) {
+ xit('closes the menu if the element is destroyed', inject(function($document, $rootScope) {
var select = setupSelect('ng-model="val"');
+
openSelect(select);
- waitForSelectOpen();
expect($document.find('md-select-menu').length).toBe(1);
+
select.scope().$destroy();
waitForSelectClose();
+
expect($document.find('md-select-menu').length).toBe(0);
}));
it('restores focus to select when the menu is closed', inject(function($document) {
var select = setupSelect('ng-model="val"');
openSelect(select);
- waitForSelectOpen();
$document[0].body.appendChild(select[0]);
@@ -301,7 +300,7 @@ describe('', function() {
var selectEl = setupSelect('ng-model="myModel", ng-change="changed()"', [1, 2, 3]);
openSelect(selectEl);
- waitForSelectOpen();
+
var menuEl = $document.find('md-select-menu');
menuEl.triggerHandler({
type: 'click',
diff --git a/src/components/switch/switch.js b/src/components/switch/switch.js
index ae7cc150492..2a2ddfb4073 100644
--- a/src/components/switch/switch.js
+++ b/src/components/switch/switch.js
@@ -52,6 +52,7 @@ function MdSwitch(mdCheckboxDirective, $mdTheming, $mdUtil, $document, $mdConsta
return {
restrict: 'E',
+ priority:210, // Run before ngAria
transclude: true,
template:
'' +
diff --git a/src/components/switch/switch.spec.js b/src/components/switch/switch.spec.js
index f382f8aa810..eb39b0b43c2 100644
--- a/src/components/switch/switch.spec.js
+++ b/src/components/switch/switch.spec.js
@@ -19,11 +19,13 @@ describe('
', function() {
var switches = angular.element(element[0].querySelectorAll('md-switch'));
expect(switches.eq(0).hasClass(CHECKED_CSS)).toEqual(false);
- expect(switches.eq(1).hasClass(CHECKED_CSS)).toEqual(true);
expect(switches.eq(0).attr('aria-checked')).toEqual('false');
- expect(switches.eq(1).attr('aria-checked')).toEqual('true');
expect(switches.eq(0).attr('role')).toEqual('checkbox');
+ expect(switches.eq(1).hasClass(CHECKED_CSS)).toEqual(true);
+ expect(switches.eq(1).attr('aria-checked')).toEqual('true');
+ expect(switches.eq(1).attr('role')).toEqual('checkbox');
+
$rootScope.$apply(function(){
$rootScope.blue = true;
$rootScope.green = false;
diff --git a/src/components/toolbar/toolbar.spec.js b/src/components/toolbar/toolbar.spec.js
index 3ba73ed1c26..6ab9b334e57 100644
--- a/src/components/toolbar/toolbar.spec.js
+++ b/src/components/toolbar/toolbar.spec.js
@@ -2,7 +2,7 @@ describe('', function() {
beforeEach(module('material.components.toolbar'));
- it('with scrollShrink, it should shrink scrollbar when going to bottom', inject(function($compile, $rootScope, $mdConstant, mdToolbarDirective) {
+ it('with scrollShrink, it should shrink scrollbar when going to bottom', inject(function($compile, $rootScope, $mdConstant, mdToolbarDirective, $$rAF) {
var parent = angular.element('');
var toolbar = angular.element('
');
@@ -33,7 +33,10 @@ describe('', function() {
mdShrinkSpeedFactor: 1
});
+ $rootScope.$apply();
$rootScope.$broadcast('$mdContentLoaded', contentEl);
+ $$rAF.flush();
+
//Expect everything to be in its proper initial state.
expect(toolbarCss[$mdConstant.CSS.TRANSFORM]).toEqual('translate3d(0,0px,0)');
@@ -45,6 +48,7 @@ describe('', function() {
type: 'scroll',
target: { scrollTop: 500 }
});
+ $$rAF.flush();
expect(toolbarCss[$mdConstant.CSS.TRANSFORM]).toEqual('translate3d(0,-100px,0)');
expect(contentCss[$mdConstant.CSS.TRANSFORM]).toEqual('translate3d(0,0px,0)');
@@ -54,6 +58,7 @@ describe('', function() {
type: 'scroll',
target: { scrollTop: 0 }
});
+ $$rAF.flush();
expect(toolbarCss[$mdConstant.CSS.TRANSFORM]).toEqual('translate3d(0,0px,0)');
expect(contentCss[$mdConstant.CSS.TRANSFORM]).toEqual('translate3d(0,100px,0)');
diff --git a/src/components/tooltip/tooltip.js b/src/components/tooltip/tooltip.js
index bd60edff4f3..ca0887e4a23 100644
--- a/src/components/tooltip/tooltip.js
+++ b/src/components/tooltip/tooltip.js
@@ -41,6 +41,7 @@ function MdTooltipDirective($timeout, $window, $$rAF, $document, $mdUtil, $mdThe
return {
restrict: 'E',
transclude: true,
+ priority:210, // Before ngAria
template: '\
\
',
diff --git a/src/components/tooltip/tooltip.spec.js b/src/components/tooltip/tooltip.spec.js
index cc2ca537b5c..cd767eaeb63 100644
--- a/src/components/tooltip/tooltip.spec.js
+++ b/src/components/tooltip/tooltip.spec.js
@@ -1,12 +1,12 @@
describe(' directive', function() {
- beforeEach(module('material.components.tooltip'));
+ beforeEach(module('material.components.tooltip', 'ngAnimateMock'));
function findTooltip() {
return angular.element(document.body).find('md-tooltip');
}
- it('should show and hide when visible is set', inject(function($compile, $rootScope, $timeout) {
+ it('should show and hide when visible is set', inject(function($compile, $rootScope, $animate) {
var element = $compile('' +
'Hello' +
'Tooltip' +
@@ -16,26 +16,27 @@ describe(' directive', function() {
expect(findTooltip().length).toBe(0);
$rootScope.$apply('isVisible = true');
+ $animate.triggerCallbacks();
expect(findTooltip().length).toBe(1);
expect(findTooltip().hasClass('md-show')).toBe(true);
$rootScope.$apply('isVisible = false');
- expect(findTooltip().hasClass('md-show')).toBe(false);
- $timeout.flush();
+ $animate.triggerCallbacks();
expect(findTooltip().length).toBe(0);
}));
- it('should describe parent', inject(function($compile, $rootScope, $timeout) {
+ it('should describe parent', inject(function($compile, $rootScope, $animate) {
var element = $compile('' +
'Hello' +
'Tooltip' +
'')($rootScope);
$rootScope.$apply('isVisible = true');
+ $animate.triggerCallbacks();
expect(element.attr('aria-describedby')).toEqual(findTooltip().attr('id'));
- $rootScope.$apply('isVisible = false');
+ $rootScope.$apply('isVisible = false'); $animate.triggerCallbacks();
expect(element.attr('aria-describedby')).toBeFalsy();
}));
@@ -108,22 +109,29 @@ describe(' directive', function() {
expect($rootScope.isVisible).toBe(false);
}));
- it('should show after tooltipDelay ms', inject(function($compile, $rootScope, $timeout) {
+ it('should show after tooltipDelay ms', inject(function($compile, $rootScope, $timeout, $animate) {
var element = $compile('' +
'Hello' +
'' +
'Tooltip' +
'' +
'')($rootScope);
+
+ $rootScope.$apply();
+ $animate.triggerCallbacks();
+
element.triggerHandler('focus');
expect($rootScope.isVisible).toBeFalsy();
+
// Wait 1 below delay, nothing should happen
$timeout.flush(98);
expect($rootScope.isVisible).toBeFalsy();
+
// Total 99 == tooltipDelay
$timeout.flush(1);
expect($rootScope.isVisible).toBe(true);
+
}));
});
diff --git a/src/core/services/aria/aria.js b/src/core/services/aria/aria.js
index eb67f04da7f..6c8dcd419d2 100644
--- a/src/core/services/aria/aria.js
+++ b/src/core/services/aria/aria.js
@@ -4,6 +4,9 @@
angular.module('material.core')
.service('$mdAria', AriaService);
+/*
+ * @ngInject
+ */
function AriaService($$rAF, $log, $window) {
return {
diff --git a/src/core/services/gesture/gesture.spec.js b/src/core/services/gesture/gesture.spec.js
index 903e2cc4ff3..1a53825abcb 100644
--- a/src/core/services/gesture/gesture.spec.js
+++ b/src/core/services/gesture/gesture.spec.js
@@ -355,6 +355,7 @@ describe('$mdGesture', function() {
describe('drag', function() {
var startDragSpy, el, dragSpy, endDragSpy, doc;
+
beforeEach(function() {
inject(function($mdGesture, $document) {
doc = $document;
@@ -415,13 +416,7 @@ describe('$mdGesture', function() {
touches: [{pageX: 90, pageY: 99}]
});
expect(startDragSpy).not.toHaveBeenCalled();
- expect(dragSpy).toHaveBeenCalled();
- expect(dragSpy.calls.mostRecent().args[0].pointer).toHaveFields({
- x: 90,
- y: 99,
- distanceX: 1,
- distanceY: -1
- });
+ expect(dragSpy).not.toHaveBeenCalled();
expect(endDragSpy).not.toHaveBeenCalled();
dragSpy.calls.reset();