diff --git a/.gitignore b/.gitignore index 651780f..2fbc200 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ external/ icons/svg-min/ .sass-cache/ dist/ +tmp/ diff --git a/demos/typography.html b/demos/typography.html index 8552857..3b05b4d 100644 --- a/demos/typography.html +++ b/demos/typography.html @@ -1,5 +1,5 @@ - + CSS Chassis - Typography diff --git a/lib/reporter.js b/lib/reporter.js new file mode 100644 index 0000000..87b479d --- /dev/null +++ b/lib/reporter.js @@ -0,0 +1,48 @@ + + +function color(code, str) { + return "\u001b[" + code + "m" + str + "\u001b[0m"; +} + +exports = module.exports = function( grunt, results, threshold ) { + var pass = true; + results.forEach( function ( result ) { + grunt.log.subhead( result.url ); + var violations = result.violations; + if ( violations.length ) { + if ( violations.length > threshold ) { + pass = false; + grunt.log.error( "Found " + result.violations.length + " accessibility violations:" ); + } else { + grunt.log.ok( "Found " + result.violations.length + " accessibility violations: (under threshold of " + threshold + ")" ); + } + result.violations.forEach( function( ruleResult ) { + grunt.log.subhead( " " + color(31, "\u00D7") + " " + ruleResult.help ); + + ruleResult.nodes.forEach( function( violation, index ) { + grunt.log.writeln( " " + ( index + 1 ) + ". " + JSON.stringify( violation.target ) ); + + if ( violation.any.length ) { + grunt.log.writeln( " Fix any of the following:" ); + violation.any.forEach( function( check ) { + grunt.log.writeln( " \u2022 " + check.message ); + } ); + } + + var alls = violation.all.concat( violation.none ); + if ( alls.length ) { + grunt.log.writeln( " Fix all of the following:" ); + alls.forEach( function( check ) { + grunt.log.writeln( " \u2022 " + check.message ); + } ); + } + grunt.log.writeln(); + }); + }); + return; + } else { + grunt.log.ok( "Found no accessibility violations." ); + } + } ); + return pass; +}; diff --git a/package.json b/package.json index 1a089eb..40f9408 100644 --- a/package.json +++ b/package.json @@ -37,16 +37,17 @@ }, "dependencies": {}, "devDependencies": { + "axe-webdriverjs": "^0.1.0", "browser-perf": "1.2.3", "chromedriver": "2.13.0", "commitplease": "2.0.0", "ejs-template": "0.1.0", "grunt": "0.4.5", "grunt-autoprefixer": "2.1.0", - "grunt-contrib-cssmin": "0.10.0", + "grunt-contrib-connect": "0.9.0", "grunt-contrib-csslint": "0.4.0", + "grunt-contrib-cssmin": "0.10.0", "grunt-contrib-jshint": "0.10.0", - "grunt-contrib-connect": "0.9.0", "grunt-contrib-watch": "0.6.1", "grunt-csscomb": "3.0.0", "grunt-git-authors": "2.0.0", @@ -58,7 +59,9 @@ "grunt-svgstore": "0.5.0", "jsass-vars": "0.0.3", "load-grunt-config": "0.16.0", - "perfjankie": "1.2.2" + "perfjankie": "1.2.2", + "promise": "^7.0.3", + "selenium-webdriver": "^2.46.1" }, "keywords": [] } diff --git a/tasks/alias.js b/tasks/alias.js index 0b1fe20..77e8234 100644 --- a/tasks/alias.js +++ b/tasks/alias.js @@ -1,7 +1,8 @@ module.exports = function( grunt ) { grunt.registerTask( "default", [ "test" ] ); -grunt.registerTask( "test", [ "build", "jshint", "jscs", "csslint" ] ); -grunt.registerTask( "build", [ "svg", "sass", "csscomb", "cssmin" ] ); +grunt.registerTask( "accessibility", [ "connect:accessibility", "axe-webdriver"]) +grunt.registerTask( "test", [ "build", "jshint", "jscs", "csslint", "accessibility" ] ); +grunt.registerTask( "build", [ "buildVariables", "svg", "sass", "csscomb", "cssmin" ] ); grunt.registerTask( "perf", [ "start-selenium-server", "connect:perf", diff --git a/tasks/axe-webdriver.js b/tasks/axe-webdriver.js new file mode 100644 index 0000000..f1d3357 --- /dev/null +++ b/tasks/axe-webdriver.js @@ -0,0 +1,58 @@ +/*! aXe-grunt-webdriver + * Copyright (c) 2015 Deque Systems, Inc. + * + * Your use of this Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This entire copyright notice must appear in every copy of this file you + * distribute or in any file that contains substantial portions of this source + * code. + */ + +'use strict'; + +module.exports = function( grunt ) { + var WebDriver = require( "selenium-webdriver" ), + AxeBuilder = require( "axe-webdriverjs" ), + Promise = require( "promise" ), + path = require( "path" ), + reporter = require( "../lib/reporter" ); + + grunt.registerMultiTask( "axe-webdriver", "Grunt plugin for aXe utilizing WebDriverJS", function () { + var options = this.options( { + browser: "firefox", + server: null, + threshold: 0 + } ); + + var done = this.async (); + var driver = new WebDriver.Builder () + .forBrowser( options.browser ) + .build (); + + var dest = this.data.dest; + Promise.all( this.data.urls.map( function( url ) { + return new Promise( function( resolve, reject ) { + + driver + .get( url ) + .then( function() { + new AxeBuilder( driver ) + .analyze( function( results ) { + results.url = url; + resolve( results ); + } ); + } ); + } ); + })).then( function( results ) { + if ( dest ) { + grunt.file.write( dest, JSON.stringify( results, null, " " ) ); + } + var result = reporter( grunt, results, options.threshold ); + driver.quit().then( function () { + done( result ); + } ); + } ); + } ); +}; diff --git a/tasks/options/axe-webdriver.js b/tasks/options/axe-webdriver.js new file mode 100644 index 0000000..b7a485f --- /dev/null +++ b/tasks/options/axe-webdriver.js @@ -0,0 +1,16 @@ +module.exports = { + firefox: { + options: { + threshold: 0 + }, + urls: ['http://localhost:4200/demos/typography.html'], + dest: 'tmp/gu.json' + }, + chrome: { + options: { + browser: 'chrome', + threshold: 0 + }, + urls: ['http://localhost:4200/demos/typography.html'] + } +}; diff --git a/tasks/options/connect.js b/tasks/options/connect.js index 10731f9..abd1ab5 100644 --- a/tasks/options/connect.js +++ b/tasks/options/connect.js @@ -5,32 +5,36 @@ var template = require( "ejs-template" ), module.exports = { options: { port: 4200, - base: ".", - middleware: [ - template.middleware({ basedir: __dirname }), - function( req, res ) { - var data, i, - url = urlParser.parse( req.url, true ), - query = {}, - parts = url.pathname.split( "/" ), - file = req.url.replace( /^\//, "" ).split( "?" )[ 0 ]; + base: "." + }, + perf: { + options: { + middleware: [ + template.middleware({ basedir: __dirname }), + function( req, res ) { + var data, i, + url = urlParser.parse( req.url, true ), + query = {}, + parts = url.pathname.split( "/" ), + file = req.url.replace( /^\//, "" ).split( "?" )[ 0 ]; - for ( i = 1; i < parts.length; i += 2 ) { - query[ parts[ i ] ] = parts[ i + 1 ]; + for ( i = 1; i < parts.length; i += 2 ) { + query[ parts[ i ] ] = parts[ i + 1 ]; + } + if ( file.split( "." ).length <= 1 ) { + data = componentGenerator.generate( + query.framework, + query.component, + query.count + ); + file = "../../performance/component.html"; + } + res.endTemplate( file, data ); } - if ( file.split( "." ).length <= 1 ) { - data = componentGenerator.generate( - query.framework, - query.component, - query.count - ); - file = "../../performance/component.html"; - } - res.endTemplate( file, data ); - } - ] + ] + } }, - perf: {}, + accessibility: {}, dev: { options: { keepalive: true