From 0cdd7582f4927fa4e36866d07478c98c1e31f233 Mon Sep 17 00:00:00 2001 From: Marcy Sutton Date: Wed, 3 Dec 2014 16:13:41 -0800 Subject: [PATCH] feat(ngAria): Adds button role to ng-click Closes #9254 --- src/ngAria/aria.js | 7 +- test/ngAria/ariaSpec.js | 149 ++++++++++++++++++++++------------------ 2 files changed, 87 insertions(+), 69 deletions(-) diff --git a/src/ngAria/aria.js b/src/ngAria/aria.js index e70199c3e7e7..e605a4b5688e 100644 --- a/src/ngAria/aria.js +++ b/src/ngAria/aria.js @@ -22,13 +22,13 @@ * * | Directive | Supported Attributes | * |---------------------------------------------|----------------------------------------------------------------------------------------| - * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required | * | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled | * | {@link ng.directive:ngShow ngShow} | aria-hidden | * | {@link ng.directive:ngHide ngHide} | aria-hidden | - * | {@link ng.directive:ngClick ngClick} | tabindex, keypress event | * | {@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 | * * Find out more information about each directive by reading the * {@link guide/accessibility ngAria Developer Guide}. @@ -320,6 +320,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) { return true; } } + if (!elem.attr('role') && !isNodeOneOf(elem, ['BUTTON', 'A'])) { + elem.attr('role', 'button'); + } if ($aria.config('tabindex') && !elem.attr('tabindex')) { elem.attr('tabindex', 0); diff --git a/test/ngAria/ariaSpec.js b/test/ngAria/ariaSpec.js index 908e38b9d83d..f98cdcb8ea76 100644 --- a/test/ngAria/ariaSpec.js +++ b/test/ngAria/ariaSpec.js @@ -5,7 +5,7 @@ describe('$aria', function() { beforeEach(module('ngAria')); - afterEach(function(){ + afterEach(function() { dealoc(element); }); @@ -16,7 +16,7 @@ describe('$aria', function() { }); } - function compileInput(inputHtml) { + function compileElement(inputHtml) { element = $compile(inputHtml)(scope); scope.$digest(); } @@ -25,7 +25,7 @@ describe('$aria', function() { beforeEach(injectScopeAndCompiler); it('should attach aria-hidden to ng-show', function() { - compileInput('
'); + compileElement('
'); scope.$apply('val = false'); expect(element.attr('aria-hidden')).toBe('true'); @@ -34,7 +34,7 @@ describe('$aria', function() { }); it('should attach aria-hidden to ng-hide', function() { - compileInput('
'); + compileElement('
'); scope.$apply('val = false'); expect(element.attr('aria-hidden')).toBe('false'); @@ -43,7 +43,7 @@ describe('$aria', function() { }); it('should not change aria-hidden if it is already present on ng-show', function() { - compileInput('
'); + compileElement('
'); expect(element.attr('aria-hidden')).toBe('userSetValue'); scope.$apply('val = true'); @@ -51,7 +51,7 @@ describe('$aria', function() { }); it('should not change aria-hidden if it is already present on ng-hide', function() { - compileInput('
'); + compileElement('
'); expect(element.attr('aria-hidden')).toBe('userSetValue'); scope.$apply('val = true'); @@ -68,10 +68,10 @@ describe('$aria', function() { it('should not attach aria-hidden', function() { scope.$apply('val = false'); - compileInput('
'); + compileElement('
'); expect(element.attr('aria-hidden')).toBeUndefined(); - compileInput('
'); + compileElement('
'); expect(element.attr('aria-hidden')).toBeUndefined(); }); }); @@ -80,7 +80,7 @@ describe('$aria', function() { beforeEach(injectScopeAndCompiler); it('should attach itself to input type="checkbox"', function() { - compileInput(''); + compileElement(''); scope.$apply('val = true'); expect(element.attr('aria-checked')).toBe('true'); @@ -104,25 +104,25 @@ describe('$aria', function() { it('should attach itself to role="radio"', function() { scope.$apply("val = 'one'"); - compileInput('
'); + compileElement('
'); expect(element.attr('aria-checked')).toBe('true'); }); it('should attach itself to role="checkbox"', function() { scope.val = true; - compileInput('
'); + compileElement('
'); expect(element.attr('aria-checked')).toBe('true'); }); it('should attach itself to role="menuitemradio"', function() { scope.val = 'one'; - compileInput('
'); + compileElement('
'); expect(element.attr('aria-checked')).toBe('true'); }); it('should attach itself to role="menuitemcheckbox"', function() { scope.val = true; - compileInput('
'); + compileElement('
'); expect(element.attr('aria-checked')).toBe('true'); }); @@ -143,34 +143,49 @@ describe('$aria', function() { describe('roles for custom inputs', function() { beforeEach(injectScopeAndCompiler); + it('should add missing role="button" to custom input', function() { + compileElement('
'); + expect(element.attr('role')).toBe('button'); + }); + + it('should not add role="button" to anchor', function() { + compileElement(''); + expect(element.attr('role')).not.toBe('button'); + }); + it('should add missing role="checkbox" to custom input', function() { scope.$apply('val = true'); - compileInput('
'); + compileElement('
'); expect(element.attr('role')).toBe('checkbox'); }); + it('should not add a role to a native checkbox', function() { scope.$apply('val = true'); - compileInput(''); + compileElement(''); expect(element.attr('role')).toBe(undefined); }); + it('should add missing role="radio" to custom input', function() { scope.$apply('val = true'); - compileInput('
'); + compileElement('
'); expect(element.attr('role')).toBe('radio'); }); + it('should not add a role to a native radio button', function() { scope.$apply('val = true'); - compileInput(''); + compileElement(''); expect(element.attr('role')).toBe(undefined); }); + it('should add missing role="slider" to custom input', function() { scope.$apply('val = true'); - compileInput('
'); + compileElement('
'); expect(element.attr('role')).toBe('slider'); }); + it('should not add a role to a native range input', function() { scope.$apply('val = true'); - compileInput(''); + compileElement(''); expect(element.attr('role')).toBe(undefined); }); }); @@ -182,16 +197,16 @@ describe('$aria', function() { beforeEach(injectScopeAndCompiler); it('should not attach aria-checked', function() { - compileInput("
"); + compileElement("
"); expect(element.attr('aria-checked')).toBeUndefined(); - compileInput("
"); + compileElement("
"); expect(element.attr('aria-checked')).toBeUndefined(); - compileInput("
"); + compileElement("
"); expect(element.attr('aria-checked')).toBeUndefined(); - compileInput("
"); + compileElement("
"); expect(element.attr('aria-checked')).toBeUndefined(); }); }); @@ -201,7 +216,7 @@ describe('$aria', function() { it('should attach itself to input elements', function() { scope.$apply('val = false'); - compileInput(""); + compileElement(""); expect(element.attr('aria-disabled')).toBe('false'); scope.$apply('val = true'); @@ -210,7 +225,7 @@ describe('$aria', function() { it('should attach itself to textarea elements', function() { scope.$apply('val = false'); - compileInput(''); + compileElement(''); expect(element.attr('aria-disabled')).toBe('false'); scope.$apply('val = true'); @@ -219,7 +234,7 @@ describe('$aria', function() { it('should attach itself to button elements', function() { scope.$apply('val = false'); - compileInput(''); + compileElement(''); expect(element.attr('aria-disabled')).toBe('false'); scope.$apply('val = true'); @@ -228,7 +243,7 @@ describe('$aria', function() { it('should attach itself to select elements', function() { scope.$apply('val = false'); - compileInput(''); + compileElement(''); expect(element.attr('aria-disabled')).toBe('false'); scope.$apply('val = true'); @@ -271,7 +286,7 @@ describe('$aria', function() { beforeEach(injectScopeAndCompiler); it('should attach aria-invalid to input', function() { - compileInput(''); + compileElement(''); scope.$apply("txtInput='LTten'"); expect(element.attr('aria-invalid')).toBe('true'); @@ -280,7 +295,7 @@ describe('$aria', function() { }); it('should not attach itself if aria-invalid is already present', function() { - compileInput(''); + compileElement(''); scope.$apply("txtInput='LTten'"); expect(element.attr('aria-invalid')).toBe('userSetValue'); }); @@ -294,7 +309,7 @@ describe('$aria', function() { it('should not attach aria-invalid if the option is disabled', function() { scope.$apply("txtInput='LTten'"); - compileInput(''); + compileElement(''); expect(element.attr('aria-invalid')).toBeUndefined(); }); }); @@ -303,7 +318,7 @@ describe('$aria', function() { beforeEach(injectScopeAndCompiler); it('should attach aria-required to input', function() { - compileInput(''); + compileElement(''); expect(element.attr('aria-required')).toBe('true'); scope.$apply("val='input is valid now'"); @@ -311,7 +326,7 @@ describe('$aria', function() { }); it('should attach aria-required to textarea', function() { - compileInput(''); + compileElement(''); expect(element.attr('aria-required')).toBe('true'); scope.$apply("val='input is valid now'"); @@ -319,7 +334,7 @@ describe('$aria', function() { }); it('should attach aria-required to select', function() { - compileInput(''); + compileElement(''); expect(element.attr('aria-required')).toBe('true'); scope.$apply("val='input is valid now'"); @@ -327,7 +342,7 @@ describe('$aria', function() { }); it('should attach aria-required to ngRequired', function() { - compileInput(''); + compileElement(''); expect(element.attr('aria-required')).toBe('true'); scope.$apply("val='input is valid now'"); @@ -335,16 +350,16 @@ describe('$aria', function() { }); it('should not attach itself if aria-required is already present', function() { - compileInput(""); + compileElement(""); expect(element.attr('aria-required')).toBe('userSetValue'); - compileInput(""); + compileElement(""); expect(element.attr('aria-required')).toBe('userSetValue'); - compileInput(""); + compileElement(""); expect(element.attr('aria-required')).toBe('userSetValue'); - compileInput(""); + compileElement(""); expect(element.attr('aria-required')).toBe('userSetValue'); }); }); @@ -356,13 +371,13 @@ describe('$aria', function() { beforeEach(injectScopeAndCompiler); it('should not add the aria-required attribute', function() { - compileInput(""); + compileElement(""); expect(element.attr('aria-required')).toBeUndefined(); - compileInput(""); + compileElement(""); expect(element.attr('aria-required')).toBeUndefined(); - compileInput(""); + compileElement(""); expect(element.attr('aria-required')).toBeUndefined(); }); }); @@ -371,20 +386,20 @@ describe('$aria', function() { beforeEach(injectScopeAndCompiler); it('should attach itself to textarea', function() { - compileInput(''); + compileElement(''); expect(element.attr('aria-multiline')).toBe('true'); }); it('should attach itself role="textbox"', function() { - compileInput('
'); + compileElement('
'); expect(element.attr('aria-multiline')).toBe('true'); }); it('should not attach itself if aria-multiline is already present', function() { - compileInput(''); + compileElement(''); expect(element.attr('aria-multiline')).toBe('userSetValue'); - compileInput('
'); + compileElement('
'); expect(element.attr('aria-multiline')).toBe('userSetValue'); }); }); @@ -396,12 +411,12 @@ describe('$aria', function() { beforeEach(injectScopeAndCompiler); it('should not attach itself to textarea', function() { - compileInput(''); + compileElement(''); expect(element.attr('aria-multiline')).toBeUndefined(); }); it('should not attach itself role="textbox"', function() { - compileInput('
'); + compileElement('
'); expect(element.attr('aria-multiline')).toBeUndefined(); }); }); @@ -459,12 +474,12 @@ describe('$aria', function() { it('should not attach itself', function() { scope.$apply('val = 50'); - compileInput(''); + compileElement(''); expect(element.attr('aria-valuenow')).toBeUndefined(); expect(element.attr('aria-valuemin')).toBeUndefined(); expect(element.attr('aria-valuemax')).toBeUndefined(); - compileInput('
'); + compileElement('
'); expect(element.attr('aria-valuenow')).toBeUndefined(); expect(element.attr('aria-valuemin')).toBeUndefined(); expect(element.attr('aria-valuemax')).toBeUndefined(); @@ -475,32 +490,32 @@ describe('$aria', function() { beforeEach(injectScopeAndCompiler); it('should attach tabindex to role="checkbox", ng-click, and ng-dblclick', function() { - compileInput('
'); + compileElement('
'); expect(element.attr('tabindex')).toBe('0'); - compileInput('
'); + compileElement('
'); expect(element.attr('tabindex')).toBe('0'); - compileInput('
'); + compileElement('
'); expect(element.attr('tabindex')).toBe('0'); }); it('should not attach tabindex if it is already on an element', function() { - compileInput('
'); + compileElement('
'); expect(element.attr('tabindex')).toBe('userSetValue'); - compileInput('
'); + compileElement('
'); expect(element.attr('tabindex')).toBe('userSetValue'); - compileInput('
'); + compileElement('
'); expect(element.attr('tabindex')).toBe('userSetValue'); - compileInput('
'); + compileElement('
'); expect(element.attr('tabindex')).toBe('userSetValue'); }); it('should set proper tabindex values for radiogroup', function() { - compileInput('
' + + compileElement('
' + '
1
' + '
2
' + '
'); @@ -553,7 +568,7 @@ describe('$aria', function() { scope.someAction = function() {}; clickFn = spyOn(scope, 'someAction'); - compileInput('
'); + compileElement('
'); element.triggerHandler({type: 'keypress', keyCode: 32}); @@ -562,7 +577,7 @@ describe('$aria', function() { }); it('should update bindings when keypress handled', function() { - compileInput('
{{text}}
'); + compileElement('
{{text}}
'); expect(element.text()).toBe(''); spyOn(scope.$root, '$digest').andCallThrough(); element.triggerHandler({ type: 'keypress', keyCode: 13 }); @@ -571,22 +586,22 @@ describe('$aria', function() { }); it('should pass $event to ng-click handler as local', function() { - compileInput('
{{event.type}}' + - '{{event.keyCode}}
'); + compileElement('
{{event.type}}' + + '{{event.keyCode}}
'); expect(element.text()).toBe(''); element.triggerHandler({ type: 'keypress', keyCode: 13 }); expect(element.text()).toBe('keypress13'); }); it('should not bind keypress to elements not in the default config', function() { - compileInput(''); + compileElement(''); expect(element.text()).toBe(''); element.triggerHandler({ type: 'keypress', keyCode: 13 }); expect(element.text()).toBe(''); }); }); - describe('actions when bindKeypress set to false', function() { + describe('actions when bindKeypress is set to false', function() { beforeEach(configAriaProvider({ bindKeypress: false })); @@ -611,16 +626,16 @@ describe('$aria', function() { beforeEach(injectScopeAndCompiler); it('should not add a tabindex attribute', function() { - compileInput('
'); + compileElement('
'); expect(element.attr('tabindex')).toBeUndefined(); - compileInput('
'); + compileElement('
'); expect(element.attr('tabindex')).toBeUndefined(); - compileInput('
'); + compileElement('
'); expect(element.attr('tabindex')).toBeUndefined(); - compileInput('
'); + compileElement('
'); expect(element.attr('tabindex')).toBeUndefined(); }); });