fix(ngAria): Apply ARIA attrs correctly
BREAKING CHANGE: Where appropriate, ngAria now applies ARIA to custom controls only, not native inputs. Because of this, support for `aria-multiline` on textareas has been removed. New support added for ngValue, ngChecked, and ngRequired, along with updated documentation. Closes #13078 Closes #11374 Closes #11830 Closes #13483
This commit is contained in:
committed by
Georgios Kalpakas
parent
34ec0d9629
commit
d06431e530
@@ -33,6 +33,9 @@ Currently, ngAria interfaces with the following directives:
|
||||
|
||||
* {@link guide/accessibility#ngmodel ngModel}
|
||||
* {@link guide/accessibility#ngdisabled ngDisabled}
|
||||
* {@link guide/accessibility#ngrequired ngRequired}
|
||||
* {@link guide/accessibility#ngvaluechecked ngChecked}
|
||||
* {@link guide/accessibility#ngvaluechecked ngValue}
|
||||
* {@link guide/accessibility#ngshow ngShow}
|
||||
* {@link guide/accessibility#nghide ngHide}
|
||||
* {@link guide/accessibility#ngclick ngClick}
|
||||
@@ -137,6 +140,27 @@ the keyboard. It is still up to **you** as a developer to **ensure custom contro
|
||||
accessible**. As a rule, any time you create a widget involving user interaction, be sure to test
|
||||
it with your keyboard and at least one mobile and desktop screen reader.
|
||||
|
||||
<h2 id="ngvaluechecked">ngValue and ngChecked</h2>
|
||||
|
||||
To ease the transition between native inputs and custom controls, ngAria now supports
|
||||
{@link ng/directive/ngValue ngValue} and {@link ng/directive/ngChecked ngChecked}.
|
||||
The original directives were created for native inputs only, so ngAria extends
|
||||
support to custom elements by managing `aria-checked` for accessibility.
|
||||
|
||||
###Example
|
||||
|
||||
```html
|
||||
<custom-checkbox ng-checked="val"></custom-checkbox>
|
||||
<custom-radio-button ng-value="val"></custom-radio-button>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<custom-checkbox ng-checked="val" aria-checked="true"></custom-checkbox>
|
||||
<custom-radio-button ng-value="val" aria-checked="true"></custom-radio-button>
|
||||
```
|
||||
|
||||
<h2 id="ngdisabled">ngDisabled</h2>
|
||||
|
||||
The `disabled` attribute is only valid for certain elements such as `button`, `input` and
|
||||
@@ -148,18 +172,37 @@ custom controls to be more accessible.
|
||||
###Example
|
||||
|
||||
```html
|
||||
<md-checkbox ng-disabled="disabled">
|
||||
<md-checkbox ng-disabled="disabled"></md-checkbox>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<md-checkbox disabled aria-disabled="true">
|
||||
<md-checkbox disabled aria-disabled="true"></md-checkbox>
|
||||
```
|
||||
|
||||
>You can check whether a control is legitimately disabled for a screen reader by visiting
|
||||
[chrome://accessibility](chrome://accessibility) and inspecting [the accessibility tree](http://www.paciellogroup.com/blog/2015/01/the-browser-accessibility-tree/).
|
||||
|
||||
<h2 id="ngrequired">ngRequired</h2>
|
||||
|
||||
The boolean `required` attribute is only valid for native form controls such as `input` and
|
||||
`textarea`. To properly indicate custom element directives such as `<md-checkbox>` or `<custom-input>`
|
||||
as required, using ngAria with {@link ng/directive/ngRequired ngRequired} will also add
|
||||
`aria-required`. This tells accessibility APIs when a custom control is required.
|
||||
|
||||
###Example
|
||||
|
||||
```html
|
||||
<md-checkbox ng-required="val"></md-checkbox>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<md-checkbox ng-required="val" aria-required="true"></md-checkbox>
|
||||
```
|
||||
|
||||
<h2 id="ngshow">ngShow</h2>
|
||||
|
||||
>The {@link ng/directive/ngShow} directive shows or hides the
|
||||
|
||||
+49
-49
@@ -16,19 +16,23 @@
|
||||
*
|
||||
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
|
||||
* directives are supported:
|
||||
* `ngModel`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, `ngDblClick`, and `ngMessages`.
|
||||
* `ngModel`, `ngChecked`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`,
|
||||
* `ngDblClick`, and `ngMessages`.
|
||||
*
|
||||
* Below is a more detailed breakdown of the attributes handled by ngAria:
|
||||
*
|
||||
* | Directive | Supported Attributes |
|
||||
* |---------------------------------------------|----------------------------------------------------------------------------------------|
|
||||
* | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
|
||||
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
|
||||
* | {@link ng.directive:ngRequired ngRequired} | aria-required |
|
||||
* | {@link ng.directive:ngChecked ngChecked} | aria-checked |
|
||||
* | {@link ng.directive:ngValue ngValue} | aria-checked |
|
||||
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
|
||||
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
|
||||
* | {@link ng.directive:ngDblclick ngDblclick} | tabindex |
|
||||
* | {@link module:ngMessages ngMessages} | aria-live |
|
||||
* | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
|
||||
* | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role |
|
||||
* | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role |
|
||||
*
|
||||
* Find out more information about each directive by reading the
|
||||
* {@link guide/accessibility ngAria Developer Guide}.
|
||||
@@ -90,7 +94,6 @@ function $AriaProvider() {
|
||||
ariaDisabled: true,
|
||||
ariaRequired: true,
|
||||
ariaInvalid: true,
|
||||
ariaMultiline: true,
|
||||
ariaValue: true,
|
||||
tabindex: true,
|
||||
bindKeypress: true,
|
||||
@@ -108,11 +111,10 @@ function $AriaProvider() {
|
||||
* - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags
|
||||
* - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags
|
||||
* - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags
|
||||
* - **ariaMultiline** – `{boolean}` – Enables/disables aria-multiline tags
|
||||
* - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
|
||||
* - **tabindex** – `{boolean}` – Enables/disables tabindex tags
|
||||
* - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `<div>` and
|
||||
* `<li>` elements with ng-click
|
||||
* - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `div` and
|
||||
* `li` elements with ng-click
|
||||
* - **bindRoleForClick** – `{boolean}` – Adds role=button to non-interactive elements like `div`
|
||||
* using ng-click, making them more accessible to users of assistive technologies
|
||||
*
|
||||
@@ -151,15 +153,15 @@ function $AriaProvider() {
|
||||
*
|
||||
*```js
|
||||
* ngAriaModule.directive('ngDisabled', ['$aria', function($aria) {
|
||||
* return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
|
||||
* return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false);
|
||||
* }])
|
||||
*```
|
||||
* Shown above, the ngAria module creates a directive with the same signature as the
|
||||
* traditional `ng-disabled` directive. But this ngAria version is dedicated to
|
||||
* solely managing accessibility attributes. The internal `$aria` service is used to watch the
|
||||
* boolean attribute `ngDisabled`. If it has not been explicitly set by the developer,
|
||||
* `aria-disabled` is injected as an attribute with its value synchronized to the value in
|
||||
* `ngDisabled`.
|
||||
* solely managing accessibility attributes on custom elements. The internal `$aria` service is
|
||||
* used to watch the boolean attribute `ngDisabled`. If it has not been explicitly set by the
|
||||
* developer, `aria-disabled` is injected as an attribute with its value synchronized to the
|
||||
* value in `ngDisabled`.
|
||||
*
|
||||
* Because ngAria hooks into the `ng-disabled` directive, developers do not have to do
|
||||
* anything to enable this feature. The `aria-disabled` attribute is automatically managed
|
||||
@@ -167,12 +169,15 @@ function $AriaProvider() {
|
||||
*
|
||||
* The full list of directives that interface with ngAria:
|
||||
* * **ngModel**
|
||||
* * **ngChecked**
|
||||
* * **ngRequired**
|
||||
* * **ngDisabled**
|
||||
* * **ngValue**
|
||||
* * **ngShow**
|
||||
* * **ngHide**
|
||||
* * **ngClick**
|
||||
* * **ngDblclick**
|
||||
* * **ngMessages**
|
||||
* * **ngDisabled**
|
||||
*
|
||||
* Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each
|
||||
* directive.
|
||||
@@ -198,13 +203,25 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
.directive('ngHide', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false);
|
||||
}])
|
||||
.directive('ngModel', ['$aria', function($aria) {
|
||||
.directive('ngValue', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngValue', 'aria-checked', nodeBlackList, false);
|
||||
}])
|
||||
.directive('ngChecked', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false);
|
||||
}])
|
||||
.directive('ngRequired', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false);
|
||||
}])
|
||||
.directive('ngModel', ['$aria', '$parse', function($aria, $parse) {
|
||||
|
||||
function shouldAttachAttr(attr, normalizedAttr, elem) {
|
||||
return $aria.config(normalizedAttr) && !elem.attr(attr);
|
||||
function shouldAttachAttr(attr, normalizedAttr, elem, allowBlacklistEls) {
|
||||
return $aria.config(normalizedAttr) && !elem.attr(attr) && (allowBlacklistEls || !isNodeOneOf(elem, nodeBlackList));
|
||||
}
|
||||
|
||||
function shouldAttachRole(role, elem) {
|
||||
// if element does not have role attribute
|
||||
// AND element type is equal to role (if custom element has a type equaling shape) <-- remove?
|
||||
// AND element is not INPUT
|
||||
return !elem.attr('role') && (elem.attr('type') === role) && (elem[0].nodeName !== 'INPUT');
|
||||
}
|
||||
|
||||
@@ -214,8 +231,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
|
||||
return ((type || role) === 'checkbox' || role === 'menuitemcheckbox') ? 'checkbox' :
|
||||
((type || role) === 'radio' || role === 'menuitemradio') ? 'radio' :
|
||||
(type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' :
|
||||
(type || role) === 'textbox' || elem[0].nodeName === 'TEXTAREA' ? 'multiline' : '';
|
||||
(type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' : '';
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -227,7 +243,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
|
||||
return {
|
||||
pre: function(scope, elem, attr, ngModel) {
|
||||
if (shape === 'checkbox' && attr.type !== 'checkbox') {
|
||||
if (shape === 'checkbox') {
|
||||
//Use the input[checkbox] $isEmpty implementation for elements with checkbox roles
|
||||
ngModel.$isEmpty = function(value) {
|
||||
return value === false;
|
||||
@@ -235,29 +251,18 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}
|
||||
},
|
||||
post: function(scope, elem, attr, ngModel) {
|
||||
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem)
|
||||
&& !isNodeOneOf(elem, nodeBlackList);
|
||||
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, false);
|
||||
|
||||
function ngAriaWatchModelValue() {
|
||||
return ngModel.$modelValue;
|
||||
}
|
||||
|
||||
function getRadioReaction() {
|
||||
if (needsTabIndex) {
|
||||
needsTabIndex = false;
|
||||
return function ngAriaRadioReaction(newVal) {
|
||||
var boolVal = (attr.value == ngModel.$viewValue);
|
||||
elem.attr('aria-checked', boolVal);
|
||||
elem.attr('tabindex', 0 - !boolVal);
|
||||
};
|
||||
} else {
|
||||
return function ngAriaRadioReaction(newVal) {
|
||||
elem.attr('aria-checked', (attr.value == ngModel.$viewValue));
|
||||
};
|
||||
}
|
||||
function getRadioReaction(newVal) {
|
||||
var boolVal = (attr.value == ngModel.$viewValue);
|
||||
elem.attr('aria-checked', boolVal);
|
||||
}
|
||||
|
||||
function ngAriaCheckboxReaction() {
|
||||
function getCheckboxReaction() {
|
||||
elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue));
|
||||
}
|
||||
|
||||
@@ -267,9 +272,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
if (shouldAttachRole(shape, elem)) {
|
||||
elem.attr('role', shape);
|
||||
}
|
||||
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
|
||||
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem, false)) {
|
||||
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
|
||||
getRadioReaction() : ngAriaCheckboxReaction);
|
||||
getRadioReaction : getCheckboxReaction);
|
||||
}
|
||||
if (needsTabIndex) {
|
||||
elem.attr('tabindex', 0);
|
||||
@@ -306,22 +311,17 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
break;
|
||||
case 'multiline':
|
||||
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
|
||||
elem.attr('aria-multiline', true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
|
||||
scope.$watch(function ngAriaRequiredWatch() {
|
||||
return ngModel.$error.required;
|
||||
}, function ngAriaRequiredReaction(newVal) {
|
||||
elem.attr('aria-required', !!newVal);
|
||||
if (!attr.hasOwnProperty('ngRequired') && ngModel.$validators.required
|
||||
&& shouldAttachAttr('aria-required', 'ariaRequired', elem, false)) {
|
||||
// ngModel.$error.required is undefined on custom controls
|
||||
attr.$observe('required', function() {
|
||||
elem.attr('aria-required', !!attr['required']);
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) {
|
||||
if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem, true)) {
|
||||
scope.$watch(function ngAriaInvalidWatch() {
|
||||
return ngModel.$invalid;
|
||||
}, function ngAriaInvalidReaction(newVal) {
|
||||
@@ -334,7 +334,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
};
|
||||
}])
|
||||
.directive('ngDisabled', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngDisabled', 'aria-disabled', []);
|
||||
return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false);
|
||||
}])
|
||||
.directive('ngMessages', function() {
|
||||
return {
|
||||
|
||||
+88
-169
@@ -104,9 +104,19 @@ describe('$aria', function() {
|
||||
describe('aria-checked', function() {
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
it('should attach itself to input type="checkbox"', function() {
|
||||
it('should not attach itself to native input type="checkbox"', function() {
|
||||
compileElement('<input type="checkbox" ng-model="val">');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element.attr('aria-checked')).toBeUndefined();
|
||||
|
||||
scope.$apply('val = false');
|
||||
expect(element.attr('aria-checked')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should attach itself to custom checkbox', function() {
|
||||
compileElement('<div role="checkbox" ng-model="val">');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element.attr('aria-checked')).toBe('true');
|
||||
|
||||
@@ -114,31 +124,42 @@ describe('$aria', function() {
|
||||
expect(element.attr('aria-checked')).toBe('false');
|
||||
});
|
||||
|
||||
it('should handle checkbox with string model values using ng(True|False)Value', function() {
|
||||
var element = $compile('<input type="checkbox" ng-model="val" ng-true-value="\'yes\'" ' +
|
||||
'ng-false-value="\'no\'">'
|
||||
)(scope);
|
||||
it('should not handle native checkbox with ngChecked', function() {
|
||||
var element = $compile('<input type="checkbox" ng-checked="val">')(scope);
|
||||
|
||||
scope.$apply('val="yes"');
|
||||
scope.$apply('val = true');
|
||||
expect(element.attr('aria-checked')).toBeUndefined();
|
||||
|
||||
scope.$apply('val = false');
|
||||
expect(element.attr('aria-checked')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle custom checkbox with ngChecked', function() {
|
||||
var element = $compile('<div role="checkbox" ng-checked="val">')(scope);
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('true');
|
||||
|
||||
scope.$apply('val="no"');
|
||||
scope.$apply('val = false');
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('false');
|
||||
});
|
||||
|
||||
it('should handle checkbox with integer model values using ngTrueValue', function() {
|
||||
var element = $compile('<input type="checkbox" ng-model="val" ng-true-value="0">')(scope);
|
||||
|
||||
scope.$apply('val=0');
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('true');
|
||||
|
||||
scope.$apply('val=1');
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('false');
|
||||
});
|
||||
|
||||
it('should attach itself to input type="radio"', function() {
|
||||
it('should not attach to native input type="radio"', function() {
|
||||
var element = $compile('<input type="radio" ng-model="val" value="one">' +
|
||||
'<input type="radio" ng-model="val" value="two">')(scope);
|
||||
'<input type="radio" ng-model="val" value="two">')(scope);
|
||||
|
||||
scope.$apply("val='one'");
|
||||
expect(element.eq(0).attr('aria-checked')).toBeUndefined();
|
||||
expect(element.eq(1).attr('aria-checked')).toBeUndefined();
|
||||
|
||||
scope.$apply("val='two'");
|
||||
expect(element.eq(0).attr('aria-checked')).toBeUndefined();
|
||||
expect(element.eq(1).attr('aria-checked')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should attach to custom radio controls', function() {
|
||||
var element = $compile('<div role="radio" ng-model="val" value="one"></div>' +
|
||||
'<div role="radio" ng-model="val" value="two"></div>')(scope);
|
||||
|
||||
scope.$apply("val='one'");
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('true');
|
||||
@@ -149,22 +170,22 @@ describe('$aria', function() {
|
||||
expect(element.eq(1).attr('aria-checked')).toBe('true');
|
||||
});
|
||||
|
||||
it('should handle radios with integer model values', function() {
|
||||
var element = $compile('<input type="radio" ng-model="val" value="0">' +
|
||||
'<input type="radio" ng-model="val" value="1">')(scope);
|
||||
it('should handle custom radios with integer model values', function() {
|
||||
var element = $compile('<div role="radio" ng-model="val" value="0"></div>' +
|
||||
'<div role="radio" ng-model="val" value="1"></div>')(scope);
|
||||
|
||||
scope.$apply('val=0');
|
||||
scope.$apply("val=0");
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('true');
|
||||
expect(element.eq(1).attr('aria-checked')).toBe('false');
|
||||
|
||||
scope.$apply('val=1');
|
||||
scope.$apply("val=1");
|
||||
expect(element.eq(0).attr('aria-checked')).toBe('false');
|
||||
expect(element.eq(1).attr('aria-checked')).toBe('true');
|
||||
});
|
||||
|
||||
it('should handle radios with boolean model values using ngValue', function() {
|
||||
var element = $compile('<input type="radio" ng-model="val" ng-value="valExp">' +
|
||||
'<input type="radio" ng-model="val" ng-value="valExp2">')(scope);
|
||||
var element = $compile('<div role="radio" ng-model="val" ng-value="valExp"></div>' +
|
||||
'<div role="radio" ng-model="val" ng-value="valExp2"></div>')(scope);
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.valExp = true;
|
||||
@@ -179,24 +200,6 @@ describe('$aria', function() {
|
||||
expect(element.eq(1).attr('aria-checked')).toBe('true');
|
||||
});
|
||||
|
||||
it('should attach itself to role="radio"', function() {
|
||||
scope.val = 'one';
|
||||
compileElement('<div role="radio" ng-model="val" value="one"></div>');
|
||||
expect(element.attr('aria-checked')).toBe('true');
|
||||
|
||||
scope.$apply("val = 'two'");
|
||||
expect(element.attr('aria-checked')).toBe('false');
|
||||
});
|
||||
|
||||
it('should attach itself to role="checkbox"', function() {
|
||||
scope.val = true;
|
||||
compileElement('<div role="checkbox" ng-model="val"></div>');
|
||||
expect(element.attr('aria-checked')).toBe('true');
|
||||
|
||||
scope.$apply('val = false');
|
||||
expect(element.attr('aria-checked')).toBe('false');
|
||||
});
|
||||
|
||||
it('should attach itself to role="menuitemradio"', function() {
|
||||
scope.val = 'one';
|
||||
compileElement('<div role="menuitemradio" ng-model="val" value="one"></div>');
|
||||
@@ -217,8 +220,6 @@ describe('$aria', function() {
|
||||
|
||||
it('should not attach itself if an aria-checked value is already present', function() {
|
||||
var element = [
|
||||
$compile("<input type='checkbox' ng-model='val1' aria-checked='userSetValue'>")(scope),
|
||||
$compile("<input type='radio' ng-model='val2' value='one' aria-checked='userSetValue'><input type='radio' ng-model='val2' value='two'>")(scope),
|
||||
$compile("<div role='radio' ng-model='val' value='{{val3}}' aria-checked='userSetValue'></div>")(scope),
|
||||
$compile("<div role='menuitemradio' ng-model='val' value='{{val3}}' aria-checked='userSetValue'></div>")(scope),
|
||||
$compile("<div role='checkbox' checked='checked' aria-checked='userSetValue'></div>")(scope),
|
||||
@@ -297,52 +298,32 @@ describe('$aria', function() {
|
||||
describe('aria-disabled', function() {
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
it('should attach itself to input elements', function() {
|
||||
scope.$apply('val = false');
|
||||
compileElement("<input ng-disabled='val'>");
|
||||
expect(element.attr('aria-disabled')).toBe('false');
|
||||
|
||||
they('should not attach itself to native $prop controls', {
|
||||
input: '<input ng-disabled="val">',
|
||||
textarea: '<textarea ng-disabled="val"></textarea>',
|
||||
select: '<select ng-disabled="val"></select>',
|
||||
button: '<button ng-disabled="val"></button>'
|
||||
}, function(tmpl) {
|
||||
var element = $compile(tmpl)(scope);
|
||||
scope.$apply('val = true');
|
||||
expect(element.attr('aria-disabled')).toBe('true');
|
||||
|
||||
expect(element.attr('disabled')).toBeDefined();
|
||||
expect(element.attr('aria-disabled')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should attach itself to textarea elements', function() {
|
||||
scope.$apply('val = false');
|
||||
compileElement('<textarea ng-disabled="val"></textarea>');
|
||||
it('should attach itself to custom controls', function() {
|
||||
compileElement('<div ng-disabled="val"></div>');
|
||||
expect(element.attr('aria-disabled')).toBe('false');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element.attr('aria-disabled')).toBe('true');
|
||||
});
|
||||
|
||||
it('should attach itself to button elements', function() {
|
||||
scope.$apply('val = false');
|
||||
compileElement('<button ng-disabled="val"></button>');
|
||||
expect(element.attr('aria-disabled')).toBe('false');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element.attr('aria-disabled')).toBe('true');
|
||||
});
|
||||
|
||||
it('should attach itself to select elements', function() {
|
||||
scope.$apply('val = false');
|
||||
compileElement('<select ng-disabled="val"></select>');
|
||||
expect(element.attr('aria-disabled')).toBe('false');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expect(element.attr('aria-disabled')).toBe('true');
|
||||
});
|
||||
|
||||
it('should not attach itself if an aria-disabled attribute is already present', function() {
|
||||
var element = [
|
||||
$compile("<input aria-disabled='userSetValue' ng-disabled='val'>")(scope),
|
||||
$compile("<textarea aria-disabled='userSetValue' ng-disabled='val'></textarea>")(scope),
|
||||
$compile("<button aria-disabled='userSetValue' ng-disabled='val'></button>")(scope),
|
||||
$compile("<select aria-disabled='userSetValue' ng-disabled='val'></select>")(scope)
|
||||
];
|
||||
compileElement('<div ng-disabled="val" aria-disabled="userSetValue"></div>');
|
||||
|
||||
scope.$apply('val = true');
|
||||
expectAriaAttrOnEachElement(element, 'aria-disabled', 'userSetValue');
|
||||
expect(element.attr('aria-disabled')).toBe('userSetValue');
|
||||
});
|
||||
|
||||
|
||||
@@ -367,15 +348,10 @@ describe('$aria', function() {
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
it('should not attach aria-disabled', function() {
|
||||
var element = [
|
||||
$compile("<input ng-disabled='val'>")(scope),
|
||||
$compile("<textarea ng-disabled='val'></textarea>")(scope),
|
||||
$compile("<button ng-disabled='val'></button>")(scope),
|
||||
$compile("<select ng-disabled='val'></select>")(scope)
|
||||
];
|
||||
compileElement('<div ng-disabled="val"></div>');
|
||||
|
||||
scope.$apply('val = false');
|
||||
expectAriaAttrOnEachElement(element, 'aria-disabled', undefined);
|
||||
scope.$apply('val = true');
|
||||
expect(element.attr('aria-disabled')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -391,6 +367,15 @@ describe('$aria', function() {
|
||||
expect(element.attr('aria-invalid')).toBe('false');
|
||||
});
|
||||
|
||||
it('should attach aria-invalid to custom controls', function() {
|
||||
compileElement('<div ng-model="txtInput" role="textbox" ng-minlength="10"></div>');
|
||||
scope.$apply("txtInput='LTten'");
|
||||
expect(element.attr('aria-invalid')).toBe('true');
|
||||
|
||||
scope.$apply("txtInput='morethantencharacters'");
|
||||
expect(element.attr('aria-invalid')).toBe('false');
|
||||
});
|
||||
|
||||
it('should not attach itself if aria-invalid is already present', function() {
|
||||
compileElement('<input ng-model="txtInput" ng-minlength="10" aria-invalid="userSetValue">');
|
||||
scope.$apply("txtInput='LTten'");
|
||||
@@ -414,49 +399,28 @@ describe('$aria', function() {
|
||||
describe('aria-required', function() {
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
it('should attach aria-required to input', function() {
|
||||
it('should not attach to input', function() {
|
||||
compileElement('<input ng-model="val" required>');
|
||||
expect(element.attr('aria-required')).toBe('true');
|
||||
expect(element.attr('aria-required')).toBeUndefined();
|
||||
});
|
||||
|
||||
scope.$apply("val='input is valid now'");
|
||||
it('should attach to custom controls with ngModel and required', function() {
|
||||
compileElement('<div ng-model="val" role="checkbox" required></div>');
|
||||
expect(element.attr('aria-required')).toBe('true');
|
||||
});
|
||||
|
||||
it('should set aria-required to false when ng-required is false', function() {
|
||||
compileElement("<div role='checkbox' ng-required='false' ng-model='val'></div>");
|
||||
expect(element.attr('aria-required')).toBe('false');
|
||||
});
|
||||
|
||||
it('should attach aria-required to textarea', function() {
|
||||
compileElement('<textarea ng-model="val" required></textarea>');
|
||||
it('should attach to custom controls with ngRequired', function() {
|
||||
compileElement('<div role="checkbox" ng-model="val" ng-required="true"></div>');
|
||||
expect(element.attr('aria-required')).toBe('true');
|
||||
|
||||
scope.$apply("val='input is valid now'");
|
||||
expect(element.attr('aria-required')).toBe('false');
|
||||
});
|
||||
|
||||
it('should attach aria-required to select', function() {
|
||||
compileElement('<select ng-model="val" required></select>');
|
||||
expect(element.attr('aria-required')).toBe('true');
|
||||
|
||||
scope.$apply("val='input is valid now'");
|
||||
expect(element.attr('aria-required')).toBe('false');
|
||||
});
|
||||
|
||||
it('should attach aria-required to ngRequired', function() {
|
||||
compileElement('<input ng-model="val" ng-required="true">');
|
||||
expect(element.attr('aria-required')).toBe('true');
|
||||
|
||||
scope.$apply("val='input is valid now'");
|
||||
expect(element.attr('aria-required')).toBe('false');
|
||||
});
|
||||
|
||||
it('should not attach itself if aria-required is already present', function() {
|
||||
compileElement("<input ng-model='val' required aria-required='userSetValue'>");
|
||||
expect(element.attr('aria-required')).toBe('userSetValue');
|
||||
|
||||
compileElement("<textarea ng-model='val' required aria-required='userSetValue'></textarea>");
|
||||
expect(element.attr('aria-required')).toBe('userSetValue');
|
||||
|
||||
compileElement("<select ng-model='val' required aria-required='userSetValue'></select>");
|
||||
expect(element.attr('aria-required')).toBe('userSetValue');
|
||||
|
||||
compileElement("<input ng-model='val' ng-required='true' aria-required='userSetValue'>");
|
||||
compileElement("<div role='checkbox' ng-model='val' ng-required='true' aria-required='userSetValue'></div>");
|
||||
expect(element.attr('aria-required')).toBe('userSetValue');
|
||||
});
|
||||
});
|
||||
@@ -471,50 +435,8 @@ describe('$aria', function() {
|
||||
compileElement("<input ng-model='val' required>");
|
||||
expect(element.attr('aria-required')).toBeUndefined();
|
||||
|
||||
compileElement("<textarea ng-model='val' required></textarea>");
|
||||
compileElement("<div ng-model='val' ng-required='true'></div>");
|
||||
expect(element.attr('aria-required')).toBeUndefined();
|
||||
|
||||
compileElement("<select ng-model='val' required></select>");
|
||||
expect(element.attr('aria-required')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('aria-multiline', function() {
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
it('should attach itself to textarea', function() {
|
||||
compileElement('<textarea ng-model="val"></textarea>');
|
||||
expect(element.attr('aria-multiline')).toBe('true');
|
||||
});
|
||||
|
||||
it('should attach itself role="textbox"', function() {
|
||||
compileElement('<div role="textbox" ng-model="val"></div>');
|
||||
expect(element.attr('aria-multiline')).toBe('true');
|
||||
});
|
||||
|
||||
it('should not attach itself if aria-multiline is already present', function() {
|
||||
compileElement('<textarea aria-multiline="userSetValue"></textarea>');
|
||||
expect(element.attr('aria-multiline')).toBe('userSetValue');
|
||||
|
||||
compileElement('<div role="textbox" aria-multiline="userSetValue"></div>');
|
||||
expect(element.attr('aria-multiline')).toBe('userSetValue');
|
||||
});
|
||||
});
|
||||
|
||||
describe('aria-multiline when disabled', function() {
|
||||
beforeEach(configAriaProvider({
|
||||
ariaMultiline: false
|
||||
}));
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
it('should not attach itself to textarea', function() {
|
||||
compileElement('<textarea></textarea>');
|
||||
expect(element.attr('aria-multiline')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not attach itself role="textbox"', function() {
|
||||
compileElement('<div role="textbox"></div>');
|
||||
expect(element.attr('aria-multiline')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -636,13 +558,10 @@ describe('$aria', function() {
|
||||
});
|
||||
|
||||
it('should attach tabindex to custom inputs', function() {
|
||||
compileElement('<div type="checkbox" ng-model="val"></div>');
|
||||
expect(element.attr('tabindex')).toBe('0');
|
||||
|
||||
compileElement('<div role="checkbox" ng-model="val"></div>');
|
||||
expect(element.attr('tabindex')).toBe('0');
|
||||
|
||||
compileElement('<div type="range" ng-model="val"></div>');
|
||||
compileElement('<div role="slider" ng-model="val"></div>');
|
||||
expect(element.attr('tabindex')).toBe('0');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user