diff --git a/app/components/user-avatar.js b/app/components/user-avatar.js index 75d97efad8e..9d68f93a65b 100644 --- a/app/components/user-avatar.js +++ b/app/components/user-avatar.js @@ -5,7 +5,7 @@ import { computed } from '@ember/object'; export default Component.extend({ size: 'small', user: null, - attributeBindings: ['src', 'width', 'height'], + attributeBindings: ['src', 'width', 'height', 'alt'], tagName: 'img', width: computed('size', function() { @@ -20,6 +20,10 @@ export default Component.extend({ height: readOnly('width'), + alt: computed('user', function() { + return `${this.get('user.name')} (${this.get('user.login')})`; + }), + src: computed('size', 'user', function() { return `${this.get('user.avatar')}&s=${this.get('width') * 2}`; }) diff --git a/app/styles/app.scss b/app/styles/app.scss index d21c08f0ae3..77167e1a195 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -84,7 +84,6 @@ body { button { background: none; - outline: 0; border: 0; padding: 10px 0; @@ -130,7 +129,6 @@ body { font-size: 90%; border: none; color: black; - outline: 0; margin-left: 30px; padding: 5px 5px 5px 25px; background-color: white; @@ -141,6 +139,15 @@ body { @include border-radius(15px); } +form.search label { + position: absolute; + left: -10000px; + top: auto; + width: 1px; + height: 1px; + overflow: hidden; +} + #header input.search { width: 100%; margin-left: 16px; diff --git a/app/styles/crate.scss b/app/styles/crate.scss index 44e2f092b74..21c6a54a36c 100644 --- a/app/styles/crate.scss +++ b/app/styles/crate.scss @@ -190,6 +190,12 @@ font-size: 90%; margin-bottom: 20px; + ol { + list-style: none; + padding: 0; + } + ol, li { display: inline; } + a { color: $main-color-light; text-decoration: none; diff --git a/app/styles/home.scss b/app/styles/home.scss index 09fb5f6d242..e8316b36979 100644 --- a/app/styles/home.scss +++ b/app/styles/home.scss @@ -20,7 +20,7 @@ margin-right: 10px; } - &:hover { @include vertical-gradient($start_dark, $end_dark); outline: 0; } + &:hover, &:focus { @include vertical-gradient($start_dark, $end_dark); outline: 0; } &.active { @include vertical-gradient($start_dark, $end_dark); outline: 0; } &[disabled] { @include vertical-gradient($start_light, $end_light); @@ -111,9 +111,17 @@ button.small { line-height: 20px; } - > div { margin: 0 15px; } + > section { margin: 0 15px; } - ul { list-style: none; padding: 0; } + > section[aria-busy="true"] > h2::after { + content: ''; + background-image: url('/assets/ajax-loader.gif'); + display: inline-block; + height: 16px; + width: 16px; + } + + ul, ol { list-style: none; padding: 0; } li { margin: 8px 0; } li a { @@ -143,7 +151,7 @@ button.small { @include flex-wrap(wrap); @include justify-content(left); - > div { + > section { margin: 0; padding: 0 15px; width: 33.33%; diff --git a/app/styles/me.scss b/app/styles/me.scss index 07a8068f4ad..0f09ce629a6 100644 --- a/app/styles/me.scss +++ b/app/styles/me.scss @@ -177,7 +177,7 @@ margin-bottom: 20px; background-color: $bg; color: white; - &:hover { background-color: darken($bg, 10%); } + &:hover, &:focus { background-color: darken($bg, 10%); } } } diff --git a/app/templates/application.hbs b/app/templates/application.hbs index 8e60329cfdc..0979f8285fc 100644 --- a/app/templates/application.hbs +++ b/app/templates/application.hbs @@ -1,12 +1,8 @@ {{title "Cargo: packages for Rust" separator=' - ' prepend=true}} {{google-jsapi}} - - - - -
-
-

New Crates {{#if dataTask.isRunning}}{{/if}}

+
+

New Crates

{{crate-list crates=model.new_crates}} -
-
-

Most Downloaded {{#if dataTask.isRunning}}{{/if}}

+ +
+

Most Downloaded

{{crate-list crates=model.most_downloaded}} -
-
-

Just Updated {{#if dataTask.isRunning}}{{/if}}

+ +
+

Just Updated

{{crate-list crates=model.just_updated}} -
-
-

Most Recent Downloads {{#if dataTask.isRunning}}{{/if}}

+ +
+

Most Recent Downloads

{{crate-list crates=model.most_recently_downloaded}} -
-
-

Popular Keywords {{#link-to 'keywords'}}(see all){{/link-to}} {{#if dataTask.isRunning}}{{/if}}

+ +
+

Popular Keywords {{#link-to 'keywords'}}(see all){{/link-to}}

{{keyword-list keywords=model.popular_keywords}} -
-
-

Popular Categories {{#link-to 'categories'}}(see all){{/link-to}} {{#if dataTask.isRunning}}{{/if}}

+ +
+

Popular Categories {{#link-to 'categories'}}(see all){{/link-to}}

{{category-list categories=model.popular_categories}} -
+
diff --git a/config/environment.js b/config/environment.js index e30fa3535a7..66948c54bc8 100644 --- a/config/environment.js +++ b/config/environment.js @@ -39,6 +39,11 @@ module.exports = function(environment) { // ENV.APP.LOG_TRANSITIONS = true; // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; // ENV.APP.LOG_VIEW_LOOKUPS = true; + ENV['ember-a11y-testing'] = { + componentOptions: { + turnAuditOff: true, + } + }; } if (environment === 'test') { diff --git a/package-lock.json b/package-lock.json index b70495ad1c2..c5d5c79aaa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -454,6 +454,12 @@ "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", "dev": true }, + "axe-core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-2.6.1.tgz", + "integrity": "sha512-QFfI3d+x/v92HJWGBaNfgrxdfon9/xXzd04YYfm5w5NJQOLex8qkJCctOt7+ky6e1l9zcQ5E7jsvbnTgQzyfTw==", + "dev": true + }, "babel-code-frame": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", @@ -3151,6 +3157,51 @@ "integrity": "sha1-qWfr3P6O0Ag/wkTRiUAiqOgRPqI=", "dev": true }, + "ember-a11y-testing": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ember-a11y-testing/-/ember-a11y-testing-0.5.0.tgz", + "integrity": "sha512-GgUqyY6hFIzyTly4rvezr5FoSjOshLcbLPu+vgBkhRHOehD18jAWtHVosOnBRby7tbxobp28DbU9083rz65bQg==", + "dev": true, + "requires": { + "axe-core": "2.6.1", + "broccoli-funnel": "2.0.1", + "ember-cli-babel": "6.8.2", + "ember-cli-version-checker": "2.1.0" + }, + "dependencies": { + "broccoli-funnel": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/broccoli-funnel/-/broccoli-funnel-2.0.1.tgz", + "integrity": "sha512-C8Lnp9TVsSSiZMGEF16C0dCiNg2oJqUKwuZ1K4kVC6qRPG/2Cj/rtB5kRCC9qEbwqhX71bDbfHROx0L3J7zXQg==", + "dev": true, + "requires": { + "array-equal": "1.0.0", + "blank-object": "1.0.2", + "broccoli-plugin": "1.3.0", + "debug": "2.6.8", + "fast-ordered-set": "1.0.3", + "fs-tree-diff": "0.5.6", + "heimdalljs": "0.2.5", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "path-posix": "1.0.0", + "rimraf": "2.6.1", + "symlink-or-copy": "1.1.8", + "walk-sync": "0.3.2" + } + }, + "ember-cli-version-checker": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ember-cli-version-checker/-/ember-cli-version-checker-2.1.0.tgz", + "integrity": "sha512-ssiNyVTp+PphroFum8guHX9py4xU1PCxkRYgb25NxumgjpKTPjhkgTfpRRKXlIQe+/wVMmhf+Uv6w9vSLZKWKQ==", + "dev": true, + "requires": { + "resolve": "1.3.3", + "semver": "5.3.0" + } + } + } + }, "ember-ajax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ember-ajax/-/ember-ajax-3.0.0.tgz", diff --git a/package.json b/package.json index bfa35cfafe3..5c810d87382 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "devDependencies": { "babel-plugin-transform-object-rest-spread": "^6.23.0", "broccoli-asset-rev": "2.5.0", + "ember-a11y-testing": "^0.5.0", "ember-ajax": "^3.0.0", "ember-cli": "~2.17.0", "ember-cli-app-version": "^3.0.0", diff --git a/tests/acceptance/categories-test.js b/tests/acceptance/categories-test.js index 4a819c19814..ad423603575 100644 --- a/tests/acceptance/categories-test.js +++ b/tests/acceptance/categories-test.js @@ -1,9 +1,31 @@ import { test } from 'qunit'; import { visit } from 'ember-native-dom-helpers'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; import moduleForAcceptance from 'cargo/tests/helpers/module-for-acceptance'; +import axeConfig from '../axe-config'; moduleForAcceptance('Acceptance | categories'); +test('is accessible', async function(assert) { + assert.expect(0); + + server.create('category', { category: 'API bindings', crates_cnt: 0 }); + server.create('category', { category: 'Algorithms', crates_cnt: 1 }); + server.create('category', { category: 'Asynchronous', crates_cnt: 3910 }); + + await visit('/categories'); + await a11yAudit(axeConfig); +}); + +test('category/:category_id is accessible', async function(assert) { + assert.expect(0); + + server.create('category', { category: 'Algorithms', crates_cnt: 1 }); + + await visit('/categories/algorithms'); + await a11yAudit(axeConfig); +}); + test('listing categories', async function(assert) { server.create('category', { category: 'API bindings', crates_cnt: 0 }); server.create('category', { category: 'Algorithms', crates_cnt: 1 }); diff --git a/tests/acceptance/crate-test.js b/tests/acceptance/crate-test.js index 0b374514070..a496ed0d9ba 100644 --- a/tests/acceptance/crate-test.js +++ b/tests/acceptance/crate-test.js @@ -1,10 +1,52 @@ import Service from '@ember/service'; import { test } from 'qunit'; import { click, visit, currentURL, currentRouteName, fillIn } from 'ember-native-dom-helpers'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; import moduleForAcceptance from 'cargo/tests/helpers/module-for-acceptance'; +import axeConfig from '../axe-config'; moduleForAcceptance('Acceptance | crate page'); +test('is accessible', async function(assert) { + assert.expect(0); + + server.create('crate', 'withVersion', { id: 'nanomsg' }); + + await visit('/'); + await a11yAudit(axeConfig); +}); + +test('/crates/:crate is accessible', async function(assert) { + assert.expect(0); + + server.create('crate', { id: 'nanomsg', max_version: '0.6.1' }); + server.create('version', { crate: 'nanomsg', num: '0.6.0' }); + server.create('version', { crate: 'nanomsg', num: '0.6.1' }); + + await visit('/crates/nanomsg'); + await a11yAudit(axeConfig); +}); + +test('/crates/:crate/:version is accessible', async function(assert) { + assert.expect(0); + + server.create('crate', { id: 'nanomsg', max_version: '0.6.1' }); + server.create('version', { crate: 'nanomsg', num: '0.6.0' }); + server.create('version', { crate: 'nanomsg', num: '0.6.1' }); + + await visit('/crates/nanomsg/0.6.0'); + await a11yAudit(axeConfig); +}); + +test('/crates/:crate/owners is accessible', async function(assert) { + assert.expect(0); + + server.loadFixtures(); + + await visit('/crates/nanomsg/owners'); + await a11yAudit(axeConfig); +}); + test('visiting a crate page from the front page', async function(assert) { server.create('crate', 'withVersion', { id: 'nanomsg' }); diff --git a/tests/acceptance/crates-test.js b/tests/acceptance/crates-test.js index 8af3f6c3927..f53faf12889 100644 --- a/tests/acceptance/crates-test.js +++ b/tests/acceptance/crates-test.js @@ -1,9 +1,29 @@ import { test } from 'qunit'; import { click, visit, currentURL } from 'ember-native-dom-helpers'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; import moduleForAcceptance from 'cargo/tests/helpers/module-for-acceptance'; +import axeConfig from '../axe-config'; moduleForAcceptance('Acceptance | crates page'); +test('is accessible', async function(assert) { + assert.expect(0); + + server.loadFixtures(); + + await visit('/'); + await a11yAudit(axeConfig); +}); + +test('/crates is accessible', async function(assert) { + assert.expect(0); + + server.loadFixtures(); + + await visit('/crates'); + await a11yAudit(axeConfig); +}); + test('visiting the crates page from the front page', async function(assert) { server.loadFixtures(); diff --git a/tests/acceptance/front-page-test.js b/tests/acceptance/front-page-test.js index d14cb0d5072..0d585819829 100644 --- a/tests/acceptance/front-page-test.js +++ b/tests/acceptance/front-page-test.js @@ -1,9 +1,20 @@ import { test } from 'qunit'; import { visit, currentURL } from 'ember-native-dom-helpers'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; import moduleForAcceptance from 'cargo/tests/helpers/module-for-acceptance'; +import axeConfig from '../axe-config'; moduleForAcceptance('Acceptance | front page'); +test('is accessible', async function(assert) { + assert.expect(0); + + server.loadFixtures(); + + await visit('/'); + await a11yAudit(axeConfig); +}); + test('visiting /', async function(assert) { server.loadFixtures(); diff --git a/tests/acceptance/keyword-test.js b/tests/acceptance/keyword-test.js index 0531c59c53d..12bcd7e73ff 100644 --- a/tests/acceptance/keyword-test.js +++ b/tests/acceptance/keyword-test.js @@ -1,9 +1,20 @@ import { test } from 'qunit'; import { visit } from 'ember-native-dom-helpers'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; import moduleForAcceptance from 'cargo/tests/helpers/module-for-acceptance'; +import axeConfig from '../axe-config'; moduleForAcceptance('Acceptance | keywords'); +test('keyword/:keyword_id is accessible', async function(assert) { + assert.expect(0); + + server.create('keyword', { id: 'network', keyword: 'network', crates_cnt: 38 }); + + await visit('keywords/network'); + await a11yAudit(axeConfig); +}); + test('keyword/:keyword_id index default sort is recent-downloads', async function(assert) { server.create('keyword', { id: 'network', keyword: 'network', crates_cnt: 38 }); diff --git a/tests/acceptance/search-test.js b/tests/acceptance/search-test.js index f8afb744abb..9d53eaed15f 100644 --- a/tests/acceptance/search-test.js +++ b/tests/acceptance/search-test.js @@ -1,10 +1,21 @@ import { test } from 'qunit'; import { fillIn, visit, triggerEvent, currentURL, blur } from 'ember-native-dom-helpers'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; import moduleForAcceptance from 'cargo/tests/helpers/module-for-acceptance'; import { triggerKeyDown, triggerKeyPress } from 'ember-keyboard'; +import axeConfig from '../axe-config'; moduleForAcceptance('Acceptance | search'); +test('/search?q=rust is accessible', async function(assert) { + assert.expect(0); + + server.loadFixtures(); + + await visit('/'); + await a11yAudit(axeConfig); +}); + test('searching for "rust"', async function(assert) { server.loadFixtures(); diff --git a/tests/acceptance/team-page-test.js b/tests/acceptance/team-page-test.js index f17f0246ecb..d2a9f12a616 100644 --- a/tests/acceptance/team-page-test.js +++ b/tests/acceptance/team-page-test.js @@ -1,9 +1,20 @@ import { test } from 'qunit'; import { visit } from 'ember-native-dom-helpers'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; import moduleForAcceptance from 'cargo/tests/helpers/module-for-acceptance'; +import axeConfig from '../axe-config'; moduleForAcceptance('Acceptance | team page'); +test('is accessible', async function(assert) { + assert.expect(0); + + server.loadFixtures(); + + await visit('/teams/github:org:thehydroimpulse'); + await a11yAudit(axeConfig); +}); + test('has team organization display', async function(assert) { server.loadFixtures(); diff --git a/tests/acceptance/user-page-test.js b/tests/acceptance/user-page-test.js index 516fca94654..c66717873b0 100644 --- a/tests/acceptance/user-page-test.js +++ b/tests/acceptance/user-page-test.js @@ -1,9 +1,20 @@ import { test } from 'qunit'; import { visit } from 'ember-native-dom-helpers'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; import moduleForAcceptance from 'cargo/tests/helpers/module-for-acceptance'; +import axeConfig from '../axe-config'; moduleForAcceptance('Acceptance | user page'); +test('is accessible', async function(assert) { + assert.expect(0); + + server.loadFixtures(); + + await visit('/users/thehydroimpulse'); + await a11yAudit(axeConfig); +}); + test('has user display', async function(assert) { server.loadFixtures(); diff --git a/tests/axe-config.js b/tests/axe-config.js new file mode 100644 index 00000000000..5231fe0e897 --- /dev/null +++ b/tests/axe-config.js @@ -0,0 +1,7 @@ +export default { + rules: { + 'color-contrast': { + enabled: false, + } + } +};