Compare commits

..

14 Commits
1.0.0 ... 1.1.0

Author SHA1 Message Date
Rowan Manning
2ebb54f545 Version 1.1.0 2013-11-22 14:01:53 +00:00
Rowan Manning
d57ae45b4f Write tests for result pages (and related actions) 2013-11-22 11:44:54 +00:00
Rowan Manning
ea330548b1 Write tests for task pages (and related actions) 2013-11-22 11:10:57 +00:00
Rowan Manning
ae5b214834 Write tests for the new URL form 2013-11-22 09:17:44 +00:00
Rowan Manning
b5735b7f05 Reload fixtures for tests and finish testing home 2013-11-21 13:54:52 +00:00
Rowan Manning
a47fb38d7f Add test running instructions to the readme 2013-11-21 12:04:42 +00:00
Rowan Manning
da0e98eab1 Update the Travis config to run tests 2013-11-21 11:52:40 +00:00
Rowan Manning
623a52e112 Build a basic framework for functional testing 2013-11-21 09:58:44 +00:00
Rowan Manning
9d72e50b4e Update docs to include webservice dependencies 2013-11-21 09:25:28 +00:00
Rowan Manning
3535b9b11c Document new configuration options 2013-11-21 09:21:15 +00:00
Rowan Manning
8f636173a0 Allow the webservice to run automatically 2013-11-21 09:12:54 +00:00
Rowan Manning
ec9f82aa6f Add build status to readme 2013-11-20 13:47:47 +00:00
Rowan Manning
34a7c351c9 Add Travis config 2013-11-20 13:46:47 +00:00
Rowan Manning
c13af05422 Fix lint errors 2013-11-20 13:46:41 +00:00
29 changed files with 758 additions and 43 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
# Config files
config/development.json
config/production.json
config/test.json
# Generated npm files
node_modules

24
.travis.yml Normal file
View File

@@ -0,0 +1,24 @@
# Language/versions
language: node_js
node_js:
- "0.10"
# Services setup
services:
- mongodb
# Build script
before_script:
- npm install -g grunt-cli
- cp config/test.sample.json config/test.json
- grunt start-test &
- sleep 5 # give server time to start
script: 'grunt ci'
# Notifications
notifications:
email:
- j.robinson@nature.com
- perry.harlock@nature.com
- rowan.manning@nature.com

View File

@@ -34,6 +34,15 @@ module.exports = function (grunt) {
}
},
mochaTest: {
functional: {
src: ['test/functional/**/*.js'],
options: {
reporter: 'spec'
}
}
},
nodemon: {
development: {
options: {
@@ -43,6 +52,15 @@ module.exports = function (grunt) {
NODE_ENV: 'development'
}
}
},
test: {
options: {
cwd: __dirname,
file: 'index.js',
env: {
NODE_ENV: 'test'
}
}
}
},
@@ -84,12 +102,15 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-mocha-test');
grunt.loadNpmTasks('grunt-nodemon');
grunt.registerTask('lint', ['jshint']);
grunt.registerTask('test', ['mochaTest']);
grunt.registerTask('compile', ['less', 'uglify']);
grunt.registerTask('start', ['nodemon:development']);
grunt.registerTask('default', ['compile', 'lint']);
grunt.registerTask('ci', ['compile', 'lint']);
grunt.registerTask('start-test', ['nodemon:test']);
grunt.registerTask('default', ['compile', 'lint', 'test']);
grunt.registerTask('ci', ['lint', 'test']);
};

View File

