accessibility-developer-tools
Advanced tools
Comparing version 2.8.0 to 2.9.0-rc.0
{ | ||
"name": "accessibility-developer-tools", | ||
"version": "2.8.0", | ||
"version": "2.9.0-rc.0", | ||
"homepage": "https://github.com/GoogleChrome/accessibility-developer-tools", | ||
@@ -11,2 +11,3 @@ "authors": [ | ||
"moduleType": [ | ||
"amd", | ||
"globals" | ||
@@ -17,5 +18,6 @@ ], | ||
"testing", | ||
"WCAG" | ||
"WCAG", | ||
"module" | ||
], | ||
"license": "Apache License 2.0", | ||
"license": "Apache-2.0", | ||
"ignore": [ | ||
@@ -22,0 +24,0 @@ "**/.*", |
@@ -0,1 +1,27 @@ | ||
## 2.9.0-rc.0 - 2015-08-21 | ||
### New rules | ||
* A label element may not have labelable descendants other than its labeled control (`src/audits/MultipleLabelableElementsPerLabel.js`) | ||
### Enhancements | ||
* Implement support for specifying audit configuration options through an object when initializing audits (#165). | ||
* Implement support for AMD loaders. | ||
### Bug fixes: | ||
* Fix `badAriaAttributeValue` not correctly handling decimal values (#182). | ||
* Work around null pointer exception caused by closure compiler issue (#183). | ||
* Add a special case to handle color `"transparent"` to fix (#180). | ||
* Fix `matchSelector` not working properly in browser environments without vendor prefixes (#189). | ||
* Fix false positives on elements with no role for Unsupported ARIA Attribute rule (#178 and #199). | ||
* Fix ARIA `tablist` and ARIA `tab` scope (#204) | ||
* Fix link with clear purpose with text alternative (#156); | ||
* Handle edge cases in number parser, e.g. "+1", ".1", "01" | ||
* HTML button containing img with alt attribute now passes controlsWithoutLabel (#202) | ||
* Disabled elements should be ignored by low contrast audit (#205) | ||
* Fix input of type "text" did not find correct implied role (#225) | ||
* Hidden links are no longer relevant for meaningful link text rule. | ||
## 2.8.0 - 2015-07-24 | ||
@@ -5,3 +31,3 @@ | ||
# Enhancements: | ||
### Enhancements: | ||
* Pull color code into separate file. | ||
@@ -8,0 +34,0 @@ * Improve color suggestion algorithm. |
@@ -79,2 +79,9 @@ module.exports = function(grunt) { | ||
eslint: { | ||
options: { | ||
configFile: '.eslintrc' | ||
}, | ||
target: ['./src/js/', './src/audits/'] | ||
}, | ||
prompt: { | ||
@@ -273,2 +280,3 @@ 'gh-release': { | ||
grunt.registerTask('build', ['clean:local', 'save-revision', 'closurecompiler:minify']); | ||
grunt.registerTask('lint', ['eslint']); | ||
grunt.registerTask('test:unit', ['qunit']); | ||
@@ -275,0 +283,0 @@ grunt.registerTask('dist', ['clean:dist', 'build', 'copy:dist']); |
{ | ||
"name": "accessibility-developer-tools", | ||
"version": "2.8.0", | ||
"version": "2.9.0-rc.0", | ||
"repository": { | ||
@@ -10,2 +10,3 @@ "type": "git", | ||
"bluebird": "^2.9.27", | ||
"eslint-plugin-google-camelcase": "0.0.1", | ||
"grunt": "^0.4.5", | ||
@@ -19,2 +20,3 @@ "grunt-bump": "^0.3.1", | ||
"grunt-contrib-qunit": "^0.7.0", | ||
"grunt-eslint": "^16.0.0", | ||
"grunt-prompt": "^1.3.0", | ||
@@ -42,3 +44,3 @@ "load-grunt-tasks": "^3.2.0", | ||
"author": "Google", | ||
"license": "Apache License 2.0" | ||
"license": "Apache-2.0" | ||
} |
@@ -18,6 +18,6 @@ # Accessibility Developer Tools | ||
`git 1.6.5` or later: | ||
`git 1.6.5` or later: | ||
% git clone --recursive https://github.com/GoogleChrome/accessibility-developer-tools.git | ||
Before `git 1.6.5`: | ||
@@ -59,3 +59,3 @@ | ||
Once you have included `axs_testing.js`, you can call call `axs.Audit.run()`. This returns an object in the following form: | ||
Once you have included `axs_testing.js`, you can call `axs.Audit.run()`. This returns an object in the following form: | ||
@@ -88,3 +88,3 @@ { | ||
println(report) | ||
### Run audit from Selenium WebDriver (Scala)(with caching): | ||
@@ -105,3 +105,3 @@ val cache = collection.mutable.Map[String, String]() | ||
println(report) | ||
If println() outputs nothing, check if you need to set DesiredCapabilities for your WebDriver (such as loggingPrefs): | ||
@@ -189,3 +189,14 @@ https://code.google.com/p/selenium/wiki/DesiredCapabilities | ||
axs.Audit.run(configuration); | ||
You may also specify a configuration payload while instantiating the `axs.AuditConfiguration`, | ||
which allows you to provide multiple configuration options at once. | ||
var configuration = new axs.AuditConfiguration({ | ||
auditRulesToRun: ['badAriaRole'], | ||
scope: document.querySelector('main'), | ||
maxResults: 5 | ||
}); | ||
axs.Audit.run(configuration); | ||
## License | ||
@@ -192,0 +203,0 @@ |
@@ -20,5 +20,15 @@ /* | ||
*/ | ||
// AUTO-GENERATED CONTENT BELOW: DO NOT EDIT! See above for details. | ||
%output% | ||
var fn = (function() { | ||
%output% | ||
return axs; | ||
}); | ||
// Define AMD module if possible, export globals otherwise. | ||
if (typeof define !== 'undefined' && define.amd) { | ||
define([], fn); | ||
} else { | ||
var axs = fn.call(this); | ||
} |
@@ -40,3 +40,3 @@ // Copyright 2014 Google Inc. | ||
return false; | ||
var appliedRole = elementRole.applied; | ||
var appliedRole = elementRole.applied; | ||
var ariaRole = appliedRole.details; | ||
@@ -43,0 +43,0 @@ var requiredScope = ariaRole['scope']; |
@@ -62,3 +62,6 @@ // Copyright 2012 Google Inc. | ||
} | ||
if (!axs.utils.hasLabel(control)) | ||
if (axs.utils.hasLabel(control)) | ||
return false; | ||
var textAlternatives = axs.properties.findTextAlternatives(control, {}); | ||
if (textAlternatives === null || textAlternatives.trim() === '') | ||
return true; | ||
@@ -65,0 +68,0 @@ return false; |
@@ -46,3 +46,3 @@ // Copyright 2012 Google Inc. | ||
var textAlternatives = axs.properties.findTextAlternatives(element, {}); | ||
if (!textAlternatives || textAlternatives.trim() === '') | ||
if (textAlternatives === null || textAlternatives.trim() === '') | ||
return false; | ||
@@ -49,0 +49,0 @@ |
@@ -32,3 +32,3 @@ // Copyright 2013 Google Inc. | ||
relevantElementMatcher: function(element) { | ||
return axs.browserUtils.matchSelector(element, 'a'); | ||
return axs.browserUtils.matchSelector(element, 'a') && !axs.utils.isElementOrAncestorHidden(element); | ||
}, | ||
@@ -43,3 +43,3 @@ /** | ||
var blacklistPhrases = config['blacklistPhrases'] || []; | ||
var whitespaceRE = /\s+/ | ||
var whitespaceRE = /\s+/; | ||
for (var i = 0; i < blacklistPhrases.length; i++) { | ||
@@ -61,3 +61,5 @@ // Match the blacklist phrase, case insensitively, as the whole string (allowing for | ||
['click', 'tap', 'go', 'here', 'learn', 'more', 'this', 'page', 'link', 'about']; | ||
var filteredText = anchor.textContent; | ||
var filteredText = axs.properties.findTextAlternatives(anchor, {}); | ||
if (filteredText === null || filteredText.trim() === '') | ||
return true; | ||
filteredText = filteredText.replace(/[^a-zA-Z ]/g, ''); | ||
@@ -64,0 +66,0 @@ for (var i = 0; i < stopwords.length; i++) { |
@@ -29,3 +29,4 @@ // Copyright 2012 Google Inc. | ||
relevantElementMatcher: function(element) { | ||
return axs.properties.hasDirectTextDescendant(element); | ||
return axs.properties.hasDirectTextDescendant(element) && | ||
!axs.utils.isElementDisabled(element); | ||
}, | ||
@@ -32,0 +33,0 @@ test: function(element) { |
@@ -32,3 +32,3 @@ // Copyright 2013 Google Inc. | ||
if (el.textContent && el.textContent.length > 0) { | ||
return false; | ||
return false; | ||
} | ||
@@ -38,4 +38,4 @@ var style = window.getComputedStyle(el, null); | ||
if (!bgImage || bgImage === 'undefined' || bgImage === 'none' || | ||
bgImage.indexOf('url') != 0) { | ||
return false; | ||
bgImage.indexOf('url') != 0) { | ||
return false; | ||
} | ||
@@ -42,0 +42,0 @@ var width = parseInt(style.width, 10); |
@@ -24,3 +24,3 @@ // Copyright 2013 Google Inc. | ||
relevantElementMatcher: function(element) { | ||
return element.tagName.toLowerCase() == "html"; | ||
return element.tagName.toLowerCase() == 'html'; | ||
}, | ||
@@ -27,0 +27,0 @@ test: function(scope) { |
@@ -20,3 +20,3 @@ // Copyright 2014 Google Inc. | ||
(function(){ | ||
(function() { | ||
/** | ||
@@ -47,15 +47,15 @@ * @type {axs.AuditRule.Spec} | ||
for (var i = required.length - 1; i >= 0; i--) { | ||
var descendants = axs.utils.findDescendantsWithRole(element, required[i]); | ||
if (descendants && descendants.length) { // if we found at least one descendant with a required role | ||
return false; | ||
} | ||
} | ||
// if we get to this point our element has 'required owned elements' but it does not own them implicitly in the DOM | ||
var ownedElements = axs.utils.getIdReferents('aria-owns', element); | ||
for (var i = ownedElements.length - 1; i >= 0; i--) { | ||
var ownedElement = ownedElements[i]; | ||
var ownedElementRole = axs.utils.getRoles(ownedElement, true); | ||
if (ownedElementRole && ownedElementRole.applied) { | ||
var appliedRole = ownedElementRole.applied; | ||
for (var j = required.length - 1; j >= 0; j--) { | ||
var descendants = axs.utils.findDescendantsWithRole(element, required[i]); | ||
if (descendants && descendants.length) { // if we found at least one descendant with a required role | ||
return false; | ||
} | ||
} | ||
// if we get to this point our element has 'required owned elements' but it does not own them implicitly in the DOM | ||
var ownedElements = axs.utils.getIdReferents('aria-owns', element); | ||
for (var i = ownedElements.length - 1; i >= 0; i--) { | ||
var ownedElement = ownedElements[i]; | ||
var ownedElementRole = axs.utils.getRoles(ownedElement, true); | ||
if (ownedElementRole && ownedElementRole.applied) { | ||
var appliedRole = ownedElementRole.applied; | ||
for (var j = required.length - 1; j >= 0; j--) { | ||
if (appliedRole.name === required[j]) { // if this explicitly owned element has a required role | ||
@@ -65,5 +65,5 @@ return false; | ||
} | ||
} | ||
} | ||
return true; // if we made it here then we did not find the required owned elements in the DOM | ||
} | ||
} | ||
return true; // if we made it here then we did not find the required owned elements in the DOM | ||
}, | ||
@@ -70,0 +70,0 @@ code: 'AX_ARIA_08' |
@@ -20,15 +20,15 @@ // Copyright 2014 Google Inc. | ||
axs.AuditRules.addRule({ | ||
name: "tabIndexGreaterThanZero", | ||
heading: "Avoid positive integer values for tabIndex", | ||
url: "https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_focus_03", | ||
name: 'tabIndexGreaterThanZero', | ||
heading: 'Avoid positive integer values for tabIndex', | ||
url: 'https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_focus_03', | ||
severity: axs.constants.Severity.WARNING, | ||
relevantElementMatcher: function(element) { | ||
var selector = "[tabindex]"; | ||
return axs.browserUtils.matchSelector(element, selector); | ||
var selector = '[tabindex]'; | ||
return axs.browserUtils.matchSelector(element, selector); | ||
}, | ||
test: function(element) { | ||
if(element.tabIndex > 0) | ||
return true; | ||
if (element.tabIndex > 0) | ||
return true; | ||
}, | ||
code: 'AX_FOCUS_03' | ||
}); |
@@ -48,5 +48,4 @@ // Copyright 2014 Google Inc. | ||
if (role && role.applied) { | ||
supported = role.applied.details.propertiesSet; | ||
} | ||
else { | ||
supported = /** @type {Object<string, boolean>} */ (role.applied.details.propertiesSet); | ||
} else { | ||
// This test ignores the fact that some HTML elements should not take even global attributes. | ||
@@ -53,0 +52,0 @@ supported = axs.constants.GLOBAL_PROPERTIES; |
@@ -35,2 +35,17 @@ // Copyright 2012 Google Inc. | ||
/** | ||
* Elements that can have labels: https://html.spec.whatwg.org/multipage/forms.html#category-label | ||
* @const | ||
* @type {string} | ||
*/ | ||
axs.utils.LABELABLE_ELEMENTS_SELECTOR = | ||
'button,' + | ||
'input:not([type=hidden]),' + | ||
'keygen,' + | ||
'meter,' + | ||
'output,' + | ||
'progress,' + | ||
'select,' + | ||
'textarea'; | ||
/** | ||
* Returns the nearest ancestor which is an Element. | ||
@@ -390,3 +405,3 @@ * @param {Node} node | ||
var foundSolidColor = null; | ||
while (parent = axs.utils.parentElement(parent)) { | ||
while ((parent = axs.utils.parentElement(parent))) { | ||
var computedStyle = window.getComputedStyle(parent, null); | ||
@@ -567,3 +582,40 @@ if (!computedStyle) | ||
/** | ||
* Determine if this element natively supports being disabled (i.e. via the `disabled` attribute. | ||
* Disabled here means that the element should be considered disabled according to specification. | ||
* This element may or may not be effectively disabled in practice as this is dependent on implementation. | ||
* | ||
* @param {Element} element An element to check. | ||
* @return {boolean} true If the element supports being natively disabled. | ||
*/ | ||
axs.utils.isNativelyDisableable = function(element) { | ||
var tagName = element.tagName.toUpperCase(); | ||
return (tagName in axs.constants.NATIVELY_DISABLEABLE); | ||
}; | ||
/** | ||
* Determine if this element is disabled directly or indirectly by a disabled ancestor. | ||
* Disabled here means that the element should be considered disabled according to specification. | ||
* This element may or may not be effectively disabled in practice as this is dependent on implementation. | ||
* | ||
* @param {Element} element An element to check. | ||
* @return {boolean} true if the element or one of its ancestors is disabled. | ||
*/ | ||
axs.utils.isElementDisabled = function(element) { | ||
if (axs.browserUtils.matchSelector(element, '[aria-disabled=true], [aria-disabled=true] *')) { | ||
return true; | ||
} | ||
if(!axs.utils.isNativelyDisableable(element) || | ||
axs.browserUtils.matchSelector(element, 'fieldset>legend:first-of-type *')) { | ||
return false; | ||
} | ||
for (var next = element; next !== null; next = axs.utils.parentElement(next)) { | ||
if(axs.utils.isNativelyDisableable(next) && next.hasAttribute('disabled')) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
}; | ||
/** | ||
* @param {Element} element An element to check. | ||
* @return {boolean} True if the element is hidden from accessibility. | ||
@@ -645,3 +697,2 @@ */ | ||
roleObject.valid = false; | ||
} | ||
@@ -683,2 +734,3 @@ result.roles.push(roleObject); | ||
result.idref = isValid.idref; | ||
// falls through | ||
case "idref_list": | ||
@@ -698,3 +750,2 @@ var idrefValues = value.split(/\s+/); | ||
case "integer": | ||
case "decimal": | ||
var validNumber = axs.utils.isValidNumber(value); | ||
@@ -706,3 +757,3 @@ if (!validNumber.valid) { | ||
} | ||
if (Math.floor(validNumber.value) != validNumber.value) { | ||
if (Math.floor(validNumber.value) !== validNumber.value) { | ||
result.valid = false; | ||
@@ -715,8 +766,12 @@ result.reason = '' + value + ' is not a whole integer'; | ||
return result; | ||
case "decimal": | ||
case "number": | ||
var validNumber = axs.utils.isValidNumber(value); | ||
if (validNumber.valid) { | ||
result.valid = true; | ||
result.value = validNumber.value; | ||
result.valid = validNumber.valid; | ||
if (!validNumber.valid) { | ||
result.reason = validNumber.reason; | ||
return result; | ||
} | ||
result.value = validNumber.value; | ||
return result; | ||
case "string": | ||
@@ -738,2 +793,3 @@ result.valid = true; | ||
} | ||
// falls through | ||
case "token_list": | ||
@@ -849,2 +905,5 @@ var tokenValues = value.split(/\s+/); | ||
/** | ||
* Tests if a number is real number for a11y purposes. | ||
* Must be a real, numerical, decimal value; heavily inspired by | ||
* http://www.w3.org/TR/wai-aria/states_and_properties#valuetype_number | ||
* @param {string} value | ||
@@ -854,14 +913,18 @@ * @return {!Object} | ||
axs.utils.isValidNumber = function(value) { | ||
try { | ||
var parsedValue = JSON.parse(value); | ||
} catch (ex) { | ||
return { 'valid': false, | ||
'value': value, | ||
'reason': '"' + value + '" is not a number' }; | ||
var failResult = { | ||
'valid': false, | ||
'value': value, | ||
'reason': '"' + value + '" is not a number' | ||
}; | ||
if (!value) { | ||
return failResult; | ||
} | ||
if (typeof(parsedValue) != 'number') { | ||
return { 'valid': false, | ||
'value': value, | ||
'reason': '"' + value + '" is not a number' }; | ||
if (/^0x/i.test(value)) { | ||
failResult.reason = '"' + value + '" is not a decimal number'; // hex is not accepted | ||
return failResult; | ||
} | ||
var parsedValue = value * 1; | ||
if (!isFinite(parsedValue)) { | ||
return failResult; | ||
} | ||
return { 'valid': true, 'value': parsedValue }; | ||
@@ -1109,2 +1172,1 @@ }; | ||
}; | ||
@@ -22,7 +22,20 @@ // Copyright 2012 Google Inc. | ||
/** | ||
* Object to hold configuration for an Audit run. | ||
* @constructor | ||
* @param {?Object=} config Configuration object | ||
* The following configuration options are supported: | ||
* - scope | ||
* - auditRulesToRun | ||
* - auditRulesToIgnore | ||
* - maxResults | ||
* - withConsoleApi | ||
* - showUnsupportedRulesWarning | ||
*/ | ||
axs.AuditConfiguration = function() { | ||
axs.AuditConfiguration = function(config) { | ||
if (config == null) { | ||
config = {}; | ||
} | ||
/** | ||
@@ -82,2 +95,8 @@ * Dictionary of { audit rule name : { rules } } where rules is a dictionary | ||
for (var prop in this) { | ||
if ((this.hasOwnProperty(prop)) && (prop in config)) { | ||
this[prop] = config[prop]; | ||
} | ||
} | ||
goog.exportProperty(this, 'scope', this.scope); | ||
@@ -87,3 +106,3 @@ goog.exportProperty(this, 'auditRulesToRun', this.auditRulesToRun); | ||
goog.exportProperty(this, 'withConsoleApi', this.withConsoleApi); | ||
goog.exportProperty(this, 'showUnsupportedRulesWarning', this.showUnsupportedRulesWarning ); | ||
goog.exportProperty(this, 'showUnsupportedRulesWarning', this.showUnsupportedRulesWarning); | ||
}; | ||
@@ -183,3 +202,3 @@ goog.exportSymbol('axs.AuditConfiguration', axs.AuditConfiguration); | ||
axs.Audit.getRulesCannotRun = function(opt_configuration) { | ||
if(opt_configuration.withConsoleApi) { | ||
if (opt_configuration.withConsoleApi) { | ||
return []; | ||
@@ -225,5 +244,5 @@ } | ||
if(!axs.Audit.unsupportedRulesWarningShown && configuration.showUnsupportedRulesWarning) { | ||
if (!axs.Audit.unsupportedRulesWarningShown && configuration.showUnsupportedRulesWarning) { | ||
var unsupportedRules = axs.Audit.getRulesCannotRun(configuration); | ||
if(unsupportedRules.length > 0) { | ||
if (unsupportedRules.length > 0) { | ||
console.warn("Some rules cannot be checked using the axs.Audit.run() method call. Use the Chrome plugin to check these rules: " + unsupportedRules.join(", ")); | ||
@@ -230,0 +249,0 @@ console.warn("To remove this message, pass an AuditConfiguration object to axs.Audit.run() and set configuration.showUnsupportedRulesWarning = false."); |
@@ -22,15 +22,15 @@ // Copyright 2013 Google Inc. | ||
axs.AuditResults = function() { | ||
/** | ||
* The errors received from the audit run. | ||
* @type {Array.<string>} | ||
* @private | ||
*/ | ||
this.errors_ = []; | ||
/** | ||
* The errors received from the audit run. | ||
* @type {Array.<string>} | ||
* @private | ||
*/ | ||
this.errors_ = []; | ||
/** | ||
* The warnings receive from the audit run. | ||
* @type {Array.<string>} | ||
* @private | ||
*/ | ||
this.warnings_ = []; | ||
/** | ||
* The warnings receive from the audit run. | ||
* @type {Array.<string>} | ||
* @private | ||
*/ | ||
this.warnings_ = []; | ||
}; | ||
@@ -44,5 +44,5 @@ goog.exportSymbol('axs.AuditResults', axs.AuditResults); | ||
axs.AuditResults.prototype.addError = function(errorMessage) { | ||
if (errorMessage != '') { | ||
this.errors_.push(errorMessage); | ||
} | ||
if (errorMessage != '') { | ||
this.errors_.push(errorMessage); | ||
} | ||
@@ -58,5 +58,5 @@ }; | ||
axs.AuditResults.prototype.addWarning = function(warningMessage) { | ||
if (warningMessage != '') { | ||
this.warnings_.push(warningMessage); | ||
} | ||
if (warningMessage != '') { | ||
this.warnings_.push(warningMessage); | ||
} | ||
@@ -72,3 +72,3 @@ }; | ||
axs.AuditResults.prototype.numErrors = function() { | ||
return this.errors_.length; | ||
return this.errors_.length; | ||
}; | ||
@@ -83,3 +83,3 @@ goog.exportProperty(axs.AuditResults.prototype, 'numErrors', | ||
axs.AuditResults.prototype.numWarnings = function() { | ||
return this.warnings_.length; | ||
return this.warnings_.length; | ||
}; | ||
@@ -94,3 +94,3 @@ goog.exportProperty(axs.AuditResults.prototype, 'numWarnings', | ||
axs.AuditResults.prototype.getErrors = function() { | ||
return this.errors_; | ||
return this.errors_; | ||
}; | ||
@@ -105,3 +105,3 @@ goog.exportProperty(axs.AuditResults.prototype, 'getErrors', | ||
axs.AuditResults.prototype.getWarnings = function() { | ||
return this.warnings_; | ||
return this.warnings_; | ||
}; | ||
@@ -116,22 +116,20 @@ goog.exportProperty(axs.AuditResults.prototype, 'getWarnings', | ||
axs.AuditResults.prototype.toString = function() { | ||
var message = ''; | ||
for (var i = 0; i < this.errors_.length; i++) { | ||
if (i == 0) { | ||
message += '\nErrors:\n'; | ||
var message = ''; | ||
for (var i = 0; i < this.errors_.length; i++) { | ||
if (i == 0) { | ||
message += '\nErrors:\n'; | ||
} | ||
var result = this.errors_[i]; | ||
message += result + '\n\n'; | ||
} | ||
var result = this.errors_[i]; | ||
message += result + '\n\n'; | ||
} | ||
for (var i = 0; i < this.warnings_.length; i++) { | ||
if (i == 0) { | ||
message += '\nWarnings:\n'; | ||
for (var i = 0; i < this.warnings_.length; i++) { | ||
if (i == 0) { | ||
message += '\nWarnings:\n'; | ||
} | ||
var result = this.warnings_[i]; | ||
message += result + '\n\n'; | ||
} | ||
var result = this.warnings_[i]; | ||
message += result + '\n\n'; | ||
} | ||
return message; | ||
return message; | ||
}; | ||
goog.exportProperty(axs.AuditResults.prototype, 'toString', | ||
axs.AuditResults.prototype.toString); | ||
@@ -186,3 +186,3 @@ // Copyright 2012 Google Inc. | ||
//If it is a iframe, get the contentDocument | ||
// If it is a iframe, get the contentDocument | ||
if (element && element.localName == 'iframe' && element.contentDocument) { | ||
@@ -204,3 +204,3 @@ axs.AuditRule.collectMatchingElements(element.contentDocument, | ||
} | ||
} | ||
}; | ||
@@ -233,4 +233,4 @@ /** | ||
for (var i = 0; i < ignoreSelectors.length; i++) { | ||
if (axs.browserUtils.matchSelector(element, ignoreSelectors[i])) | ||
return true; | ||
if (axs.browserUtils.matchSelector(element, ignoreSelectors[i])) | ||
return true; | ||
} | ||
@@ -250,3 +250,3 @@ return false; | ||
var result = failingElements.length ? axs.constants.AuditResult.FAIL : axs.constants.AuditResult.PASS; | ||
var results = { result: result, elements: failingElements}; | ||
var results = { result: result, elements: failingElements }; | ||
if (i < relevantElements.length) | ||
@@ -253,0 +253,0 @@ results['resultsTruncated'] = true; |
@@ -19,3 +19,3 @@ // Copyright 2012 Google Inc. | ||
(function(){ | ||
(function() { | ||
var auditRulesByName = {}; | ||
@@ -61,3 +61,3 @@ var auditRulesByCode = {}; | ||
var ruleNames = Object.keys(auditRulesByName); | ||
if(opt_namesOnly) | ||
if (opt_namesOnly) | ||
return ruleNames; | ||
@@ -64,0 +64,0 @@ return ruleNames.map(function(name) { |
@@ -18,2 +18,3 @@ // Copyright 2013 Google Inc. | ||
/** | ||
* Use Webkit matcher when matches() is not supported. | ||
* Use Firefox matcher when Webkit is not supported. | ||
@@ -26,2 +27,4 @@ * Use IE matcher when neither webkit nor Firefox supported. | ||
axs.browserUtils.matchSelector = function(element, selector) { | ||
if (element.matches) | ||
return element.matches(selector); | ||
if (element.webkitMatchesSelector) | ||
@@ -28,0 +31,0 @@ return element.webkitMatchesSelector(selector); |
@@ -140,2 +140,5 @@ // Copyright 2015 Google Inc. | ||
axs.color.parseColor = function(colorString) { | ||
if (colorString === "transparent") { | ||
return new axs.color.Color(0, 0, 0, 0); | ||
} | ||
var rgbRegex = /^rgb\((\d+), (\d+), (\d+)\)$/; | ||
@@ -505,3 +508,3 @@ var match = colorString.match(rgbRegex); | ||
axs.color.scalarMultiplyVector = function(vector, scalar) { | ||
var result = [] | ||
var result = []; | ||
for (var i = 0; i < vector.length; i++) | ||
@@ -508,0 +511,0 @@ result[i] = vector[i] * scalar; |
@@ -410,3 +410,4 @@ // Copyright 2012 Google Inc. | ||
"parent": [ "sectionhead", "widget" ], | ||
"properties": [ "aria-selected" ] | ||
"properties": [ "aria-selected" ], | ||
"scope": [ "tablist" ] | ||
}, | ||
@@ -417,4 +418,3 @@ "tablist": { | ||
"parent": [ "composite", "directory" ], | ||
"properties": [ "aria-level" ], | ||
"scope": [ "tablist" ] | ||
"properties": [ "aria-level" ] | ||
}, | ||
@@ -780,6 +780,20 @@ "tabpanel": { | ||
(function() { | ||
// pull values lists into sets | ||
for (var propertyName in axs.constants.ARIA_PROPERTIES) { | ||
var propertyDetails = axs.constants.ARIA_PROPERTIES[propertyName]; | ||
if (!propertyDetails.values) | ||
continue; | ||
var valuesSet = {}; | ||
for (var i = 0; i < propertyDetails.values.length; i++) | ||
valuesSet[propertyDetails.values[i]] = true; | ||
propertyDetails.valuesSet = valuesSet; | ||
} | ||
})(); | ||
/** | ||
* All of the states and properties which apply globally. | ||
* @type {Object<!string, !boolean>} | ||
*/ | ||
axs.constants.GLOBAL_PROPERTIES = axs.constants.ARIA_ROLES['roletype'].properties; | ||
axs.constants.GLOBAL_PROPERTIES = axs.constants.ARIA_ROLES['roletype'].propertiesSet; | ||
@@ -985,15 +999,2 @@ /** | ||
(function() { | ||
// pull values lists into sets | ||
for (var propertyName in axs.constants.ARIA_PROPERTIES) { | ||
var propertyDetails = axs.constants.ARIA_PROPERTIES[propertyName]; | ||
if (!propertyDetails.values) | ||
continue; | ||
var valuesSet = {}; | ||
for (var i = 0; i < propertyDetails.values.length; i++) | ||
valuesSet[propertyDetails.values[i]] = true; | ||
propertyDetails.valuesSet = valuesSet; | ||
} | ||
})(); | ||
/** @enum {string} */ | ||
@@ -1055,2 +1056,16 @@ axs.constants.Severity = { | ||
/** @enum {boolean} */ | ||
axs.constants.NATIVELY_DISABLEABLE = { | ||
// W3C and WHATWG https://html.spec.whatwg.org/#enabling-and-disabling-form-controls:-the-disabled-attribute | ||
'BUTTON': true, | ||
'INPUT': true, | ||
'SELECT': true, | ||
'TEXTAREA': true, | ||
'FIELDSET': true, | ||
// W3C http://www.w3.org/TR/html5/disabled-elements.html#disabled-elements | ||
'OPTGROUP': true, | ||
'OPTION': true | ||
}; | ||
/** | ||
@@ -1433,5 +1448,8 @@ * Maps ARIA attributes to their exactly equivalent HTML attributes. | ||
}, { | ||
role: '', | ||
role: 'textbox', | ||
selector: 'input[type="text"]:not([list])' | ||
}, { | ||
role: 'textbox', | ||
selector: 'input:not([type])' | ||
}, { | ||
role: '', | ||
@@ -1438,0 +1456,0 @@ selector: 'input[type="time"]' |
@@ -140,5 +140,3 @@ // Copyright 2012 Google Inc. | ||
} | ||
else { // IE | ||
return hasDirectTextDescendantTreeWalker(); | ||
} | ||
return hasDirectTextDescendantTreeWalker(); | ||
@@ -346,3 +344,3 @@ /** | ||
textAlternatives['controlValue'] = { 'value': element.getAttribute('aria-valuenow'), | ||
'text': '' + element.getAttribute('aria-valuenow')}; | ||
'text': '' + element.getAttribute('aria-valuenow') }; | ||
} | ||
@@ -622,3 +620,3 @@ // If the embedded control is a menu, use the text alternative of the chosen menu item. | ||
var src = element.src; | ||
if (typeof(src) == 'string') { | ||
if (typeof src == 'string') { | ||
var parts = src.split('/'); | ||
@@ -666,4 +664,3 @@ var filename = parts.pop(); | ||
return ariaProperties; | ||
else | ||
return null; | ||
return null; | ||
} | ||
@@ -710,4 +707,3 @@ ariaProperties['roles'] = roles; | ||
var globalProperties = {}; | ||
for (var i = 0; i < axs.constants.GLOBAL_PROPERTIES.length; i++) { | ||
var property = axs.constants.GLOBAL_PROPERTIES[i]; | ||
for (var property in axs.constants.GLOBAL_PROPERTIES) { | ||
if (element.hasAttribute(property)) { | ||
@@ -909,4 +905,3 @@ var propertyValue = element.getAttribute(property); | ||
selectors[selectors.length] = htmlInfo.selector; | ||
} | ||
else { | ||
} else { | ||
selectors[selectors.length] = tagName; // Selectors API is not case sensitive. | ||
@@ -913,0 +908,0 @@ break; // No need to continue adding selectors since we will match the tag itself. |
@@ -82,1 +82,32 @@ // Copyright 2014 Google Inc. | ||
}); | ||
test('Scope role present - tablist', function() { | ||
var rule = axs.AuditRules.getRule('ariaRoleNotScoped'); | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var container = fixture.appendChild(document.createElement('ul')); | ||
container.setAttribute('role', 'tablist'); | ||
for (var i = 0; i < 4; i++) { | ||
var item = container.appendChild(document.createElement('li')); | ||
item.setAttribute('role', 'tab'); | ||
} | ||
var actual = rule.run({ scope: fixture }); | ||
equal(actual.result, axs.constants.AuditResult.PASS); | ||
deepEqual(actual.elements, []); | ||
}); | ||
test('Scope role missing - tab', function() { | ||
var rule = axs.AuditRules.getRule('ariaRoleNotScoped'); | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var container = fixture.appendChild(document.createElement('ul')); | ||
var expected = []; | ||
for (var i = 0; i < 4; i++) { | ||
var item = container.appendChild(document.createElement('li')); | ||
item.setAttribute('role', 'tab'); | ||
expected.push(item); | ||
} | ||
var actual = rule.run({ scope: fixture }); | ||
equal(actual.result, axs.constants.AuditResult.FAIL); | ||
deepEqual(actual.elements, expected); | ||
}); |
@@ -35,1 +35,23 @@ module('BadAriaAttributeValue'); | ||
}); | ||
test('Good decimal number value is good', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var div = document.createElement('div'); | ||
fixture.appendChild(div); | ||
div.setAttribute('aria-valuenow', '0.5'); | ||
deepEqual( | ||
axs.AuditRules.getRule('badAriaAttributeValue').run({ scope: fixture }), | ||
{ elements: [], result: axs.constants.AuditResult.PASS } | ||
); | ||
}); | ||
test('Good negative number value is good', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var div = document.createElement('div'); | ||
fixture.appendChild(div); | ||
div.setAttribute('aria-valuenow', '-10'); | ||
deepEqual( | ||
axs.AuditRules.getRule('badAriaAttributeValue').run({ scope: fixture }), | ||
{ elements: [], result: axs.constants.AuditResult.PASS } | ||
); | ||
}); |
@@ -79,1 +79,125 @@ module('ControlsWithoutLabel'); | ||
}); | ||
test('Button element with image with alt needs no label', function() { | ||
// Setup fixture | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var button = document.createElement('button'); | ||
var img = button.appendChild(document.createElement('img')); | ||
img.setAttribute("alt", "Save"); | ||
fixture.appendChild(button); | ||
var rule = axs.AuditRules.getRule('controlsWithoutLabel'); | ||
equal(rule.run({ scope: fixture }).result, | ||
axs.constants.AuditResult.PASS); | ||
}); | ||
test('Button element with image with empty alt needs a label', function() { | ||
// Setup fixture | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var button = document.createElement('button'); | ||
var img = button.appendChild(document.createElement('img')); | ||
img.setAttribute("alt", ""); | ||
fixture.appendChild(button); | ||
var rule = axs.AuditRules.getRule('controlsWithoutLabel'); | ||
deepEqual(rule.run({ scope: fixture }), | ||
{ elements: [button], result: axs.constants.AuditResult.FAIL }); | ||
}); | ||
test('Button element with image with no alt needs a label', function() { | ||
// Setup fixture | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var button = document.createElement('button'); | ||
button.appendChild(document.createElement('img')); | ||
fixture.appendChild(button); | ||
var rule = axs.AuditRules.getRule('controlsWithoutLabel'); | ||
deepEqual(rule.run({ scope: fixture }), | ||
{ elements: [button], result: axs.constants.AuditResult.FAIL }); | ||
}); | ||
(function(){ | ||
var rule = axs.AuditRules.getRule('controlsWithoutLabel'); | ||
var needLabel = ['checkbox', 'email', 'file', 'number', 'password', 'radio', 'range', 'tel', 'text', 'time', 'url']; | ||
test('Input types which need a label - missing label', function() { | ||
needLabel.forEach(function (type) { | ||
var input = document.createElement('input'); | ||
input.setAttribute("type", type); | ||
var fixture = document.getElementById('qunit-fixture'); | ||
fixture.appendChild(input); | ||
try { | ||
deepEqual(rule.run({ scope: fixture }), | ||
{ elements: [input], result: axs.constants.AuditResult.FAIL }); | ||
} | ||
finally { | ||
fixture.removeChild(input); | ||
} | ||
}); | ||
}); | ||
test('Input types which need a label - ARIA label', function() { | ||
needLabel.forEach(function (type) { | ||
var input = document.createElement('input'); | ||
input.setAttribute("type", type); | ||
input.setAttribute("aria-label", "What is the answer?"); | ||
var fixture = document.getElementById('qunit-fixture'); | ||
fixture.appendChild(input); | ||
try { | ||
equal(rule.run({ scope: fixture }).result, axs.constants.AuditResult.PASS); | ||
} | ||
finally { | ||
fixture.removeChild(input); | ||
} | ||
}); | ||
}); | ||
test('Input types which need a label - ARIA labelledby', function() { | ||
needLabel.forEach(function (type) { | ||
var input = document.createElement('input'); | ||
input.setAttribute("type", type); | ||
var id = goog.getUid(input); | ||
input.setAttribute("aria-labelledby", id); | ||
var label = document.createElement('span'); | ||
label.textContent = "What is the answer?"; | ||
label.setAttribute("id", id); | ||
ok(document.getElementById(id) == null, "test is not reliable, id conflict"); | ||
var fixture = document.getElementById('qunit-fixture'); | ||
fixture.appendChild(label); | ||
fixture.appendChild(input); | ||
try { | ||
equal(rule.run({ scope: fixture }).result, axs.constants.AuditResult.PASS); | ||
} | ||
finally { | ||
fixture.removeChild(input); | ||
fixture.removeChild(label); | ||
} | ||
}); | ||
}); | ||
test('Input types which need a label - HTML label', function() { | ||
needLabel.forEach(function (type) { | ||
var input = document.createElement('input'); | ||
input.setAttribute("type", type); | ||
var id = goog.getUid(input); | ||
input.setAttribute("id", id); | ||
var label = document.createElement('label'); | ||
label.textContent = "What is the answer?"; | ||
label.setAttribute("for", id); | ||
ok(document.getElementById(id) == null, "test is not reliable, id conflict"); | ||
var fixture = document.getElementById('qunit-fixture'); | ||
fixture.appendChild(label); | ||
fixture.appendChild(input); | ||
try { | ||
equal(rule.run({ scope: fixture }).result, axs.constants.AuditResult.PASS); | ||
} | ||
finally { | ||
fixture.removeChild(input); | ||
fixture.removeChild(label); | ||
} | ||
}); | ||
}); | ||
})(); |
@@ -30,2 +30,69 @@ // Copyright 2014 Google Inc. | ||
test('a link with an img with meaningful alt text is good', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var a = document.createElement('a'); | ||
a.href = '#main'; | ||
var img = a.appendChild(document.createElement('img')); | ||
img.setAttribute('alt', 'Learn more about trout fishing'); | ||
fixture.appendChild(a); | ||
var rule = axs.AuditRules.getRule('linkWithUnclearPurpose'); | ||
deepEqual( | ||
rule.run({scope: fixture}), | ||
{ elements: [], result: axs.constants.AuditResult.PASS }); | ||
}); | ||
test('a link with an img with meaningless alt text is bad', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var a = document.createElement('a'); | ||
a.href = '#main'; | ||
var img = a.appendChild(document.createElement('img')); | ||
img.setAttribute('alt', 'Click here!'); | ||
fixture.appendChild(a); | ||
var rule = axs.AuditRules.getRule('linkWithUnclearPurpose'); | ||
deepEqual(rule.run({scope: fixture}), | ||
{ elements: [a], result: axs.constants.AuditResult.FAIL }); | ||
}); | ||
/* | ||
* This test will need to be reviewed when issue #214 is addressed. | ||
*/ | ||
test('a link with meaningful aria-label is good', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
// Style our link to be visually meaningful with no descendent nodes at all. | ||
fixture.innerHTML = '<style>a.trout::after{content: "Learn more about trout fishing"}</style>'; | ||
var a = document.createElement('a'); | ||
a.href = '#main'; | ||
a.className = 'trout'; | ||
a.setAttribute('aria-label', 'Learn more about trout fishing'); | ||
fixture.appendChild(a); | ||
var rule = axs.AuditRules.getRule('linkWithUnclearPurpose'); | ||
deepEqual( | ||
rule.run({scope: fixture}), | ||
{ elements: [], result: axs.constants.AuditResult.PASS }); | ||
}); | ||
/* | ||
* This test will need to be reviewed when issue #214 is addressed. | ||
*/ | ||
test('a link with meaningful aria-labelledby is good', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
// Style our link to be visually meaningful with no descendent nodes at all. | ||
fixture.innerHTML = '<style>a.trout::after{content: "Learn more about trout fishing"}</style>'; | ||
var a = document.createElement('a'); | ||
a.href = '#main'; | ||
a.className = 'trout'; | ||
var label = document.createElement('span'); | ||
label.textContent = 'Learn more about trout fishing'; | ||
label.id = "trout" + Date.now(); | ||
a.setAttribute('aria-labelledby', label.id); | ||
fixture.appendChild(a); | ||
fixture.appendChild(label); | ||
var rule = axs.AuditRules.getRule('linkWithUnclearPurpose'); | ||
deepEqual( | ||
rule.run({scope: fixture}), | ||
{ elements: [], result: axs.constants.AuditResult.PASS }); | ||
}); | ||
test('a link without meaningful text is bad', function() { | ||
@@ -45,1 +112,29 @@ var fixture = document.getElementById('qunit-fixture'); | ||
}); | ||
test('a link with bg image and meaningful aria-label is good', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
// Style our link to be visually meaningful with no descendent nodes at all. | ||
fixture.innerHTML = '<style>a.trout{background-image: url("/trout.png"); display:block; height:1em}</style>'; | ||
var a = document.createElement('a'); | ||
a.href = '#main'; | ||
a.className = 'trout'; | ||
a.setAttribute('aria-label', 'Learn more about trout fishing'); | ||
fixture.appendChild(a); | ||
var rule = axs.AuditRules.getRule('linkWithUnclearPurpose'); | ||
deepEqual( | ||
rule.run({scope: fixture}), | ||
{ elements: [], result: axs.constants.AuditResult.PASS }); | ||
}); | ||
test('a hidden link should not be run against the audit', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var a = document.createElement('a'); | ||
a.hidden = true; | ||
a.href = '#main'; | ||
a.textContent = 'Learn more about trout fishing'; | ||
fixture.appendChild(a); | ||
var rule = axs.AuditRules.getRule('linkWithUnclearPurpose'); | ||
deepEqual( | ||
rule.run({scope: fixture}), | ||
{ result: axs.constants.AuditResult.NA }); | ||
}); |
@@ -1,4 +0,4 @@ | ||
module("LowContrast"); | ||
module('LowContrast'); | ||
test("No text = no relevant elements", function() { | ||
test('No text = no relevant elements', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
@@ -15,3 +15,3 @@ var div = document.createElement('div'); | ||
test("Black on white = no problem", function() { | ||
test('Black on white = no problem', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
@@ -29,3 +29,3 @@ var div = document.createElement('div'); | ||
test("Low contrast = fail", function() { | ||
test('Low contrast = fail', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
@@ -43,3 +43,3 @@ var div = document.createElement('div'); | ||
test("Opacity is handled", function() { | ||
test('Opacity is handled', function() { | ||
// Setup fixture | ||
@@ -57,3 +57,3 @@ var fixture = document.getElementById('qunit-fixture'); | ||
test("Uses tolerance value", function() { | ||
test('Uses tolerance value', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
@@ -70,1 +70,125 @@ var div = document.createElement('div'); | ||
}); | ||
test('Disabled button = no relevant elements', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var button = document.createElement('button'); | ||
button.textContent = 'I Can Has Cheezburger?'; | ||
button.setAttribute('disabled', 'disabled'); | ||
fixture.appendChild(button); | ||
deepEqual(axs.AuditRules.getRule('lowContrastElements').run({ scope: fixture }), | ||
{ result: axs.constants.AuditResult.NA }); | ||
}); | ||
test('aria-disabled button = no relevant elements', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var button = document.createElement('button'); | ||
button.textContent = 'I Can Has Cheezburger?'; | ||
button.setAttribute('aria-disabled', 'true'); | ||
fixture.appendChild(button); | ||
deepEqual(axs.AuditRules.getRule('lowContrastElements').run({ scope: fixture }), | ||
{ result: axs.constants.AuditResult.NA }); | ||
}); | ||
test('aria-disabled=false button = pass', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var button = document.createElement('button'); | ||
button.textContent = 'I Can Has Cheezburger?'; | ||
button.setAttribute('aria-disabled', 'false'); | ||
fixture.appendChild(button); | ||
deepEqual( | ||
axs.AuditRules.getRule('lowContrastElements').run({ scope: fixture }), | ||
{ elements: [], result: axs.constants.AuditResult.PASS }); | ||
}); | ||
test('Button in disabled fieldset = no relevant elements', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var container = document.createElement('fieldset'); | ||
container.setAttribute('disabled', 'disabled'); | ||
var button = container.appendChild(document.createElement('button')); | ||
button.textContent = 'I Can Has Cheezburger?'; | ||
fixture.appendChild(container); | ||
deepEqual(axs.AuditRules.getRule('lowContrastElements').run({ scope: fixture }), | ||
{ result: axs.constants.AuditResult.NA }); | ||
}); | ||
test('Button in disabled=false fieldset = no relevant elements', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var container = document.createElement('fieldset'); | ||
container.setAttribute('disabled', 'false'); // check that the value of disabled is irrelevant | ||
var button = container.appendChild(document.createElement('button')); | ||
button.textContent = 'I Can Has Cheezburger?'; | ||
fixture.appendChild(container); | ||
deepEqual(axs.AuditRules.getRule('lowContrastElements').run({ scope: fixture }), | ||
{ result: axs.constants.AuditResult.NA }); | ||
}); | ||
test('Button in aria-disabled container = no relevant elements', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var container = document.createElement('div'); | ||
container.setAttribute('role', 'group'); | ||
container.setAttribute('aria-disabled', 'true'); | ||
var legend = container.appendChild(document.createElement('legend')); | ||
legend.textContent = 'I am Legend'; | ||
var button = container.appendChild(document.createElement('button')); | ||
button.textContent = 'I Can Has Cheezburger?'; | ||
fixture.appendChild(container); | ||
deepEqual(axs.AuditRules.getRule('lowContrastElements').run({ scope: fixture }), | ||
{ result: axs.constants.AuditResult.NA }); | ||
}); | ||
test('Crazy button in disabled fieldset legend test = relevant elements', function() { | ||
/* | ||
* Test this bit of the spec: | ||
* The disabled attribute, when specified, causes all the form control descendants of the fieldset element, | ||
* excluding those that are descendants of the fieldset element's first legend element child, if any, | ||
* to be disabled. | ||
* @see http://www.w3.org/TR/html5/forms.html#attr-fieldset-disabled | ||
*/ | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var container = document.createElement('fieldset'); | ||
container.setAttribute('disabled', 'disabled'); | ||
var legend = container.appendChild(document.createElement('legend')); | ||
var button = legend.appendChild(document.createElement('button')); | ||
button.textContent = 'I Can Has Cheezburger?'; | ||
fixture.appendChild(container); | ||
deepEqual( | ||
axs.AuditRules.getRule('lowContrastElements').run({ scope: fixture }), | ||
{ elements: [], result: axs.constants.AuditResult.PASS }); | ||
}); | ||
test('Even crazier button in disabled fieldset second legend test = no relevant elements', function() { | ||
/* | ||
* Test this bit of the spec: | ||
* The disabled attribute, when specified, causes all the form control descendants of the fieldset element, | ||
* excluding those that are descendants of the fieldset element's first legend element child, if any, | ||
* to be disabled. | ||
* @see http://www.w3.org/TR/html5/forms.html#attr-fieldset-disabled | ||
*/ | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var container = document.createElement('fieldset'); | ||
container.setAttribute('disabled', 'disabled'); | ||
var legend = container.appendChild(document.createElement('legend')); | ||
legend = container.appendChild(document.createElement('legend')); | ||
var button = legend.appendChild(document.createElement('button')); | ||
button.textContent = 'I Can Has Cheezburger?'; | ||
fixture.appendChild(container); | ||
deepEqual(axs.AuditRules.getRule('lowContrastElements').run({ scope: fixture }), | ||
{ result: axs.constants.AuditResult.NA }); | ||
}); | ||
test('Low contrast, disabled on undisableable = fail', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var div = document.createElement('div'); | ||
div.setAttribute('disabled', 'disabled'); | ||
div.setAttribute('role', 'checkbox'); | ||
div.tabIndex = 0; | ||
div.disabled = true; | ||
div.style.backgroundColor = 'white'; | ||
div.style.color = 'white'; | ||
div.textContent = 'Some text'; | ||
fixture.appendChild(div); | ||
deepEqual( | ||
axs.AuditRules.getRule('lowContrastElements').run({ scope: fixture }), | ||
{ elements: [div], result: axs.constants.AuditResult.FAIL } | ||
); | ||
}); |
@@ -31,2 +31,11 @@ // Copyright 2014 Google Inc. | ||
test('Element with no role and global attributes only', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var div = fixture.appendChild(document.createElement('div')); | ||
var expected = { elements: [], result: axs.constants.AuditResult.PASS }; | ||
div.setAttribute('aria-hidden', 'false'); // global | ||
div.setAttribute('aria-label', 'bananas'); // global | ||
deepEqual(rule.run({ scope: fixture }), expected); | ||
}); | ||
/* | ||
@@ -129,2 +138,36 @@ * This rule shouldn't care if required and/or supported roles are missing. | ||
test('Element with no role and global aria- attributes', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var widget = fixture.appendChild(document.createElement('input')); | ||
widget.type = "text"; | ||
widget.setAttribute('aria-label', 'This is my label'); // global | ||
var expected = { elements: [], result: axs.constants.AuditResult.PASS }; | ||
deepEqual(rule.run({ scope: fixture }), expected); | ||
}); | ||
test('Input with no type attribute with no role and aria-required', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var widget = fixture.appendChild(document.createElement('input')); | ||
widget.setAttribute('aria-required', 'true'); | ||
var expected = { elements: [], result: axs.constants.AuditResult.PASS }; | ||
deepEqual(rule.run({ scope: fixture }), expected, 'An input with no type is a textbox'); | ||
}); | ||
test('Input with type=text attribute with no role and aria-required', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var widget = fixture.appendChild(document.createElement('input')); | ||
widget.setAttribute('type', 'text'); | ||
widget.setAttribute('aria-required', 'true'); | ||
var expected = { elements: [], result: axs.constants.AuditResult.PASS }; | ||
deepEqual(rule.run({ scope: fixture }), expected, 'A text input is a textbox'); | ||
}); | ||
test('Input with type=text property with no role and aria-required', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var widget = fixture.appendChild(document.createElement('input')); | ||
widget.type = 'text'; | ||
widget.setAttribute('aria-required', 'true'); | ||
var expected = { elements: [], result: axs.constants.AuditResult.PASS }; | ||
deepEqual(rule.run({ scope: fixture }), expected, 'A text input is a textbox'); | ||
}); | ||
})(); |
@@ -49,2 +49,18 @@ module("AuditConfiguration", { | ||
test("Specify configuration as an object", function() { | ||
var fixtures = document.getElementById('qunit-fixture'); | ||
var configPayload = { | ||
auditRulesToRun: ['badAriaRole'], | ||
scope: fixtures, | ||
maxResults: 1 | ||
}; | ||
var auditConfig = new axs.AuditConfiguration(configPayload); | ||
for (var k in configPayload) { | ||
equal(auditConfig[k], configPayload[k]); | ||
} | ||
}); | ||
test("Configure the number of results returned", function() { | ||
@@ -51,0 +67,0 @@ var fixture = document.getElementById('qunit-fixture'); |
@@ -46,2 +46,22 @@ // Copyright 2012 Google Inc. | ||
test("handles rgba transparent value correctly", function() { | ||
var colorString = 'rgba(0, 0, 0, 0)'; | ||
var color = axs.color.parseColor(colorString); | ||
equal(color.red, 0); | ||
equal(color.blue, 0); | ||
equal(color.green, 0); | ||
equal(color.alpha, 0); | ||
}); | ||
test("handles xbrowser 'transparent' value correctly", function() { | ||
// Firefox, IE11, Project Spartan (MS Edge Release Candidate) | ||
// See #180 https://github.com/GoogleChrome/accessibility-developer-tools/issues/180 | ||
var colorString = 'transparent'; | ||
var color = axs.color.parseColor(colorString); | ||
equal(color.red, 0); | ||
equal(color.blue, 0); | ||
equal(color.green, 0); | ||
equal(color.alpha, 0); | ||
}); | ||
module("suggestColors"); | ||
@@ -48,0 +68,0 @@ test("suggests correct grey values", function() { |
@@ -330,1 +330,259 @@ // Copyright 2012 Google Inc. | ||
}()); | ||
module("isValidNumber"); | ||
test("with integer.", function() { | ||
var actual = axs.utils.isValidNumber("10"); | ||
strictEqual(actual.value, 10, "Integer should be parsed"); | ||
ok(actual.valid, "Integer should be valid"); | ||
}); | ||
test("with leading and trailing whitespace.", function() { | ||
var actual = axs.utils.isValidNumber(" 10 "); | ||
strictEqual(actual.value, 10, "Integer should be parsed"); | ||
ok(actual.valid, "Integer should be valid"); | ||
}); | ||
test("with zero.", function() { | ||
var actual = axs.utils.isValidNumber("0"); | ||
strictEqual(actual.value, 0, "Zero should be parsed"); | ||
ok(actual.valid, "Integer should be valid"); | ||
}); | ||
test("with leading zero integer.", function() { | ||
var actual = axs.utils.isValidNumber("09"); | ||
strictEqual(actual.value, 9, "Integer should be parsed"); | ||
ok(actual.valid, "Integer should be valid"); | ||
}); | ||
test("with multiple leading zero integer.", function() { | ||
var actual = axs.utils.isValidNumber("000000009"); | ||
strictEqual(actual.value, 9, "Integer should be parsed"); | ||
ok(actual.valid, "Integer should be valid"); | ||
}); | ||
test("with leading zero positivie integer.", function() { | ||
var actual = axs.utils.isValidNumber("+09"); | ||
strictEqual(actual.value, 9, "Integer should be parsed"); | ||
ok(actual.valid, "Integer should be valid"); | ||
}); | ||
test("with leading zero negative integer.", function() { | ||
var actual = axs.utils.isValidNumber("-09"); | ||
strictEqual(actual.value, -9, "Integer should be parsed"); | ||
ok(actual.valid, "Integer should be valid"); | ||
}); | ||
test("with string starting with number.", function() { | ||
var actual = axs.utils.isValidNumber("10 foo"); | ||
equal(actual.valid, false, "String that starts with a number is not valid"); | ||
ok(actual.reason, "There should be a reason"); | ||
}); | ||
test("with true.", function() { | ||
var actual = axs.utils.isValidNumber("true"); | ||
equal(actual.valid, false, "boolean is not valid"); | ||
ok(actual.reason, "There should be a reason"); | ||
}); | ||
test("with true, leading and trailing space.", function() { | ||
var actual = axs.utils.isValidNumber(" true "); | ||
equal(actual.valid, false, "boolean is not valid"); | ||
ok(actual.reason, "There should be a reason"); | ||
}); | ||
test("with false.", function() { | ||
var actual = axs.utils.isValidNumber("false"); | ||
equal(actual.valid, false, "boolean is not valid"); | ||
ok(actual.reason, "There should be a reason"); | ||
}); | ||
test("with hexadecimal.", function() { | ||
var actual = axs.utils.isValidNumber("0xF"); | ||
equal(actual.valid, false, "Hexadecimal is not valid"); | ||
ok(actual.reason, "There should be a reason"); | ||
}); | ||
test("with float.", function() { | ||
var actual = axs.utils.isValidNumber("0.5"); | ||
strictEqual(actual.value, 0.5, "Float should be parsed"); | ||
ok(actual.valid, "Float should be valid"); | ||
}); | ||
test("with float with no integer part.", function() { | ||
var actual = axs.utils.isValidNumber(".5"); | ||
strictEqual(actual.value, .5, "Float should be parsed"); | ||
ok(actual.valid, "Float should be valid"); | ||
}); | ||
test("with negative integer.", function() { | ||
var actual = axs.utils.isValidNumber("-100"); | ||
strictEqual(actual.value, -100, "Negative should be parsed"); | ||
ok(actual.valid, "Negative should be valid"); | ||
}); | ||
test("with negative float.", function() { | ||
var actual = axs.utils.isValidNumber("-1.5"); | ||
strictEqual(actual.value, -1.5, "Negative should be parsed"); | ||
ok(actual.valid, "Negative should be valid"); | ||
}); | ||
test("with positive integer.", function() { | ||
var actual = axs.utils.isValidNumber("+100"); | ||
strictEqual(actual.value, 100, "Positive should be parsed"); | ||
ok(actual.valid, "Positive should be valid"); | ||
}); | ||
test("with positive float.", function() { | ||
var actual = axs.utils.isValidNumber("+1.5"); | ||
strictEqual(actual.value, 1.5, "Positive should be parsed"); | ||
ok(actual.valid, "Positive should be valid"); | ||
}); | ||
test("with Infinity.", function() { | ||
var actual = axs.utils.isValidNumber("Infinity"); | ||
equal(actual.valid, false, "Infinity is not a real number"); | ||
ok(actual.reason, "There should be a reason"); | ||
}); | ||
test("with -Infinity.", function() { | ||
var actual = axs.utils.isValidNumber("-Infinity"); | ||
equal(actual.valid, false, "-Infinity is not a real number"); | ||
ok(actual.reason, "There should be a reason"); | ||
}); | ||
module('isElementDisabled', { | ||
setup: function () { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var html = '<fieldset><legend>I am Legend<input/><span tabindex="0" role="checkbox"/></legend>'; | ||
html += '<legend>I am Legend Too<input/><span tabindex="0" role="checkbox"/></legend>'; | ||
html += '<input/><span tabindex="0" role="checkbox"/></fieldset>'; | ||
fixture.innerHTML = html; | ||
} | ||
}); | ||
test('nothing disabled', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var widget = fixture.querySelector('fieldset>input'); | ||
strictEqual(axs.utils.isElementDisabled(widget), false); | ||
}); | ||
test('form control natively disabled', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var widget = fixture.querySelector('fieldset>input'); | ||
widget.setAttribute('disabled', 'false'); // also testing that disabled false is the same as disabled true | ||
strictEqual(axs.utils.isElementDisabled(widget), true); | ||
}); | ||
test('form control aria-disabled', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var widget = fixture.querySelector('fieldset>input'); | ||
widget.setAttribute('aria-disabled', 'true'); | ||
strictEqual(axs.utils.isElementDisabled(widget), true); | ||
}); | ||
test('form control aria-disabled false', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var widget = fixture.querySelector('fieldset>input'); | ||
widget.setAttribute('aria-disabled', 'false'); | ||
strictEqual(axs.utils.isElementDisabled(widget), false); | ||
}); | ||
test('ARIA widget erroneously disabled', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var widget = fixture.querySelector('fieldset>[role]'); | ||
widget.setAttribute('disabled', 'disabled'); | ||
strictEqual(axs.utils.isElementDisabled(widget), false); | ||
}); | ||
test('ARIA widget aria-disabled', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var widget = fixture.querySelector('fieldset>[role]'); | ||
widget.setAttribute('aria-disabled', 'true'); | ||
strictEqual(axs.utils.isElementDisabled(widget), true); | ||
}); | ||
test('container natively disabled', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var container = fixture.querySelector('fieldset'); | ||
container.setAttribute('disabled', 'disabled'); | ||
var widget = container.querySelector('fieldset>input'); | ||
var actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, true, 'form control '); | ||
widget = container.querySelector('legend:first-of-type>input'); | ||
actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, false, 'control in legend should not be disabled'); | ||
widget = container.querySelector('legend:nth-of-type(2)>input'); | ||
actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, true, 'control in 2nd legend should be disabled'); | ||
widget = container.querySelector('fieldset>[role]'); | ||
actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, false, 'ARIA widget should not be disabled'); | ||
widget = container.querySelector('legend:first-of-type [role]'); | ||
actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, false, 'ARIA widget in legend should not be disabled'); | ||
widget = container.querySelector('legend:nth-of-type(2) [role]'); | ||
actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, false, 'ARIA widget in 2nd legend should not be disabled'); | ||
}); | ||
(function() { | ||
test('container aria-disabled', function() { | ||
ariaDisabledOnContainerHelper(true); | ||
}); | ||
test('container aria-disabled=false', function() { | ||
ariaDisabledOnContainerHelper(false); | ||
}); | ||
function ariaDisabledOnContainerHelper(ariaDisabled) { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var container = fixture.querySelector('fieldset'); | ||
container.setAttribute('aria-disabled', ariaDisabled); | ||
var widget = container.querySelector('fieldset>input'); | ||
var actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, ariaDisabled, 'form control'); | ||
widget = container.querySelector('legend:first-of-type>input'); | ||
actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, ariaDisabled, 'control in legend'); | ||
widget = container.querySelector('legend:nth-of-type(2)>input'); | ||
actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, ariaDisabled, 'control in 2nd legend'); | ||
widget = container.querySelector('fieldset [role]'); | ||
actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, ariaDisabled, 'ARIA widget'); | ||
widget = container.querySelector('legend:first-of-type [role]'); | ||
actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, ariaDisabled, 'ARIA widget in legend'); | ||
widget = container.querySelector('legend:nth-of-type(2) [role]'); | ||
actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, ariaDisabled, 'ARIA widget in 2nd legend'); | ||
} | ||
})(); | ||
test('first fieldset legend aria-disabled', function() { | ||
var fixture = document.getElementById('qunit-fixture'); | ||
var container = fixture.querySelector('legend:first-of-type'); | ||
container.setAttribute('aria-disabled', 'true'); | ||
var widget = container.querySelector('input'); | ||
var actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, true, 'form control should be disabled'); | ||
widget = container.querySelector('[role]'); | ||
actual = axs.utils.isElementDisabled(widget); | ||
strictEqual(actual, true, 'ARIA widget should be disabled'); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
600948
92
13691
212
0
14
1