From 2e650789bb16edda6a967ee43a7299540691e9b7 Mon Sep 17 00:00:00 2001 From: Addy Osmani Date: Mon, 11 May 2015 00:04:43 +0100 Subject: [PATCH 01/11] Update version to 2.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9557995e..de320f0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "accessibility-developer-tools", - "version": "2.6.0", + "version": "2.7.0", "repository": { "type": "git", "url": "http://github.com/GoogleChrome/accessibility-developer-tools" From 4fc456f957e79237deb2e4050064843b225e8883 Mon Sep 17 00:00:00 2001 From: Addy Osmani Date: Mon, 11 May 2015 00:05:06 +0100 Subject: [PATCH 02/11] Build new release to dist/js/axs_testing All tests passing. --- dist/js/axs_testing.js | 683 ++++++++++++++++++++++++++++++----------- 1 file changed, 511 insertions(+), 172 deletions(-) diff --git a/dist/js/axs_testing.js b/dist/js/axs_testing.js index f0e6f4a3..7236b238 100644 --- a/dist/js/axs_testing.js +++ b/dist/js/axs_testing.js @@ -1,5 +1,5 @@ /* - * Copyright 2014 Google Inc. + * Copyright 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Generated from http://github.com/GoogleChrome/accessibility-developer-tools/tree/3d4893b4ecd0eb8f4765e04479213d04b240f3e0 + * Generated from http://github.com/GoogleChrome/accessibility-developer-tools/tree/a33b34feb4bf5c6990c9d88f98c3c8e3115168ab * * See project README for build steps. */ @@ -62,7 +62,7 @@ goog.setTestOnly = function(a) { goog.forwardDeclare = function(a) { }; COMPILED || (goog.isProvided_ = function(a) { - return!goog.implicitNamespaces_[a] && goog.isDefAndNotNull(goog.getObjectByName(a)); + return !goog.implicitNamespaces_[a] && goog.isDefAndNotNull(goog.getObjectByName(a)); }, goog.implicitNamespaces_ = {}); goog.getObjectByName = function(a, b) { for (var c = a.split("."), d = b || goog.global, e;e = c.shift();) { @@ -153,14 +153,14 @@ goog.DEPENDENCIES_ENABLED && (goog.included_ = {}, goog.dependencies_ = {pathToN var b = goog.global.document; if ("complete" == b.readyState) { if (/\bdeps.js$/.test(a)) { - return!1; + return !1; } throw Error('Cannot write "' + a + '" after document load'); } b.write(' + diff --git a/test/js/properties-test.js b/test/js/properties-test.js index c96b59d1..f023bd6f 100644 --- a/test/js/properties-test.js +++ b/test/js/properties-test.js @@ -32,6 +32,66 @@ test('returns the calculated text alternative for the given element', function() ok(false, 'Threw exception'); } }); +test('Image with no text alternative', function() { + var fixture = document.getElementById('qunit-fixture'); + var img = fixture.appendChild(document.createElement('img')); + img.src = 'smile.jpg'; + var textAlternatives = {}; + axs.properties.findTextAlternatives(img, textAlternatives); + equal(Object.keys(textAlternatives).length, 0, 'Image has no text alternative'); +}); + +test('Image with alt text', function() { + var fixture = document.getElementById('qunit-fixture'); + var img = fixture.appendChild(document.createElement('img')); + img.src = 'smile.jpg'; + img.alt = 'Smile!'; + var textAlternatives = {}; + axs.properties.findTextAlternatives(img, textAlternatives); + equal(Object.keys(textAlternatives).length, 1, 'exactly one text alternative'); + equal('alt' in textAlternatives, true, 'alt in textAlternatives'); + equal('Smile!', textAlternatives.alt.text); +}); + +test('Image with aria label', function() { + var fixture = document.getElementById('qunit-fixture'); + var img = fixture.appendChild(document.createElement('img')); + img.src = 'smile.jpg'; + img.setAttribute('aria-label', 'Smile!'); + var textAlternatives = {}; + axs.properties.findTextAlternatives(img, textAlternatives); + equal(Object.keys(textAlternatives).length, 1, 'exactly one text alternative'); + equal('ariaLabel' in textAlternatives, true, 'ariaLabel in textAlternatives'); + equal('Smile!', textAlternatives.ariaLabel.text); +}); + +test('Image with aria labelledby', function() { + var fixture = document.getElementById('qunit-fixture'); + var img = fixture.appendChild(document.createElement('img')); + img.src = 'smile.jpg'; + var label = fixture.appendChild(document.createElement('div')); + label.textContent = 'Smile!'; + label.id = 'label'; + img.setAttribute('aria-labelledby', 'label'); + var textAlternatives = {}; + axs.properties.findTextAlternatives(img, textAlternatives); + equal(Object.keys(textAlternatives).length, 1, 'exactly one text alternative'); + equal('ariaLabelledby' in textAlternatives, true, 'ariaLabelledby in textAlternatives'); + equal('Smile!', textAlternatives.ariaLabelledby.text); +}); + +test('Image with title', function() { + var fixture = document.getElementById('qunit-fixture'); + var img = fixture.appendChild(document.createElement('img')); + img.src = 'smile.jpg'; + img.setAttribute('title', 'Smile!'); + var textAlternatives = {}; + axs.properties.findTextAlternatives(img, textAlternatives); + equal(Object.keys(textAlternatives).length, 1, 'exactly one text alternative'); + equal('title' in textAlternatives, true, 'title in textAlternatives'); + equal('Smile!', textAlternatives.title.text); +}); + module('getTextFromHostLanguageAttributes', { setup: function () { @@ -154,3 +214,17 @@ test('get implicit role for li descendant of ul', function() { var actual = axs.properties.getImplicitRole(element); strictEqual(actual, ''); }); + +module('getTextProperties', {}); +test('Image with no text alternative', function() { + var fixture = document.getElementById('qunit-fixture'); + var img = fixture.appendChild(document.createElement('img')); + img.src = 'smile.jpg'; + var textProperties = axs.properties.getTextProperties(img); + equal('alt' in textProperties, true, 'alt in textProperties'); + equal(textProperties.alt.valid, false, 'alt is not valid'); + equal('filename' in textProperties, true, 'filename in textProperties'); + equal(textProperties.filename.text, 'smile.jpg'); + equal('computedText' in textProperties, true, 'computedText in textProperties'); + equal(textProperties.computedText, 'smile.jpg'); +}); From 74d2f758639c0302433109bec8a58a0a5790f500 Mon Sep 17 00:00:00 2001 From: Xavier Spriet Date: Wed, 3 Jun 2015 15:12:49 -0400 Subject: [PATCH 05/11] Add bower config * Once this is merged, the module can be registered into the bower registry. * Fixes #157 --- bower.json | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 bower.json diff --git a/bower.json b/bower.json new file mode 100644 index 00000000..2611fe5a --- /dev/null +++ b/bower.json @@ -0,0 +1,29 @@ +{ + "name": "accessibility-developer-tools", + "version": "2.7.0", + "homepage": "https://github.com/GoogleChrome/accessibility-developer-tools", + "authors": [ + "Google" + ], + "description": "This is a library of accessibility-related testing and utility code.", + "main": "dist/js/axs_testing.js", + "moduleType": [ + "globals" + ], + "keywords": [ + "accessibility", + "testing", + "WCAG" + ], + "license": "Apache License 2.0", + "ignore": [ + "**/.*", + "lib", + "scripts", + "src", + "test", + "tools", + "Gruntfile.js", + "package.json" + ] +} From 92a4dc192c3497457c66acf802a6cbdeba882418 Mon Sep 17 00:00:00 2001 From: Alice Boxhall Date: Mon, 8 Jun 2015 07:58:44 -0700 Subject: [PATCH 06/11] Add changelog --- Changelog.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Changelog.md diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 00000000..836c3cb2 --- /dev/null +++ b/Changelog.md @@ -0,0 +1,59 @@ +## 2.7.0 - 2015-05-15 + +### New rules +* This element does not support ARIA roles, states and properties (`src/audits/AriaOnReservedElement.js`) +* aria-owns should not be used if ownership is implicit in the DOM (`src/audits/AriaOwnsDescendant.js`) +* Elements with ARIA roles must be in the correct scope (`src/audits/AriaRoleNotScoped.js`) +* An element's ID must be unique in the DOM (`src/audits/DuplicateId.js`) +* The web page should have the content's human language indicated in the markup (`src/audits/HumanLangMissing.js`) +* An element's ID must not be present in more that one aria-owns attribute at any time (`src/audits/MultipleAriaOwners.js`) +* ARIA attributes which refer to other elements by ID should refer to elements which exist in the DOM (`src/audits/NonExistentAriaRelatedElement.js` - previously `src/audits/NonExistentAriaLabeledBy.js`) +* Elements with ARIA roles must ensure required owned elements are present (`src/audits/RequiredOwnedAriaRoleMissing.js`) +* Avoid positive integer values for tabIndex (`src/audits/TabIndexGreaterThanZero.js`) +* This element has an unsupported ARIA attribute (`src/audits/UnsupportedAriaAttribute.js`) + +### Enhancements: +* Add configurable blacklist phrases and stop words to LinkWithUnclearPurpose (#99) +* Detect and warn if we reuse the same code for more than one rule. (#133) +* Force focus before testing visibility on focusable elements. (#65) +* Use getDistributedNodes to get nodes distributed into shadowRoots (#128) +* Add section to Audit Rules page for HumanLangMissing and link to it from rule (#119) +* Reference "applied role" in axs.utils.getRoles enhancement (#130) +* Add warning that AX_FOCUS_02 is not available from axs.Audit.run() (#85) + +### Bug fixes: +* Incorrect use of nth-of-type against className in utils.getQuerySelectorText (#87) +* AX_TEXT_01 Accessibility Audit test should probably ignore role=presentation elements (#97) +* Fix path to audit rules in phantomjs runner (#108) +* Label audit should fail if form fields lack a label, even with placeholder text (#81) +* False positives for controls without labels with role=presentation (#23) +* Fix "valid" flag on return value of axs.utils.getRoles (#131) + +Note: this version number is somewhat arbitrary - just bringing it vaguely in line with [the extension](https://github.com/GoogleChrome/accessibility-developer-tools-extension) since that's where the library originated - but will use semver for version bumps going forward from here. + +## 0.0.5 - 2014-02-04 + +### Enhancements: +* overlapping elements detection code made more sophisticated +* axs.properties.getFocusProperties() returns more information about visibility +* new axs.properties.hasDirectTextDescendant() method with more sophisticated detection of text content + +### Bug fixes: +* FocusableElementNotVisibleAndNotAriaHidden audit passes on elements which are brought onscreen on focus +* UnfocusableElementsWithOnclick checks for element.disabled +* Fix infinite loop when getting descendant text content of a label containing an input +* Detect elements which are out of scroll area of any parent element, not just the document scroll area +* findTextAlternatives doesn't throw TypeError if used on a HTMLSelectElement + +## 0.0.4 - 2013-10-03 + +### Enhancements: + +* axs.AuditRule.run() has a new signature: it now takes an options object. Please see method documentation for details. +* Audit Rule severity can be overridden (per Audit Rule) in AuditConfig. + +### Bug fixes: + +* axs.utils.isLowContrast() now rounds to the nearest 0.1 before checking (so `#777` is now a passing value) +* MainRoleOnInappropriateElement was always failing due to accessible name calculation taking the main role into account and not descending into content (now just gets descendant content directly) +* UnfocusableElementsWithOnClick had a dangling if-statement causing very noisy false positives From c30d11d0a33fccaa3c43e02a922bc4804b862d43 Mon Sep 17 00:00:00 2001 From: Alice Boxhall Date: Fri, 12 Jun 2015 10:27:07 -0700 Subject: [PATCH 07/11] Bring Changelog up to date --- Changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Changelog.md b/Changelog.md index 836c3cb2..e1290cb4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,10 @@ +### Enhancements: +* Rework findTextAlternatives not to return non-exposed text alternatives. +* Add Bower config (#157) + +### Bug fixes: +* Check for any text alternatives when assessing unlabeled images (#154). + ## 2.7.0 - 2015-05-15 ### New rules From 9f8a64e57054d5a071841ddb232104df818f6d29 Mon Sep 17 00:00:00 2001 From: Xavier Spriet Date: Fri, 5 Jun 2015 11:37:25 -0400 Subject: [PATCH 08/11] Gruntfile Enhancements * Rename type -> releaseType in gh-release task * Fix default git remote * Add prereleaseName bump option * Ensure release type has been provided * Push Github releases through grunt * Use the Github API to create a release against release info found in `gh-release` config. * Mark the new release as draft for manual review. * Add GH release task to `release` task chain. * Add interactive release configuration * Add prompts with sensible defaults for git remote, github credentials, and github repo name. * Use config options extracted from prompt responses where appropriate. * Reimport dist bundle. * Add grunt `dist` task. * Fix markdown header level for changelog * Use `##` instead of `###` for release header entries. * Add `clean:all` dependency to `build` task. * Miscelaneous Gruntfile cleanups. * Normalize string quoting. * Move build path for `axs_testing.js` to `.tmp/build/` * Add changelog filename as a grunt config option * Parse `package.json` and store it in the `grunt` config. * Use `git-contrib-copy` instead of `grunt.file.copy` paired with a custom task. * Build in `.tmp/build` instead of `gen`. * Add `.tmp` to `.gitignore`. * Remove `git-describe` config since that only applies when using the `git-describe` module, not a custom task. * Use `grunt.util.spawn` in `git-describe` task and nuke dependency to `grunt-util-spawn` module. * Break down grunt tasks into atomic tasks (`build`, `test:unit`, `travis`, `default` and `release`). * Set up load-grunt-tasks --- .gitignore | 1 + Gruntfile.js | 227 ++++++++++++++++++++++++++++++++++++++--------- dist/js/.gitkeep | 0 package.json | 7 +- 4 files changed, 190 insertions(+), 45 deletions(-) delete mode 100644 dist/js/.gitkeep diff --git a/.gitignore b/.gitignore index 955852e2..665cbb82 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ extension/Handlebar.js upload.py gen/ node_modules/ +.tmp/ diff --git a/Gruntfile.js b/Gruntfile.js index bd90d175..9bda6c22 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,63 +1,188 @@ -'use strict'; +var request = require('superagent'); module.exports = function(grunt) { + 'use strict'; + + require('load-grunt-tasks')(grunt); + grunt.initConfig({ - 'git-describe': { - options: { + pkg: grunt.file.readJSON('package.json'), + changelog: 'CHANGELOG.md', + + 'gh-release': {}, - }, - 'run': {} - }, closurecompiler: { minify: { requiresConfig: 'git-revision', files: { - "gen/axs_testing.js": [ - "./lib/closure-library/closure/goog/base.js", - "./src/js/axs.js", - "./src/js/BrowserUtils.js", - "./src/js/Constants.js", - "./src/js/AccessibilityUtils.js", - "./src/js/Properties.js", - "./src/js/AuditRule.js", - "./src/js/AuditRules.js", - "./src/js/AuditResults.js", - "./src/js/Audit.js", - "./src/audits/*" + '.tmp/build/axs_testing.js': [ + './lib/closure-library/closure/goog/base.js', + './src/js/axs.js', + './src/js/BrowserUtils.js', + './src/js/Constants.js', + './src/js/AccessibilityUtils.js', + './src/js/Properties.js', + './src/js/AuditRule.js', + './src/js/AuditRules.js', + './src/js/AuditResults.js', + './src/js/Audit.js', + './src/audits/*' ] }, options: { - "language_in": "ECMASCRIPT5", - "formatting": "PRETTY_PRINT", - "summary_detail_level": 3, - "warning_level": "VERBOSE", - "compilation_level": "SIMPLE_OPTIMIZATIONS", - "output_wrapper": "<%= grunt.file.read('scripts/output_wrapper.txt') %>", - "externs": "./src/js/externs/externs.js" + 'language_in': 'ECMASCRIPT5', + 'formatting': 'PRETTY_PRINT', + 'summary_detail_level': 3, + 'warning_level': 'VERBOSE', + 'compilation_level': 'SIMPLE_OPTIMIZATIONS', + 'output_wrapper': "<%= grunt.file.read('scripts/output_wrapper.txt') %>", + 'externs': './src/js/externs/externs.js' } } }, + qunit: { all: ['test/index.html'] + }, + + copy: { + dist: { + expand: true, + cwd: '.tmp/build', + src: '**/*', + dest: 'dist/js' + } + }, + + clean: { + all: ['.tmp', 'dist'] + }, + + bump: { + options: { + prereleaseName: 'rc', + files: ['package.json', 'bower.json'], + updateConfigs: ['pkg'], + pushTo: "<%= grunt.config.get('gh-release.remote') %>", + commitFiles: ['package.json', "<%= grunt.config.get('changelog') %>", 'bower.json', 'dist'] + } + }, + + prompt: { + 'gh-release': { + options: { + questions: [ + { + config: 'gh-release.remote', + type: 'input', + message: 'Git Remote (usually upstream or origin)', + default: 'upstream', + validate: function(val) { + return (grunt.util._.size(val) > 0); + } + }, + { + config: 'gh-release.repo', + type: 'input', + message: 'Github Repository', + default: 'GoogleChrome/accessibility-developer-tools', + validate: function(val) { + return (grunt.util._.size(val) > 0); + } + }, + { + config: 'gh-release.username', + type: 'input', + message: 'Github Username', + validate: function(val) { + return (grunt.util._.size(val) > 0); + } + }, + { + config: 'gh-release.password', + type: 'password', + message: 'Github Password or Token', + validate: function(val) { + return (grunt.util._.size(val) > 0); + } + } + ] + } + } } }); - grunt.loadNpmTasks('grunt-closurecompiler'); - grunt.loadNpmTasks('grunt-contrib-qunit'); + grunt.registerTask('changelog', function(type) { + grunt.task.requires('bump-only:' + type); - grunt.registerTask('git-describe', function() { - var _spawn = require("grunt-util-spawn")(grunt); + var config = { + data: { + version: grunt.config.get('pkg.version'), + releaseDate: grunt.template.today("yyyy-mm-dd") + } + }; + var stopRegex = /^\#\#\ [0-9]+.*$/m; + var stopIndex = 0; + var releaseNotes = ''; + var dest = grunt.config.get('changelog'); + var contents = grunt.file.read(dest); + var headerTpl = "## <%= version %> - <%= releaseDate %>\n\n"; + var header = grunt.template.process(headerTpl, config); + + if (contents.length > 0) { + if ((stopIndex = contents.search(stopRegex)) !== -1) { + releaseNotes = contents.slice(0, stopIndex); + } + } + + grunt.config.set("gh-release.release-notes", releaseNotes); + + grunt.file.write(dest, "" + header + contents); + grunt.log.ok("Changelog updated, and release notes extracted."); + }); + + grunt.registerTask('gh-release', function() { + var config = grunt.config.get('gh-release'); + var pkg = grunt.config.get('pkg'); + var done = this.async(); + + request + .post('https://api.github.com/repos/' + config.repo + '/releases') + .auth(config.username, config.password) + .set('Accept', 'application/vnd.github.v3') + .set('User-Agent', 'grunt') + .send({ + 'tag_name': 'v' + pkg.version, + name: pkg.version, + body: config['release-notes'], + draft: true + }) + .end(function(err, res){ + if (typeof err !== "undefined" && err !== null) { + grunt.fail.warn('Error encountered while creating Github release.', err); + } + + if (res.statusCode === 201){ + grunt.log.ok('Github release created'); + done(); + } else { + grunt.fail.warn('Unable to create github release.', res.text); + } + }); + }); + + grunt.registerTask('git-describe', function() { // Start async task var done = this.async(); - _spawn({ - "cmd" : "git", - "args" : [ "rev-parse", "HEAD" ], - "opts" : { - "cwd" : "." + grunt.util.spawn({ + 'cmd' : 'git', + 'args' : [ 'rev-parse', 'HEAD' ], + 'opts' : { + 'cwd' : '.' } - }, function(err, result) { + }, function(err, result) { if (err) { grunt.log.error(err).verbose.error(result); done(); @@ -69,20 +194,34 @@ module.exports = function(grunt) { }); }); + grunt.registerTask('release', function(releaseType) { + if (typeof releaseType === 'undefined' || releaseType === null) { + grunt.fail.fatal('You must specify a release type. i.e. grunt release:prerelease'); + } + + grunt.task.run([ + 'prompt:gh-release', + 'build', + 'test:unit', + 'copy:dist', + 'bump-only:' + releaseType, + 'changelog:' + releaseType, + 'bump-commit', + 'gh-release' + ]); + }); + grunt.registerTask('save-revision', function() { grunt.event.once('git-describe', function (rev) { - grunt.log.writeln("Git Revision: " + rev); + grunt.log.writeln('Git Revision: ' + rev); grunt.config.set('git-revision', rev); }); grunt.task.run('git-describe'); }); - grunt.registerTask('copy-dist', function() { - grunt.file.copy('gen/axs_testing.js', 'dist/js/axs_testing.js'); - }); - - grunt.registerTask('default', ['save-revision', 'closurecompiler:minify', 'qunit']); - grunt.registerTask('build', ['default', 'copy-dist']); - grunt.registerTask('travis', ['closurecompiler:minify', 'qunit']); + grunt.registerTask('build', ['clean:all', 'save-revision', 'closurecompiler:minify']); + grunt.registerTask('test:unit', ['qunit']); + grunt.registerTask('dist', ['build', 'copy:dist']); + grunt.registerTask('travis', ['closurecompiler:minify', 'test:unit']); + grunt.registerTask('default', ['build', 'test:unit']); }; - diff --git a/dist/js/.gitkeep b/dist/js/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/package.json b/package.json index 66ef555d..74650867 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,15 @@ }, "devDependencies": { "grunt": "^0.4.5", + "grunt-bump": "^0.3.1", "grunt-cli": "^0.1.13", "grunt-closurecompiler": "^0.9.9", + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-copy": "^0.8.0", "grunt-contrib-qunit": "^0.7.0", - "grunt-util-spawn": "^0.0.2" + "grunt-prompt": "^1.3.0", + "load-grunt-tasks": "^3.2.0", + "superagent": "^1.2.0" }, "scripts": { "test": "grunt travis --verbose" From cafca561161f2d0af3d6f5a86d8dde9ed57d049c Mon Sep 17 00:00:00 2001 From: Alice Boxhall Date: Mon, 8 Jun 2015 07:58:44 -0700 Subject: [PATCH 09/11] Add changelog --- Changelog.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Changelog.md diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 00000000..836c3cb2 --- /dev/null +++ b/Changelog.md @@ -0,0 +1,59 @@ +## 2.7.0 - 2015-05-15 + +### New rules +* This element does not support ARIA roles, states and properties (`src/audits/AriaOnReservedElement.js`) +* aria-owns should not be used if ownership is implicit in the DOM (`src/audits/AriaOwnsDescendant.js`) +* Elements with ARIA roles must be in the correct scope (`src/audits/AriaRoleNotScoped.js`) +* An element's ID must be unique in the DOM (`src/audits/DuplicateId.js`) +* The web page should have the content's human language indicated in the markup (`src/audits/HumanLangMissing.js`) +* An element's ID must not be present in more that one aria-owns attribute at any time (`src/audits/MultipleAriaOwners.js`) +* ARIA attributes which refer to other elements by ID should refer to elements which exist in the DOM (`src/audits/NonExistentAriaRelatedElement.js` - previously `src/audits/NonExistentAriaLabeledBy.js`) +* Elements with ARIA roles must ensure required owned elements are present (`src/audits/RequiredOwnedAriaRoleMissing.js`) +* Avoid positive integer values for tabIndex (`src/audits/TabIndexGreaterThanZero.js`) +* This element has an unsupported ARIA attribute (`src/audits/UnsupportedAriaAttribute.js`) + +### Enhancements: +* Add configurable blacklist phrases and stop words to LinkWithUnclearPurpose (#99) +* Detect and warn if we reuse the same code for more than one rule. (#133) +* Force focus before testing visibility on focusable elements. (#65) +* Use getDistributedNodes to get nodes distributed into shadowRoots (#128) +* Add section to Audit Rules page for HumanLangMissing and link to it from rule (#119) +* Reference "applied role" in axs.utils.getRoles enhancement (#130) +* Add warning that AX_FOCUS_02 is not available from axs.Audit.run() (#85) + +### Bug fixes: +* Incorrect use of nth-of-type against className in utils.getQuerySelectorText (#87) +* AX_TEXT_01 Accessibility Audit test should probably ignore role=presentation elements (#97) +* Fix path to audit rules in phantomjs runner (#108) +* Label audit should fail if form fields lack a label, even with placeholder text (#81) +* False positives for controls without labels with role=presentation (#23) +* Fix "valid" flag on return value of axs.utils.getRoles (#131) + +Note: this version number is somewhat arbitrary - just bringing it vaguely in line with [the extension](https://github.com/GoogleChrome/accessibility-developer-tools-extension) since that's where the library originated - but will use semver for version bumps going forward from here. + +## 0.0.5 - 2014-02-04 + +### Enhancements: +* overlapping elements detection code made more sophisticated +* axs.properties.getFocusProperties() returns more information about visibility +* new axs.properties.hasDirectTextDescendant() method with more sophisticated detection of text content + +### Bug fixes: +* FocusableElementNotVisibleAndNotAriaHidden audit passes on elements which are brought onscreen on focus +* UnfocusableElementsWithOnclick checks for element.disabled +* Fix infinite loop when getting descendant text content of a label containing an input +* Detect elements which are out of scroll area of any parent element, not just the document scroll area +* findTextAlternatives doesn't throw TypeError if used on a HTMLSelectElement + +## 0.0.4 - 2013-10-03 + +### Enhancements: + +* axs.AuditRule.run() has a new signature: it now takes an options object. Please see method documentation for details. +* Audit Rule severity can be overridden (per Audit Rule) in AuditConfig. + +### Bug fixes: + +* axs.utils.isLowContrast() now rounds to the nearest 0.1 before checking (so `#777` is now a passing value) +* MainRoleOnInappropriateElement was always failing due to accessible name calculation taking the main role into account and not descending into content (now just gets descendant content directly) +* UnfocusableElementsWithOnClick had a dangling if-statement causing very noisy false positives From 96eb419f93e4b628345edff480887dcb381a7b68 Mon Sep 17 00:00:00 2001 From: Xavier Spriet Date: Fri, 12 Jun 2015 17:16:25 -0400 Subject: [PATCH 10/11] Implement GH release management class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement a quick promise-based interface for dealing with the Github v3 API’s `releases` management endpoints. * Most of the methods are standard mappings to the API, except for `getReleaseByName` which needs to iterate through the releases list because of a bug in the Github API. * Add GHRepo compile task. * Manage Github releases through GHRepo. --- Gruntfile.js | 66 +++++++++++++++++++++++++-------------- package.json | 2 ++ src/util/gh_repo.coffee | 69 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 src/util/gh_repo.coffee diff --git a/Gruntfile.js b/Gruntfile.js index 9bda6c22..e13ff852 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,5 +1,3 @@ -var request = require('superagent'); - module.exports = function(grunt) { 'use strict'; @@ -7,7 +5,7 @@ module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), - changelog: 'CHANGELOG.md', + changelog: 'Changelog.md', 'gh-release': {}, @@ -68,6 +66,14 @@ module.exports = function(grunt) { } }, + coffee: { + compile: { + files: { + '.tmp/util/gh_repo.js': 'src/util/gh_repo.coffee' + } + } + }, + prompt: { 'gh-release': { options: { @@ -143,32 +149,43 @@ module.exports = function(grunt) { }); grunt.registerTask('gh-release', function() { + // Compile and load GH Repo manager. + grunt.task.requires('coffee:compile'); + var GHRepo = require('./.tmp/util/gh_repo'); + + var done = this.async(); var config = grunt.config.get('gh-release'); var pkg = grunt.config.get('pkg'); - var done = this.async(); - - request - .post('https://api.github.com/repos/' + config.repo + '/releases') - .auth(config.username, config.password) - .set('Accept', 'application/vnd.github.v3') - .set('User-Agent', 'grunt') - .send({ - 'tag_name': 'v' + pkg.version, - name: pkg.version, - body: config['release-notes'], - draft: true - }) - .end(function(err, res){ - if (typeof err !== "undefined" && err !== null) { - grunt.fail.warn('Error encountered while creating Github release.', err); - } + var currentRelease = 'v' + pkg.version; + var nextRelease = currentRelease.replace(/-rc\.[0-9]+/, ''); + var repo = new GHRepo(config); + repo.log = function() { grunt.log.writeln.apply(grunt, arguments); }; + + var payload = { + tag_name: currentRelease, + name: nextRelease, + body: config['release-notes'], + draft: true + }; - if (res.statusCode === 201){ - grunt.log.ok('Github release created'); - done(); + grunt.log.writeln("Searching for existing GH release:", nextRelease); + repo.getReleaseByName(nextRelease) + .then(function(release) { + if (release) { + payload.body += "\n" + release.body; + repo.updateRelease(release, payload).then(function() { + grunt.log.ok('Github release ' + nextRelease + ' updated successfully.'); + done(); + }); } else { - grunt.fail.warn('Unable to create github release.', res.text); + repo.createRelease(payload).then(function() { + grunt.log.ok('Github release ' + nextRelease + ' created successfully'); + done(); + }); } + }) + .catch(function(err) { + throw err; }); }); @@ -207,6 +224,7 @@ module.exports = function(grunt) { 'bump-only:' + releaseType, 'changelog:' + releaseType, 'bump-commit', + 'coffee:compile', 'gh-release' ]); }); diff --git a/package.json b/package.json index 74650867..c06f6ff5 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,13 @@ "url": "http://github.com/GoogleChrome/accessibility-developer-tools" }, "devDependencies": { + "bluebird": "^2.9.27", "grunt": "^0.4.5", "grunt-bump": "^0.3.1", "grunt-cli": "^0.1.13", "grunt-closurecompiler": "^0.9.9", "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-coffee": "^0.13.0", "grunt-contrib-copy": "^0.8.0", "grunt-contrib-qunit": "^0.7.0", "grunt-prompt": "^1.3.0", diff --git a/src/util/gh_repo.coffee b/src/util/gh_repo.coffee new file mode 100644 index 00000000..a4844cc4 --- /dev/null +++ b/src/util/gh_repo.coffee @@ -0,0 +1,69 @@ +request = require 'superagent' +Promise = require 'bluebird' + +# Small utility class to interact with the Github v3 releases API. +module.exports = class GHRepo + constructor: (@config = {}) -> + @baseUrl = "https://api.github.com/repos/#{@config.repo}" + + _buildRequest: (req) -> + req + .auth @config.username, @config.password + .set 'Accept', 'application/vnd.github.v3' + .set 'User-Agent', 'grunt' + + log: -> console.log.apply console, arguments + + getReleaseByTagName: (tag) -> + # GET /repos/:owner/:repo/releases/tags/:tag + new Promise (resolve, reject) => + @log 'GET', "#{@baseUrl}/releases/tags/#{tag}" + @_buildRequest(request.get "#{@baseUrl}/releases/tags/#{tag}") + .end (err, res) -> + return resolve() if res.statusCode is 404 + return reject(err) if err? + return reject("Request failed") if res.statusCode isnt 200 + resolve res.body + + getReleases: (tag) -> + # GET /repos/:owner/:repo/releases + new Promise (resolve, reject) => + @log 'GET', "#{@baseUrl}/releases" + @_buildRequest(request.get "#{@baseUrl}/releases") + .end (err, res) -> + return resolve() if res.statusCode is 404 + return reject(err) if err? + return reject("Request failed") if res.statusCode isnt 200 + resolve res.body + + updateRelease: (release, payload) -> + # PATCH /repos/:owner/:repo/releases/:id + new Promise (resolve, reject) => + @log 'PATCH', "#{@baseUrl}/releases/#{release.id}" + @_buildRequest(request.patch "#{@baseUrl}/releases/#{release.id}") + .send payload + .end (err, res) -> + return reject(err) if err? + return reject("Request failed") if res.statusCode isnt 200 + resolve res.body + + createRelease: (payload) -> + # POST /repos/:owner/:repo/releases + new Promise (resolve, reject) => + @log 'POST', "#{@baseUrl}/releases" + @_buildRequest(request.post "#{@baseUrl}/releases") + .send payload + .end (err, res) -> + return reject(err) if err? + return reject("Request failed") if res.statusCode isnt 201 + resolve res.body + + getReleaseByName: (name) -> + new Promise (resolve, reject) => + @getReleases().then (releases = []) -> + for release in releases + return resolve(release) if release.name is name + + return resolve() + .catch (err) -> + reject "Unable to fetch project releases." From a3c9125f9d49b80543804a80fe16d3bd6155c38d Mon Sep 17 00:00:00 2001 From: Xavier Spriet Date: Fri, 12 Jun 2015 23:53:30 -0400 Subject: [PATCH 11/11] Merge with upstream/master --- Changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Changelog.md b/Changelog.md index 836c3cb2..e1290cb4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,10 @@ +### Enhancements: +* Rework findTextAlternatives not to return non-exposed text alternatives. +* Add Bower config (#157) + +### Bug fixes: +* Check for any text alternatives when assessing unlabeled images (#154). + ## 2.7.0 - 2015-05-15 ### New rules