@@ -3,7 +3,8 @@ pa11y-dashboard
pa11y-dashboard is a web interface to the [pa11y][pa11y] accessibility reporter; allowing you to focus on *fixing* issues rather than hunting them down.
**Current Version:** *1.0.0*
**Current Version:** *1.1.0*
**Build Status:** [![Build Status][travis-img]][travis]
**Node Version Support:** *0.10*
@@ -14,7 +15,7 @@ pa11y-dashboard is a web interface to the [pa11y][pa11y] accessibility reporter;
Setup
-----
pa11y-dashboard requires [Node.js][node] 0.10+ and [pa11y-webservice][pa11y-webservice] to be installed and running. You'll need to follow the setup guide for pa11y-webservice before setting up pa11y-dashboard.
pa11y-dashboard requires [Node.js][node] 0.10+, [PhantomJS][phantom], and [MongoDB][mongo] to be installed. See the [pa11y][pa11y] and [pa11y-webservice][pa11y-webservice] documentation for more information on these dependencies.
You'll then need to clone this repo locally and install dependencies with `npm install`.
@@ -23,6 +24,7 @@ Once you have a local clone, you'll need to copy some sample configuration files
```sh
$ cp config/development.sample.json config/development.json
$ cp config/production.sample.json config/production.json
$ cp config/test.sample.json config/test.json
```
Each of these files defines configurations for a different environment. If you're just running the application locally, then you should be OK with just development configurations. The [available configurations are documented here](#configurations).
@@ -32,6 +34,7 @@ Now that you've got your application configured, you can run in each mode with t
```sh
$ NODE_ENV=production node . # Run in production
$ NODE_ENV=development node . # Run in development
$ NODE_ENV=test node . # Run in test
```
Check the [development instructions](#development) for more information about running locally (and restarting automatically when files change).
@@ -42,9 +45,6 @@ Configurations
The boot configurations for pa11y-dashboard are as follows. Look at the sample JSON files in the repo for example usage.
### webservice
*(string)* The base URL of the [pa11y-webservice][pa11y-webservice] instance you intend on using.
### port
*(number)* The port to run the application on.
@@ -57,12 +57,21 @@ The boot configurations for pa11y-dashboard are as follows. Look at the sample J
### siteMessage
*(string)* A message to display prominently on the site home page. Defaults to `null`.
### webservice
This can either be an object containing [pa11y-webservice configurations][pa11y-webservice-config], or a string which is the base URL of a [pa11y-webservice][pa11y-webservice] instance you are running separately.
Development
-----------
To develop pa11y-dashboard, you'll need to clone the repo and get set up as outlined in the [setup guide](#setup). You'll also need [Grunt][grunt] to be installed globally in order to run tests, you can do this with `npm install -g grunt-cli`.
Once you've done this, you'll need to start the application in test mode with:
```sh
$ grunt start-test
```
Now you'll be able to run the following commands:
```sh
@@ -70,6 +79,8 @@ $ grunt # Run the lint and test tasks together
$ grunt lint # Run JSHint with the correct config
$ grunt compile # Compile front-end assets
$ grunt start # Run app in development mode, restarting if files change
$ grunt start-test # Run app in test mode, restarting if files change
$ grunt test # Run functional tests
$ grunt watch # Watch for file changes and compile assets
```
@@ -88,7 +99,11 @@ pa11y-dashboard is licensed under the [GNU General Public License 3.0][gpl].
[gpl]: http://www.gnu.org/licenses/gpl-3.0.html
[grunt]: http://gruntjs.com/
[mongo]: http://www.mongodb.org/
[node]: http://nodejs.org/
[pa11y]: https://github.com/nature/pa11y
[pa11y-webservice]: https://github.com/nature/pa11y-webservice
[supervisor]: https://github.com/isaacs/node-supervisor
[pa11y-webservice-config]: https://github.com/nature/pa11y-webservice#configurations
[phantom]: http://phantomjs.org/
[travis]: https://travis-ci.org/nature/pa11y-dashboard
[travis-img]: https://travis-ci.org/nature/pa11y-dashboard.png?branch=master

7
app.js
View File

@@ -13,11 +13,16 @@ module.exports = initApp;
function initApp (config, callback) {
config = defaultConfig(config);
var webserviceUrl = config.webservice;
if (typeof webserviceUrl == 'object') {
webserviceUrl = 'http://' + webserviceUrl.host + ':' + webserviceUrl.port + '/';
}
var app = new EventEmitter();
app.address = null;
app.express = express();
app.server = http.createServer(app.express);
app.webservice = createClient(config.webservice);
app.webservice = createClient(webserviceUrl);
// Compression
app.express.use(express.compress());

View File

@@ -1,6 +1,12 @@
{
"webservice": "http://localhost:3000/",
"port": 4000,
"noindex": true,
"readonly": false
"readonly": false,
"webservice": {
"database": "mongodb://localhost/pa11y-webservice-dev",
"host": "0.0.0.0",
"port": 3000,
"cron": "0 30 0 * * *"
}
}

View File

@@ -1,6 +1,12 @@
{
"webservice": "http://localhost:3000/",
"port": 4000,
"noindex": true,
"readonly": false
"readonly": false,
"webservice": {
"database": "mongodb://localhost/pa11y-webservice",
"host": "0.0.0.0",
"port": 3000,
"cron": "0 30 0 * * *"
}
}

12
config/test.sample.json Normal file
View File

@@ -0,0 +1,12 @@
{
"port": 4000,
"noindex": true,
"readonly": false,
"webservice": {
"database": "mongodb://localhost/pa11y-webservice-test",
"host": "0.0.0.0",
"port": 3000,
"cron": "0 30 0 * * *"
}
}

View File

@@ -18,4 +18,14 @@ require('./app')(config, function (err, app) {
console.error(chalk.grey(stack.join('\n')));
});
// Start the webservice if required
if (typeof config.webservice === 'object') {
require('pa11y-webservice')(config.webservice, function (err, webservice) {
console.log('');
console.log(chalk.underline.cyan('pa11y-webservice started'));
console.log(chalk.grey('mode: %s'), process.env.NODE_ENV);
console.log(chalk.grey('uri: %s'), webservice.server.info.uri);
});
}
});

