├── test
├── fixtures
│ ├── 123
│ └── testing
├── expected
│ ├── custom_options
│ └── default_options
├── pages
│ ├── test1.html
│ └── morepages
│ │ └── test2.html
└── a11y_test.js
├── .travis.yml
├── .jshintrc
├── .gitignore
├── LICENSE-MIT
├── package.json
├── Gruntfile.js
├── README.md
└── tasks
└── a11y.js
/test/fixtures/123:
--------------------------------------------------------------------------------
1 | 1 2 3
--------------------------------------------------------------------------------
/test/fixtures/testing:
--------------------------------------------------------------------------------
1 | Testing
--------------------------------------------------------------------------------
/test/expected/custom_options:
--------------------------------------------------------------------------------
1 | Testing: 1 2 3 !!!
--------------------------------------------------------------------------------
/test/expected/default_options:
--------------------------------------------------------------------------------
1 | Testing, 1 2 3.
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language:
2 | - node_js
3 | node_js:
4 | - "0.10"
5 |
--------------------------------------------------------------------------------
/test/pages/test1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | My test page
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/pages/morepages/test2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | My other test page
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": true,
3 | "eqeqeq": true,
4 | "immed": true,
5 | "latedef": true,
6 | "newcap": true,
7 | "noarg": true,
8 | "sub": true,
9 | "undef": true,
10 | "boss": true,
11 | "eqnull": true,
12 | "node": true
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Commenting this out is preferred by some people, see
24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
25 | node_modules
26 |
27 | # Users Environment Variables
28 | .lock-wscript
29 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Joao Figueiredo
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grunt-a11y",
3 | "description": "Grunt wrapper for a11y",
4 | "version": "0.1.5",
5 | "homepage": "https://github.com/lucalanca/grunt-a11y",
6 | "author": {
7 | "name": "Joao Figueiredo",
8 | "email": "lucalanca2@hotmail.com",
9 | "url": "lucalanca.github.io"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git://github.com/lucalanca/grunt-a11y.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/lucalanca/grunt-a11y/issues"
17 | },
18 | "license": "MIT",
19 | "engines": {
20 | "node": ">= 0.8.0"
21 | },
22 | "scripts": {
23 | "test": "grunt test"
24 | },
25 | "devDependencies": {
26 | "grunt": "^1.0.1",
27 | "grunt-contrib-clean": "^1.0.0",
28 | "grunt-contrib-jshint": "^0.12.0",
29 | "grunt-contrib-nodeunit": "^1.0.0"
30 | },
31 | "peerDependencies": {
32 | "grunt": "^1.0.1"
33 | },
34 | "keywords": [
35 | "gruntplugin", "accessibility", "a11y"
36 | ],
37 | "dependencies": {
38 | "a11y": "^0.5.0",
39 | "chalk": "^1.0.0",
40 | "globby": "^4.0.0",
41 | "indent-string": "^2.1.0",
42 | "log-symbols": "^1.0.1",
43 | "protocolify": "^1.0.2",
44 | "q": "^1.1.2"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*
2 | * grunt-a11y
3 | * https://github.com/lucalanca/grunt-a11y
4 | *
5 | * Copyright (c) 2014 Joao Figueiredo
6 | * Licensed under the MIT license.
7 | */
8 |
9 | 'use strict';
10 |
11 | module.exports = function(grunt) {
12 |
13 | // Project configuration.
14 | grunt.initConfig({
15 | jshint: {
16 | all: [
17 | 'Gruntfile.js',
18 | 'tasks/*.js',
19 | '<%= nodeunit.tests %>'
20 | ],
21 | options: {
22 | jshintrc: '.jshintrc'
23 | }
24 | },
25 |
26 | // Before generating any new files, remove any previously-created files.
27 | clean: {
28 | tests: ['tmp']
29 | },
30 |
31 | // Configuration to be run (and then tested).
32 | a11y: {
33 | default_options: {
34 | options: {
35 | urls: ['www.google.com']
36 | }
37 | },
38 | setViewportSize: {
39 | options: {
40 | urls: ['www.google.com'],
41 | viewportSize: '360x640'
42 | }
43 | },
44 | simulate_fail_warn: {
45 | options: {
46 | urls: ['www.google.com']
47 | }
48 | },
49 | simulate_fail_fatal: {
50 | options: {
51 | urls: ['www.google.com'],
52 | failOnError: true
53 | }
54 | },
55 | no_glob_pattern: {
56 | options: {
57 | urls: ['test/pages/test1.html']
58 | }
59 | },
60 | glob_pattern: {
61 | options: {
62 | urls: ['test/pages/**/*.html']
63 | }
64 | }
65 | },
66 |
67 | // Unit tests.
68 | nodeunit: {
69 | tests: ['test/*_test.js']
70 | }
71 |
72 | });
73 |
74 | // Actually load this plugin's task(s).
75 | grunt.loadTasks('tasks');
76 |
77 | // These plugins provide necessary tasks.
78 | grunt.loadNpmTasks('grunt-contrib-jshint');
79 | grunt.loadNpmTasks('grunt-contrib-clean');
80 | grunt.loadNpmTasks('grunt-contrib-nodeunit');
81 |
82 | // Whenever the "test" task is run, first clean the "tmp" dir, then run this
83 | // plugin's task(s), then test the result.
84 | grunt.registerTask('test', ['clean', 'a11y', 'nodeunit']);
85 |
86 | // By default, lint and run all tests.
87 | grunt.registerTask('default', ['jshint', 'test']);
88 |
89 | };
90 |
--------------------------------------------------------------------------------
/test/a11y_test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var grunt = require('grunt');
4 | var path = require('path');
5 | var exec = require('child_process').exec;
6 | var execOptions = {
7 | cwd: path.join(__dirname, '..')
8 | };
9 |
10 |
11 | /*
12 | ======== A Handy Little Nodeunit Reference ========
13 | https://github.com/caolan/nodeunit
14 |
15 | Test methods:
16 | test.expect(numAssertions)
17 | test.done()
18 | Test assertions:
19 | test.ok(value, [message])
20 | test.equal(actual, expected, [message])
21 | test.notEqual(actual, expected, [message])
22 | test.deepEqual(actual, expected, [message])
23 | test.notDeepEqual(actual, expected, [message])
24 | test.strictEqual(actual, expected, [message])
25 | test.notStrictEqual(actual, expected, [message])
26 | test.throws(block, [error], [message])
27 | test.doesNotThrow(block, [error], [message])
28 | test.ifError(value)
29 | */
30 |
31 | exports.a11y = {
32 | setUp: function(done) {
33 | // setup here if necessary
34 | done();
35 | },
36 | default_options: function(test) {
37 | // test.expect(1);
38 |
39 | // var actual = grunt.file.read('tmp/default_options');
40 | var expected = grunt.file.read('test/expected/default_options');
41 | // test.equal(actual, expected, 'should describe what the default behavior is.');
42 | test.equal(1, 1, 'as');
43 |
44 | test.done();
45 | },
46 | custom_options: function(test) {
47 | // test.expect(1);
48 |
49 | // var actual = grunt.file.read('tmp/custom_options');
50 | var expected = grunt.file.read('test/expected/custom_options');
51 | // test.equal(actual, expected, 'should describe what the custom option(s) behavior is.');
52 | test.equal(1, 1, 'as');
53 | test.done();
54 | },
55 | no_glob_pattern: function(test) {
56 | test.expect(1);
57 | exec('grunt a11y:no_glob_pattern', execOptions, function(error, stdout) {
58 | test.equal(
59 | stdout.indexOf('Done, without errors') > -1,
60 | true,
61 | 'Should support ordinary urls'
62 | );
63 | test.done();
64 | });
65 | },
66 | glob_pattern: function(test) {
67 | test.expect(2);
68 | exec('grunt a11y:glob_pattern', execOptions, function(error, stdout) {
69 | test.equal(
70 | stdout.indexOf('Done, without errors') > -1,
71 | true,
72 | 'Should support glob patterns as urls'
73 | );
74 | test.equal(
75 | stdout.split('Report for').length === 3,
76 | true,
77 | 'Should discover two files'
78 | );
79 | test.done();
80 | });
81 | }
82 | };
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # grunt-a11y [](https://travis-ci.org/lucalanca/grunt-a11y) [](https://david-dm.org/lucalanca/grunt-a11y#info=devDependencies) [](https://david-dm.org/lucalanca/grunt-a11y)
2 |
3 | > Grunt wrapper for [a11y](https://github.com/addyosmani/a11y), automate your accessibility audits
4 |
5 | ## Getting Started
6 | This plugin requires Grunt `~0.4.5`
7 |
8 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
9 |
10 | ```shell
11 | npm install grunt-a11y --save-dev
12 | ```
13 |
14 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
15 |
16 | ```js
17 | grunt.loadNpmTasks('grunt-a11y');
18 | ```
19 |
20 | ## The "a11y" task
21 |
22 | ### Overview
23 | In your project's Gruntfile, add a section named `a11y` to the data object passed into `grunt.initConfig()`.
24 |
25 | ```js
26 | grunt.initConfig({
27 | a11y: {
28 | dev: {
29 | options: {
30 | urls: ['www.twitter.com', 'www.google.com', 'dist/**/*.html']
31 | }
32 | }
33 | }
34 | });
35 | ```
36 |
37 | ### Options
38 |
39 | #### options.urls
40 | Type: `Array`
41 | Default value: `[]`
42 |
43 | An Array of strings representing the url's to process. Supports globbing.
44 |
45 | #### options.failOnError
46 | Type: `Boolean`
47 | Default value: `false`
48 |
49 | If set to true, the grunt task fails when there is an accessibility error in one of the audits.
50 |
51 | #### options.viewportSize
52 | Type: `String`
53 | Default value: `1024x768`
54 |
55 | Sets the viewport size
56 |
57 | #### options.vebose
58 | Type: `Boolean`
59 | Default value: `false`
60 |
61 | Sets the viewport size
62 |
63 |
64 | ## Contributing
65 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
66 |
67 |
68 | ### Contributors
69 | - [lucalanca](https://github.com/lucalanca)
70 | - [jrcryer](https://github.com/jrcryer)
71 |
72 | ## Release History
73 |
74 | 0.1.5 Adds `verbose` flag. [#19](https://github.com/lucalanca/grunt-a11y/pull/19) Thanks to [@srescio](https://github.com/srescio).
75 |
76 | 0.1.4 Makes `protocolify` and `globby` part of dependencies. [#13](https://github.com/lucalanca/grunt-a11y/pull/13)) Thanks to [@shortdiv](https://github.com/shortdiv)
77 |
78 | 0.1.3 Added globbing possibility to `urls`option (See [#12](https://github.com/lucalanca/grunt-a11y/issues/12)) Thanks to [@peterhaldbaek](https://github.com/peterhaldbaek)
79 |
80 | 0.1.2 Added `viewportSize` option (See [#10](https://github.com/lucalanca/grunt-a11y/issues/10))
81 |
82 | 0.1.1 Added `failOnError` option (See [#2](https://github.com/lucalanca/grunt-a11y/issues/2))
83 |
84 | 0.1.0 First release just wrapping a11y
85 |
--------------------------------------------------------------------------------
/tasks/a11y.js:
--------------------------------------------------------------------------------
1 | /*
2 | * grunt-a11y
3 | * https://github.com/lucalanca/grunt-a11y
4 | *
5 | * Copyright (c) 2014 Joao Figueiredo
6 | * Licensed under the MIT license.
7 | */
8 |
9 | 'use strict';
10 |
11 | var a11y = require('a11y');
12 | var chalk = require('chalk');
13 | var indent = require('indent-string');
14 | var logSymbols = require('log-symbols');
15 | var Q = require('q');
16 | var globby = require('globby');
17 | var protocolify = require('protocolify');
18 |
19 | var fail = chalk.bold.red;
20 | var log = chalk.blue;
21 | var pass = chalk.green;
22 |
23 | module.exports = function(grunt) {
24 |
25 | /**
26 | * A promise-based wrapper on a11y.
27 | * @param {String} url
28 | * @param {String} viewportSize
29 | * @param {Boolean} verbose
30 | * @return {Promise}
31 | */
32 | function a11yPromise (url, viewportSize, verbose) {
33 | var deferred = Q.defer();
34 | a11y(url, {viewportSize: viewportSize}, function (err, reports) {
35 | if (err) {
36 | deferred.reject(new Error(err));
37 | } else {
38 | deferred.resolve({url: url, reports: reports, verbose: verbose});
39 | }
40 | });
41 | return deferred.promise;
42 | }
43 |
44 | /**
45 | * Utility function that logs an audit in the console and
46 | * returns a boolean with the validity of the audit.
47 | *
48 | * @param {String} url
49 | * @param {a11y reports} reports
50 | * @return {Boolean} if the audit is valid
51 | */
52 | function logReports (url, reports, verbose) {
53 | var passes = '';
54 | var failures = '';
55 |
56 | grunt.log.writeln(chalk.underline(chalk.cyan('\nReport for ' + url + '\n')));
57 | reports.audit.forEach(function (el) {
58 | if (el.result === 'PASS') {
59 | passes += logSymbols.success + ' ' + el.heading + '\n';
60 | }
61 |
62 | if (el.result === 'FAIL') {
63 | failures += logSymbols.error + ' ' + el.heading + '\n';
64 | failures += el.elements + '\n\n';
65 | }
66 | });
67 |
68 | grunt.log.writeln(indent(failures, ' ', 2));
69 | grunt.log.writeln(indent(passes , ' ', 2));
70 |
71 | if (verbose) {
72 | grunt.log.writeln('VERBOSE OUTPUT\n', reports.audit);
73 | }
74 |
75 | return !failures.length;
76 | }
77 |
78 | // Please see the Grunt documentation for more information regarding task
79 | // creation: http://gruntjs.com/creating-tasks
80 |
81 | grunt.registerMultiTask('a11y', 'Grunt wrapper for a11y', function() {
82 | var done = this.async();
83 | // Merge task-specific and/or target-specific options with these defaults.
84 | var options = this.options({
85 | urls: [],
86 | failOnError: false,
87 | viewportSize: '1024x768',
88 | verbose: false
89 | });
90 |
91 | var urls = globby.sync(options.urls, {
92 | nonull: true
93 | }).map(protocolify);
94 | var a11yPromises = urls.map(function (url) {
95 | return a11yPromise(url, options.viewportSize, options.verbose);
96 | });
97 |
98 | a11yPromises.forEach(function (f) {
99 | f.then(function (audit) {
100 | var valid = logReports(audit.url, audit.reports, audit.verbose);
101 | if (!valid) {
102 | if (options.failOnError) {
103 | grunt.fail.fatal('FATAL: Audit failed for ' + audit.url);
104 | } else {
105 | grunt.log.error('WARN: Audit failed for ' + audit.url);
106 | }
107 | }
108 | }).catch(function (error) {
109 | if (options.failOnError) {
110 | grunt.fail.fatal(error);
111 | } else {
112 | grunt.log.error(error);
113 | }
114 | });
115 | });
116 |
117 | Q.all(a11yPromises).then(done);
118 | });
119 |
120 | };
121 |
--------------------------------------------------------------------------------