View File

@@ -1,6 +1,6 @@
{
"name": "pa11y-dashboard",
"version": "1.0.0",
"version": "1.1.0",
"private": true,
"description": "pa11y-dashboard is a visual web interface to the pa11y accessibility reporter",
@@ -25,6 +25,7 @@
"express": "~3.4",
"express-hbs": "~0.2",
"moment": "~2.2",
"pa11y-webservice": "~1.1",
"pa11y-webservice-client-node": "~1.0",
"underscore": "~1.5"
},
@@ -35,7 +36,11 @@
"grunt-contrib-less": "~0.8",
"grunt-contrib-uglify": "~0.2",
"grunt-contrib-watch": "~0.5",
"grunt-nodemon": "~0.1"
"grunt-mocha-test": "~0.7",
"grunt-nodemon": "~0.1",
"jsdom": "~0.8",
"proclaim": "~2.0",
"request": "~2.27"
},
"scripts": {

View File

@@ -167,12 +167,20 @@ $(document).ready(function(){
$.each(datasets, function(key, val) {
var lowerCaseValue = (val.label.substring(0, val.label.length - 1)).toLowerCase();
choiceContainer.append('<li class="text-center '+
lowerCaseValue +'"><div class="series-checkbox-container"><input type="checkbox" name="' + key +
'" checked="checked" id="id' + key +
'"/><label for="id' + key +
'"><span class="stat-type">' + val.label +
'</span></label></div></li>');
choiceContainer.append(
'<li class="text-center ' + lowerCaseValue + '">' +
'<div class="series-checkbox-container">' +
'<input type="checkbox"' +
'name="' + key + '" ' +
'checked="checked" ' +
'id="id' + key + '"' +
'/>' +
'<label for="id' + key + '">' +
'<span class="stat-type">' + val.label + '</span>' +
'</label>' +
'</div>' +
'</li>'
);
});
choiceContainer.find('input').click(plotAccordingToChoices);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,51 @@
'use strict';
var jsdom = require('jsdom');
var request = require('request');
module.exports = createNavigator;
// Create a navigate function
function createNavigator (baseUrl, store) {
return function (opts, callback) {
store.body = null;
store.dom = null;
store.request = null;
store.response = null;
store.status = null;
store.window = null;
request({
url: baseUrl + opts.endpoint,
method: opts.method || 'GET',
body: opts.body,
json: true,
qs: opts.query,
followAllRedirects: true
}, function (err, res, body) {
store.body = body;
store.request = res.request;
store.response = res;
store.status = res.statusCode;
if (opts.nonDom) {
store.window = null;
store.dom = null;
callback();
} else {
jsdom.env(
store.body,
function (err, window) {
store.window = window;
store.dom = window.document;
callback();
}
);
}
});
};
}

View File

@@ -0,0 +1,14 @@
'use strict';
var createClient = require('pa11y-webservice-client-node');
module.exports = createWebserviceClient;
// Create a webservice client
function createWebserviceClient (config) {
var webserviceUrl = config.webservice;
if (typeof webserviceUrl == 'object') {
webserviceUrl = 'http://' + webserviceUrl.host + ':' + webserviceUrl.port + '/';
}
return createClient(webserviceUrl);
}

View File

@@ -0,0 +1,65 @@
/* global beforeEach, describe, it */
/* jshint maxlen: false, maxstatements: false */
'use strict';
var assert = require('proclaim');
describe('GET /', function () {
beforeEach(function (done) {
var req = {
method: 'GET',
endpoint: '/'
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should display an "Add new URL" button', function () {
var elem = this.last.dom.querySelectorAll('[data-test=add-task]');
assert.strictEqual(elem.length, 1);
assert.strictEqual(elem[0].getAttribute('href'), '/new');
});
it('should display all of the expected tasks', function () {
var tasks = this.last.dom.querySelectorAll('[data-test=task]');
assert.strictEqual(tasks.length, 3);
assert.match(tasks[0].textContent, /npg home\s+\(wcag2aa\)/i);
assert.match(tasks[1].textContent, /npg home\s+\(wcag2aaa\)/i);
assert.match(tasks[2].textContent, /nature news\s+\(section508\)/i);
});
it('should have links to each task', function () {
var tasks = this.last.dom.querySelectorAll('[data-test=task]');
assert.strictEqual(tasks[0].querySelectorAll('[href="/abc000000000000000000001"]').length, 1);
assert.strictEqual(tasks[1].querySelectorAll('[href="/abc000000000000000000002"]').length, 1);
assert.strictEqual(tasks[2].querySelectorAll('[href="/abc000000000000000000003"]').length, 1);
});
it('should display a delete button for each task', function () {
var tasks = this.last.dom.querySelectorAll('[data-test=task]');
assert.strictEqual(tasks[0].querySelectorAll('[href="/abc000000000000000000001/delete"]').length, 1);
assert.strictEqual(tasks[1].querySelectorAll('[href="/abc000000000000000000002/delete"]').length, 1);
assert.strictEqual(tasks[2].querySelectorAll('[href="/abc000000000000000000003/delete"]').length, 1);
});
it('should display the task result counts if the task has been run', function () {
var tasks = this.last.dom.querySelectorAll('[data-test=task]');
assert.match(tasks[0].textContent, /1\s*errors/i);
assert.match(tasks[0].textContent, /2\s*warnings/i);
assert.match(tasks[0].textContent, /3\s*notices/i);
});
it('should display a message indicating that there are no results if the task has not been run', function () {
var tasks = this.last.dom.querySelectorAll('[data-test=task]');
assert.match(tasks[2].textContent, /no results/i);
});
it('should not display an alert message', function () {
assert.strictEqual(this.last.dom.querySelectorAll('[data-test=alert]').length, 0);
});
});

View File

@@ -0,0 +1,136 @@
/* global beforeEach, describe, it */
/* jshint maxlen: false, maxstatements: false */
'use strict';
var assert = require('proclaim');
describe('GET /new', function () {
beforeEach(function (done) {
var req = {
method: 'GET',
endpoint: '/new'
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should not display an error message', function () {
assert.strictEqual(this.last.dom.querySelectorAll('[data-test=error]').length, 0);
});
it('should have an "Add new URL" form', function () {
var form = this.last.dom.querySelectorAll('[data-test=new-url-form]')[0];
assert.isDefined(form);
assert.strictEqual(form.getAttribute('action'), '/new');
assert.strictEqual(form.getAttribute('method'), 'post');
});
describe('"Add New URL" form', function () {
beforeEach(function () {
this.form = this.last.dom.querySelectorAll('[data-test=new-url-form]')[0];
});
it('should have a "name" field', function () {
var field = this.form.querySelectorAll('input[name=name]')[0];
assert.isDefined(field);
assert.strictEqual(field.getAttribute('type'), 'text');
assert.strictEqual(field.getAttribute('value'), '');
});
it('should have a "url" field', function () {
var field = this.form.querySelectorAll('input[name=url]')[0];
assert.isDefined(field);
assert.strictEqual(field.getAttribute('type'), 'url');
assert.strictEqual(field.getAttribute('value'), '');
});
it('should have a "standard" field', function () {
var field = this.form.querySelectorAll('select[name=standard]')[0];
assert.isDefined(field);
assert.strictEqual(field.querySelectorAll('option').length, 4);
});
it('should have "ignore" fields', function () {
var fields = this.form.querySelectorAll('input[name="ignore[]"]');
assert.isDefined(fields);
assert.notStrictEqual(fields.length, 0);
});
});
});
describe('POST /new', function () {
describe('with invalid query', function () {
beforeEach(function (done) {
var req = {
method: 'POST',
endpoint: '/new',
body: {
name: '',
url: ''
}
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should display an error message', function () {
assert.strictEqual(this.last.dom.querySelectorAll('[data-test=error]').length, 1);
});
});
describe('with valid query', function () {
beforeEach(function (done) {
var req = {
method: 'POST',
endpoint: '/new',
body: {
name: 'Example',
url: 'http://example.com/',
standard: 'WCAG2AA'
}
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should create the task', function (done) {
this.webservice.tasks.get({}, function (err, tasks) {
assert.strictEqual(tasks.length, 4);
done();
});
});
it('should redirect me to the new URL page', function () {
assert.match(this.last.request.uri.pathname, /^\/[a-z0-9]{24}$/i);
});
it('should not display an error message', function () {
assert.strictEqual(this.last.dom.querySelectorAll('[data-test=error]').length, 0);
});
it('should display a success message', function () {
var alert = this.last.dom.querySelectorAll('[data-test=alert]')[0];
assert.isDefined(alert);
assert.match(alert.textContent, /url has been added/i);
});
});
});

View File

@@ -0,0 +1,53 @@
/* global beforeEach, describe, it */
/* jshint maxlen: false, maxstatements: false */
'use strict';
var assert = require('proclaim');
describe('GET /<task-id>/<result-id>.csv', function () {
beforeEach(function (done) {
var req = {
method: 'GET',
endpoint: '/abc000000000000000000001/def000000000000000000001.csv',
nonDom: true
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should output CSV results', function () {
assert.match(this.last.body, /^"code", "message", "type"/);
});
});
describe('GET /<task-id>/<result-id>.json', function () {
beforeEach(function (done) {
var req = {
method: 'GET',
endpoint: '/abc000000000000000000001/def000000000000000000001.json',
nonDom: true
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should output JSON results', function () {
var json = this.last.body;
assert.strictEqual(json.task.name, 'NPG Home');
assert.strictEqual(json.task.url, 'nature.com');
assert.strictEqual(json.count.error, 1);
assert.strictEqual(json.count.warning, 2);
assert.strictEqual(json.count.notice, 3);
assert.isArray(json.results);
});
});

View File

@@ -0,0 +1,55 @@
/* global beforeEach, describe, it */
/* jshint maxlen: false, maxstatements: false */
'use strict';
var assert = require('proclaim');
describe('GET /<task-id>/<result-id>', function () {
beforeEach(function (done) {
var req = {
method: 'GET',
endpoint: '/abc000000000000000000001/def000000000000000000001'
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should display a "Download CSV" button', function () {
var elem = this.last.dom.querySelectorAll('[data-test=download-csv]');
assert.strictEqual(elem.length, 1);
assert.strictEqual(elem[0].getAttribute('href'), '/abc000000000000000000001/def000000000000000000001.csv');
});
it('should display a "Download JSON" button', function () {
var elem = this.last.dom.querySelectorAll('[data-test=download-json]');
assert.strictEqual(elem.length, 1);
assert.strictEqual(elem[0].getAttribute('href'), '/abc000000000000000000001/def000000000000000000001.json');
});
it('should display a link back to the task', function () {
assert.isDefined(this.last.dom.querySelectorAll('[href="/abc000000000000000000001"]')[0]);
});
it('should display errors', function () {
var elem = this.last.dom.querySelectorAll('[data-test=task-errors]')[0];
assert.isDefined(elem);
assert.match(elem.textContent, /errors \( 1 \)/i);
});
it('should display warnings', function () {
var elem = this.last.dom.querySelectorAll('[data-test=task-warnings]')[0];
assert.isDefined(elem);
assert.match(elem.textContent, /warnings \( 2 \)/i);
});
it('should display notices', function () {
var elem = this.last.dom.querySelectorAll('[data-test=task-notices]')[0];
assert.isDefined(elem);
assert.match(elem.textContent, /notices \( 3 \)/i);
});
});

View File

@@ -0,0 +1,61 @@
/* global beforeEach, describe, it */
/* jshint maxlen: false, maxstatements: false */
'use strict';
var assert = require('proclaim');
describe('GET /<task-id>/delete', function () {
beforeEach(function (done) {
var req = {
method: 'GET',
endpoint: '/abc000000000000000000001/delete'
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should have a "Delete URL" form', function () {
var form = this.last.dom.querySelectorAll('[data-test=delete-url-form]')[0];
assert.isDefined(form);
assert.strictEqual(form.getAttribute('action'), '/abc000000000000000000001/delete');
assert.strictEqual(form.getAttribute('method'), 'post');
});
});
describe('POST /<task-id>/delete', function () {
beforeEach(function (done) {
var req = {
method: 'POST',
endpoint: '/abc000000000000000000001/delete'
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should delete the task', function (done) {
this.webservice.task('abc000000000000000000001').get({}, function (err) {
assert.strictEqual(err.message, 'Error 404');
done();
});
});
it('should redirect me to the home page', function () {
assert.strictEqual(this.last.request.uri.pathname, '/');
});
it('should display a success message', function () {
var alert = this.last.dom.querySelectorAll('[data-test=alert]')[0];
assert.isDefined(alert);
assert.match(alert.textContent, /been deleted/i);
});
});

View File

@@ -0,0 +1,94 @@
/* global beforeEach, describe, it */
/* jshint maxlen: false, maxstatements: false */
'use strict';
var assert = require('proclaim');
describe('GET /<task-id>', function () {
describe('when task has results', function () {
beforeEach(function (done) {
var req = {
method: 'GET',
endpoint: '/abc000000000000000000001'
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should display a "Run" button', function () {
var elem = this.last.dom.querySelectorAll('[data-test=run-task]');
assert.strictEqual(elem.length, 1);
assert.strictEqual(elem[0].getAttribute('href'), '/abc000000000000000000001/run');
});
it('should display a "Download CSV" button for the latest result', function () {
var elem = this.last.dom.querySelectorAll('[data-test=download-csv]');
assert.strictEqual(elem.length, 1);
assert.strictEqual(elem[0].getAttribute('href'), '/abc000000000000000000001/def000000000000000000001.csv');
});
it('should display a "Download JSON" button for the latest result', function () {
var elem = this.last.dom.querySelectorAll('[data-test=download-json]');
assert.strictEqual(elem.length, 1);
assert.strictEqual(elem[0].getAttribute('href'), '/abc000000000000000000001/def000000000000000000001.json');
});
it('should display links to all results', function () {
assert.isDefined(this.last.dom.querySelectorAll('[href="/abc000000000000000000001/def000000000000000000001"]')[0]);
assert.isDefined(this.last.dom.querySelectorAll('[href="/abc000000000000000000001/def000000000000000000003"]')[0]);
});
it('should display errors', function () {
var elem = this.last.dom.querySelectorAll('[data-test=task-errors]')[0];
assert.isDefined(elem);
assert.match(elem.textContent, /errors \( 1 \)/i);
});
it('should display warnings', function () {
var elem = this.last.dom.querySelectorAll('[data-test=task-warnings]')[0];
assert.isDefined(elem);
assert.match(elem.textContent, /warnings \( 2 \)/i);
});
it('should display notices', function () {
var elem = this.last.dom.querySelectorAll('[data-test=task-notices]')[0];
assert.isDefined(elem);
assert.match(elem.textContent, /notices \( 3 \)/i);
});
});
describe('when task has no results', function () {
beforeEach(function (done) {
var req = {
method: 'GET',
endpoint: '/abc000000000000000000003'
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should display a "Run" button', function () {
var elem = this.last.dom.querySelectorAll('[data-test=run-task]');
assert.strictEqual(elem.length, 1);
assert.strictEqual(elem[0].getAttribute('href'), '/abc000000000000000000003/run');
});
it('should display a message indicating that there are no results', function () {
var alert = this.last.dom.querySelectorAll('[data-test=alert]')[0];
assert.isDefined(alert);
assert.match(alert.textContent, /there are no results to show/i);
});
});
});

View File

@@ -0,0 +1,31 @@
/* global beforeEach, describe, it */
/* jshint maxlen: false, maxstatements: false */
'use strict';
var assert = require('proclaim');
describe('GET /<task-id>/run', function () {
beforeEach(function (done) {
var req = {
method: 'GET',
endpoint: '/abc000000000000000000001/run'
};
this.navigate(req, done);
});
it('should send a 200 status', function () {
assert.strictEqual(this.last.status, 200);
});
it('should redirect me to the task page', function () {
assert.strictEqual(this.last.request.uri.pathname, '/abc000000000000000000001');
});
it('should display a success message', function () {
var alert = this.last.dom.querySelectorAll('[data-test=alert]')[0];
assert.isDefined(alert);
assert.match(alert.textContent, /new results are being generated/i);
});
});

36
test/functional/setup.js Normal file
View File

@@ -0,0 +1,36 @@
/* global afterEach, before */
/* jshint maxlen: false, maxstatements: false */
'use strict';
var config = require('../../config/test.json');
var createNavigator = require('./helper/navigate');
var createWebserviceClient = require('./helper/webservice');
var loadFixtures = require('pa11y-webservice/data/fixture/load');
var request = require('request');
// Run before all tests
before(function (done) {
this.baseUrl = 'http://localhost:' + config.port;
this.last = {};
this.navigate = createNavigator(this.baseUrl, this.last);
this.webservice = createWebserviceClient(config);
assertTestAppIsRunning(this.baseUrl, function () {
loadFixtures('test', config.webservice, done);
});
});
// Run after each test
afterEach(function (done) {
loadFixtures('test', config.webservice, done);
});
// Check that the test application is running, and exit if not
function assertTestAppIsRunning (url, done) {
request(url, function (err) {
if (err) {
console.error('Error: Test app not started; run with `grunt start-test`');
process.exit(1);
}
done();
});
}

View File

@@ -1,7 +1,7 @@
{{#content "title"}}pa11y-dashboard{{/content}}
{{#if siteMessage}}
<div class="col-md-12 clearfix">
<div class="col-md-12 clearfix" data-test="alert">
<div class="alert alert-info site-message">
<h3 class="crunch-top"><span class="pull-left glyphicon glyphicon-exclamation-sign"></span> Important</h3>
<p class="h5">{{siteMessage}}</p>
@@ -10,7 +10,7 @@
{{/if}}
{{#deleted}}
<div class="col-md-12 clearfix">
<div class="col-md-12 clearfix" data-test="alert">
<div class="alert alert-info">
<button aria-hidden="true" data-dismiss="alert" class="close" type="button">×</button>
<strong>Bye Bye URL</strong>

View File

@@ -3,14 +3,14 @@
Add a new URL
{{/content}}
<form role="form" class="col-md-12" action="/new" method="post">
<form role="form" class="col-md-12" action="/new" method="post" data-test="new-url-form">
<div class="legend">
<h1 class="h2 crunch-top">Add a new URL</h1>
</div>
{{#error}}
<div class="col-md-12 clearfix">
<div class="col-md-12 clearfix" data-test="error">
<div class="alert alert-danger">
<strong>Oh my gosh!</strong>
<p>{{.}}</p>

View File

@@ -12,10 +12,14 @@
<div class="action-buttons col-md-12 col-sm-6 clearfix">
<div class="row">
<div class="col-md-12 col-sm-6 col-xs-12">
<a href="{{mainResult.hrefCsv}}" class="btn-full-width btn btn-default">Download CSV <span class="glyphicon glyphicon-download"></span></a>
<a href="{{mainResult.hrefCsv}}" class="btn-full-width btn btn-default" data-test="download-csv">
Download CSV <span class="glyphicon glyphicon-download"></span>
</a>
</div>
<div class="col-md-12 col-sm-6 col-xs-12">
<a href="{{mainResult.hrefJson}}" class="btn-full-width btn btn-default">Download JSON <span class="glyphicon glyphicon-download"></span></a>
<a href="{{mainResult.hrefJson}}" class="btn-full-width btn btn-default" data-test="download-json">
Download JSON <span class="glyphicon glyphicon-download"></span>
</a>
</div>
</div>
</div>
@@ -37,7 +41,7 @@
<div class="col-md-9">
{{#if mainResult.count.error}}
<div class="heading label-danger showing first" id="errors">
<div class="heading label-danger showing first" id="errors" data-test="task-errors">
<span data-role="expander" class="pull-right expander"> - </span>
Errors ( {{mainResult.count.error}} )
</div>
@@ -58,7 +62,7 @@
{{/if}}
{{#if mainResult.count.warning}}
<div class="heading label-warning" id="warnings">
<div class="heading label-warning" id="warnings" data-test="task-warnings">
<span data-role="expander" class="pull-right expander"> + </span>
Warnings ( {{mainResult.count.warning}} )
</div>
@@ -79,7 +83,7 @@
{{/if}}
{{#if mainResult.count.notice}}
<div class="heading label-info" id="notices">
<div class="heading label-info" id="notices" data-test="task-notices">
<span data-role="expander" class="pull-right expander"> + </span>
Notices ( {{mainResult.count.notice}} )
</div>

View File

@@ -7,7 +7,9 @@
</div>
<div class="col-md-3 col-sm-3 text-right run-details">
{{#unless readonly}}
<a href="{{task.hrefRun}}" class="btn btn-success">Run <span class="glyphicon glyphicon-play"></span></a>
<a href="{{task.hrefRun}}" class="btn btn-success" data-test="run-task">
Run <span class="glyphicon glyphicon-play"></span>
</a>
{{/unless}}
{{#if mainResult}}
<div class="date">Last run : {{date-format mainResult.date format="DD MMM YYYY"}}</div>

View File

@@ -8,14 +8,14 @@
<p class="supersize-me crunch">+</p>
</span>
{{else}}
<a class="well task-card-link crunch-bottom" data-role="add-task" href="/new">
<a class="well task-card-link crunch-bottom" data-role="add-task" href="/new" data-test="add-task">
<p class="h3 crunch">Add new URL</p>
<p class="supersize-me crunch">+</p>
</a>
{{/if}}
</li>
{{#each tasks}}
<li class="col-md-4 col-sm-6 task-card">
<li class="col-md-4 col-sm-6 task-card" data-test="task">
<a class="well task-card-link crunch-bottom" title="Details for URL {{simplify-url url}}" href="{{href}}">
<p class="h3">{{name}}</p>
<p class="h5">({{standard}})</p>

View File

@@ -3,7 +3,7 @@
Delete {{task.url}} ({{task.standard}})
{{/content}}
<form class="col-md-12" action="{{task.hrefDelete}}" method="post">
<form class="col-md-12" action="{{task.hrefDelete}}" method="post" data-test="delete-url-form">
<div class="legend">
<legend>Delete URL ({{simplify-url task.url}})</legend>
</div>

View File

@@ -4,7 +4,7 @@
{{/content}}
{{#added}}
<div class="col-md-12 clearfix">
<div class="col-md-12 clearfix" data-test="alert">
<div class="alert alert-success">
<button aria-hidden="true" data-dismiss="alert" class="close" type="button">×</button>
<strong>Whoop whoop!</strong>
@@ -14,7 +14,7 @@
{{/added}}
{{#running}}
<div class="col-md-12 clearfix">
<div class="col-md-12 clearfix" data-test="alert">
<div class="alert alert-success">
<button aria-hidden="true" data-dismiss="alert" class="close" type="button">×</button>
<strong>New results incoming!</strong>
@@ -41,7 +41,7 @@
{{#if mainResult}}
{{> result}}
{{else}}
<div class="col-md-12">
<div class="col-md-12" data-test="alert">
<div class="alert alert-info">
<h4>There are no results to show</h4>
<p>pa11y has not been run against this URL yet so there are no results to show.</p>