access_lint 0.0.4 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 61c3167df7c28d8cf3963573711fd781347ca7b3
4
- data.tar.gz: b8250f03445261ed81b387eab7061c8aa84f5783
3
+ metadata.gz: f7ae823b09a9888e551139aa7b9601c3e4a57dc0
4
+ data.tar.gz: 18648a27bf348a584f520c7adeefea99097b6dda
5
5
  SHA512:
6
- metadata.gz: 8f2af9a4ace4927f8163e84b5b4cfae2c7bf60963172f36ff558b85c7f76537c33e9ae2d53ce32d9cce35fca2805eb28df12ee031f53402c833b29891420dc8c
7
- data.tar.gz: ec469e6b9e55c78d54d4a34805a1d4e4548f16e63f06c6f70a30e82cf277261e746458440fc36302b7a45c933db1c49c2c1816380cb9a2114277ab25cd32071e
6
+ metadata.gz: 5da54a7b154e263273922ff4ce69e1e328b9a5b59a1e8f21eee1d004b4d082b82ea548e6b9b487c26af82fb014533252b66f5473898d4e4aa58dc44b755d68d8
7
+ data.tar.gz: 77dbf7dd4963f49683fdd5ca18c4124787adf57464ebd978c4eab3044d4107321301fd54293f6c4ba0ba0ab92b09af205d7a10521f5370097d1534c0205c593d
data/Vendorfile CHANGED
@@ -1,6 +1,3 @@
1
1
  folder 'vendor/google-chrome/accessibility-developer-tools' do
2
- from 'https://github.com/GoogleChrome/accessibility-developer-tools.git' do |checkout_location|
3
- file 'README.md'
4
- folder 'gen'
5
- end
2
+ file 'gen/axs_testing.js', 'https://raw.github.com/GoogleChrome/accessibility-developer-tools/stable/dist/js/axs_testing.js'
6
3
  end
data/access_lint.gemspec CHANGED
@@ -22,4 +22,5 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.3"
24
24
  spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "json"
25
26
  end
@@ -1,4 +1,5 @@
1
1
  require 'access_lint'
2
+ require 'json'
2
3
 
3
4
  module AccessLint
4
5
  class Audit
@@ -9,13 +10,10 @@ module AccessLint
9
10
  end
10
11
 
11
12
  def run
12
- create_report
13
- end
14
-
15
- private
16
-
17
- def create_report
18
- %x( phantomjs #{RUNNER_PATH} #{@target})
13
+ result = `phantomjs #{RUNNER_PATH} #{@target}`
14
+ if !result.nil?
15
+ JSON.parse(result)
16
+ end
19
17
  end
20
18
  end
21
19
  end
@@ -1,10 +1,9 @@
1
1
  require 'thor'
2
2
 
3
3
  module AccessLint
4
- class CLI < Thor::Group
5
- desc 'Run an accessibility audit on a target file or url.'
6
- argument :target
7
- def audit
4
+ class CLI < Thor
5
+ desc 'audit', 'audit TARGET'
6
+ def audit(target)
8
7
  puts Audit.new(target).run
9
8
  end
10
9
  end
@@ -1,3 +1,3 @@
1
1
  module AccessLint
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -2,12 +2,14 @@ require 'spec_helper'
2
2
 
3
3
  module AccessLint
4
4
  describe Audit do
5
+ let(:target) { double(:target) }
6
+
5
7
  describe '#run' do
6
- let(:target) { double(:target) }
7
- it 'returns a string' do
8
- audit = Audit.new(target).run
9
- audit.should be_an_instance_of String
10
- audit.should_not be_empty
8
+ let(:audit) { Audit.new(target) }
9
+
10
+ it 'runs phantomjs' do
11
+ audit.should_receive(:`).with(/^phantomjs.*/)
12
+ audit.run
11
13
  end
12
14
  end
13
15
  end
@@ -1,4 +1,3 @@
1
- // do stuff
2
1
  var page = require('webpage').create(),
3
2
  system = require('system'),
4
3
  url;
@@ -11,18 +10,40 @@ if (system.args.length !== 2) {
11
10
  page.open(url, function (status) {
12
11
  if (status === 'success') {
13
12
  page.injectJs('../../google-chrome/accessibility-developer-tools/gen/axs_testing.js');
13
+
14
14
  var report = page.evaluate(function() {
15
- var results = axs.Audit.run();
16
- return axs.Audit.createReport(results);
15
+ var raw_results = axs.Audit.run();
16
+ var results = [];
17
+
18
+ for(var i = 0; i < raw_results.length; i++) {
19
+ var element_names = [];
20
+ var elements = raw_results[i]['elements'];
21
+
22
+ if(elements !== undefined) {
23
+ for(var j = 0; j < elements.length; j++) {
24
+ var element = elements[j];
25
+ var tmp = document.createElement("div");
26
+ tmp.appendChild(element);
27
+ element_names.push(tmp.innerHTML);
28
+ }
29
+ }
30
+
31
+ results.push({
32
+ status: raw_results[i]['result'],
33
+ title: raw_results[i]['rule']['heading'],
34
+ severity: raw_results[i]['rule']['severity'],
35
+ element_names: element_names,
36
+ elements: raw_results[i]['elements']
37
+ });
38
+ }
39
+
40
+ return results;
17
41
  });
18
- console.log(report);
42
+
43
+ console.log(JSON.stringify(report));
19
44
  phantom.exit();
20
45
  } else {
21
- console.log('Failed to load the page at ' +
22
- url +
23
- ". Status was: " +
24
- status
25
- );
46
+ console.log('Failed to load the page at ' + url + ".");
26
47
  phantom.exit();
27
48
  }
28
49
  });
@@ -1,11 +1,33 @@
1
+ /*
2
+ * Copyright 2013 Google Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * Generated from http://github.com/GoogleChrome/accessibility-developer-tools/tree/33515adf3dcd6e17d48f8a9dde27c60865545b70
17
+ *
18
+ * See project README for build steps.
19
+ */
20
+
21
+ // AUTO-GENERATED CONTENT BELOW: DO NOT EDIT! See above for details.
22
+
1
23
  var COMPILED = !0, goog = goog || {};
2
24
  goog.global = this;
3
25
  goog.exportPath_ = function(a, b, c) {
4
26
  a = a.split(".");
5
27
  c = c || goog.global;
6
- !(a[0] in c) && c.execScript && c.execScript("var " + a[0]);
28
+ a[0] in c || !c.execScript || c.execScript("var " + a[0]);
7
29
  for(var d;a.length && (d = a.shift());) {
8
- !a.length && void 0 !== b ? c[d] = b : c = c[d] ? c[d] : c[d] = {}
30
+ a.length || void 0 === b ? c = c[d] ? c[d] : c[d] = {} : c[d] = b
9
31
  }
10
32
  };
11
33
  goog.define = function(a, b) {
@@ -389,7 +411,7 @@ goog.scope = function(a) {
389
411
  var axs = {};
390
412
  axs.browserUtils = {};
391
413
  axs.browserUtils.matchSelector = function(a, b) {
392
- return a.webkitMatchesSelector ? a.webkitMatchesSelector(b) : a.mozMatchesSelector(b)
414
+ return a.webkitMatchesSelector ? a.webkitMatchesSelector(b) : a.mozMatchesSelector ? a.mozMatchesSelector(b) : !1
393
415
  };
394
416
  axs.constants = {};
395
417
  axs.constants.ARIA_ROLES = {alert:{namefrom:["author"], parent:["region"]}, alertdialog:{namefrom:["author"], namerequired:!0, parent:["alert", "dialog"]}, application:{namefrom:["author"], namerequired:!0, parent:["landmark"]}, article:{namefrom:["author"], parent:["document", "region"]}, banner:{namefrom:["author"], parent:["landmark"]}, button:{childpresentational:!0, namefrom:["contents", "author"], namerequired:!0, parent:["command"], properties:["aria-expanded", "aria-pressed"]}, checkbox:{namefrom:["contents",
@@ -425,8 +447,7 @@ axs.constants.addAllPropertiesToSet_ = function(a, b, c) {
425
447
  }
426
448
  }
427
449
  if(a.parent) {
428
- a = a.parent;
429
- for(d = 0;d < a.length;d++) {
450
+ for(a = a.parent, d = 0;d < a.length;d++) {
430
451
  axs.constants.addAllPropertiesToSet_(axs.constants.ARIA_ROLES[a[d]], b, c)
431
452
  }
432
453
  }
@@ -519,7 +540,7 @@ axs.utils.elementIsTransparent = function(a) {
519
540
  axs.utils.elementHasZeroArea = function(a) {
520
541
  a = a.getBoundingClientRect();
521
542
  var b = a.top - a.bottom;
522
- return!(a.right - a.left) || !b ? !0 : !1
543
+ return a.right - a.left && b ? !1 : !0
523
544
  };
524
545
  axs.utils.elementIsOutsideScrollArea = function(a) {
525
546
  a = a.getBoundingClientRect();
@@ -534,19 +555,14 @@ axs.utils.overlappingElement = function(a) {
534
555
  return null
535
556
  }
536
557
  var b = a.getBoundingClientRect(), b = document.elementFromPoint((b.left + b.right) / 2, (b.top + b.bottom) / 2);
537
- return null != b && b != a && !axs.utils.isAncestor(b, a) && !axs.utils.isAncestor(a, b) ? b : null
558
+ return null == b || b == a || axs.utils.isAncestor(b, a) || axs.utils.isAncestor(a, b) ? null : b
538
559
  };
539
560
  axs.utils.elementIsHtmlControl = function(a) {
540
561
  var b = a.ownerDocument.defaultView;
541
562
  return a instanceof b.HTMLButtonElement || a instanceof b.HTMLInputElement || a instanceof b.HTMLSelectElement || a instanceof b.HTMLTextAreaElement ? !0 : !1
542
563
  };
543
564
  axs.utils.elementIsAriaWidget = function(a) {
544
- if(a.hasAttribute("role") && (a = a.getAttribute("role"))) {
545
- if((a = axs.constants.ARIA_ROLES[a]) && "widget" in a.allParentRolesSet) {
546
- return!0
547
- }
548
- }
549
- return!1
565
+ return a.hasAttribute("role") && (a = a.getAttribute("role")) && (a = axs.constants.ARIA_ROLES[a]) && "widget" in a.allParentRolesSet ? !0 : !1
550
566
  };
551
567
  axs.utils.elementIsVisible = function(a) {
552
568
  if(axs.utils.elementIsTransparent(a) || axs.utils.elementHasZeroArea(a) || axs.utils.elementIsOutsideScrollArea(a)) {
@@ -666,12 +682,12 @@ axs.utils.suggestColors = function(a, b, c, d) {
666
682
  f.contrast = l.toFixed(2);
667
683
  e.AA = f
668
684
  }
669
- axs.utils.isLowContrast(c, d, !0) && (1 >= n && 0 <= n) && (n = axs.utils.translateColor(q, n), l = axs.utils.calculateContrastRatio(n, a), f = {}, f.fg = axs.utils.colorToString(n), f.bg = axs.utils.colorToString(a), f.contrast = l.toFixed(2), e.AAA = f);
685
+ axs.utils.isLowContrast(c, d, !0) && 1 >= n && 0 <= n && (n = axs.utils.translateColor(q, n), l = axs.utils.calculateContrastRatio(n, a), f = {}, f.fg = axs.utils.colorToString(n), f.bg = axs.utils.colorToString(a), f.contrast = l.toFixed(2), e.AAA = f);
670
686
  h = axs.utils.luminanceFromContrastRatio(g, h + 0.02, !m);
671
687
  g = axs.utils.luminanceFromContrastRatio(g, k + 0.02, !m);
672
688
  a = axs.utils.toYCC(a);
673
- !("AA" in e) && (axs.utils.isLowContrast(c, d, !1) && 1 >= h && 0 <= h) && (k = axs.utils.translateColor(a, h), l = axs.utils.calculateContrastRatio(b, k), f = {}, f.bg = axs.utils.colorToString(k), f.fg = axs.utils.colorToString(b), f.contrast = l.toFixed(2), e.AA = f);
674
- !("AAA" in e) && (axs.utils.isLowContrast(c, d, !0) && 1 >= g && 0 <= g) && (c = axs.utils.translateColor(a, g), l = axs.utils.calculateContrastRatio(b, c), f = {}, f.bg = axs.utils.colorToString(c), f.fg = axs.utils.colorToString(b), f.contrast = l.toFixed(2), e.AAA = f);
689
+ !("AA" in e) && axs.utils.isLowContrast(c, d, !1) && 1 >= h && 0 <= h && (k = axs.utils.translateColor(a, h), l = axs.utils.calculateContrastRatio(b, k), f = {}, f.bg = axs.utils.colorToString(k), f.fg = axs.utils.colorToString(b), f.contrast = l.toFixed(2), e.AA = f);
690
+ !("AAA" in e) && axs.utils.isLowContrast(c, d, !0) && 1 >= g && 0 <= g && (c = axs.utils.translateColor(a, g), l = axs.utils.calculateContrastRatio(b, c), f = {}, f.bg = axs.utils.colorToString(c), f.fg = axs.utils.colorToString(b), f.contrast = l.toFixed(2), e.AAA = f);
675
691
  return e
676
692
  };
677
693
  axs.utils.flattenColors = function(a, b) {
@@ -762,7 +778,7 @@ axs.utils.getContrastRatioForElementWithComputedStyle = function(a, b) {
762
778
  return null
763
779
  }
764
780
  var d = axs.utils.getFgColor(a, b, c);
765
- return!d ? null : axs.utils.calculateContrastRatio(d, c)
781
+ return d ? axs.utils.calculateContrastRatio(d, c) : null
766
782
  };
767
783
  axs.utils.isNativeTextElement = function(a) {
768
784
  var b = a.tagName.toLowerCase();
@@ -893,7 +909,7 @@ axs.utils.isValidTokenValue = function(a, b) {
893
909
  return axs.utils.isPossibleValue(b, axs.constants.ARIA_PROPERTIES[c].valuesSet, a)
894
910
  };
895
911
  axs.utils.isPossibleValue = function(a, b, c) {
896
- return!b[a] ? {valid:!1, value:a, reason:'"' + a + '" is not a valid value for ' + c, possibleValues:Object.keys(b)} : {valid:!0, value:a}
912
+ return b[a] ? {valid:!0, value:a} : {valid:!1, value:a, reason:'"' + a + '" is not a valid value for ' + c, possibleValues:Object.keys(b)}
897
913
  };
898
914
  axs.utils.isValidBoolean = function(a) {
899
915
  try {
@@ -904,7 +920,7 @@ axs.utils.isValidBoolean = function(a) {
904
920
  return"boolean" != typeof b ? {valid:!1, value:a, reason:'"' + a + '" is not a true/false value'} : {valid:!0, value:b}
905
921
  };
906
922
  axs.utils.isValidIDRefValue = function(a, b) {
907
- return 0 == a.length ? {valid:!0, idref:a} : !b.ownerDocument.getElementById(a) ? {valid:!1, idref:a, reason:'No element with ID "' + a + '"'} : {valid:!0, idref:a}
923
+ return 0 == a.length ? {valid:!0, idref:a} : b.ownerDocument.getElementById(a) ? {valid:!0, idref:a} : {valid:!1, idref:a, reason:'No element with ID "' + a + '"'}
908
924
  };
909
925
  axs.utils.isValidNumber = function(a) {
910
926
  try {
@@ -989,19 +1005,20 @@ axs.properties.getColorProperties = function(a) {
989
1005
  (a = axs.properties.getContrastRatioProperties(a)) && (b.contrastRatio = a);
990
1006
  return 0 == Object.keys(b).length ? null : b
991
1007
  };
992
- axs.properties.getContrastRatioProperties = function(a) {
993
- for(var b = document.evaluate(axs.properties.TEXT_CONTENT_XPATH, a, null, XPathResult.ANY_TYPE, null), c = !1, d = b.iterateNext();null != d;d = b.iterateNext()) {
1008
+ axs.properties.hasDirectTextDescendant = function(a) {
1009
+ for(var b = (a.nodeType == Node.DOCUMENT_NODE ? a : a.ownerDocument).evaluate(axs.properties.TEXT_CONTENT_XPATH, a, null, XPathResult.ANY_TYPE, null), c = !1, d = b.iterateNext();null != d;d = b.iterateNext()) {
994
1010
  if(d === a) {
995
1011
  c = !0;
996
1012
  break
997
1013
  }
998
1014
  }
999
- if(!c) {
1015
+ return c
1016
+ };
1017
+ axs.properties.getContrastRatioProperties = function(a) {
1018
+ if(!axs.properties.hasDirectTextDescendant(a)) {
1000
1019
  return null
1001
1020
  }
1002
- b = {};
1003
- c = window.getComputedStyle(a, null);
1004
- d = axs.utils.getBgColor(c, a);
1021
+ var b = {}, c = window.getComputedStyle(a, null), d = axs.utils.getBgColor(c, a);
1005
1022
  if(!d) {
1006
1023
  return null
1007
1024
  }
@@ -1014,7 +1031,7 @@ axs.properties.getContrastRatioProperties = function(a) {
1014
1031
  }
1015
1032
  b.value = a.toFixed(2);
1016
1033
  axs.utils.isLowContrast(a, c) && (b.alert = !0);
1017
- (a = axs.utils.suggestColors(d, e, a, c)) && Object.keys(a).length && (b.suggestedColors = a);
1034
+ (c = axs.utils.suggestColors(d, e, a, c)) && Object.keys(c).length && (b.suggestedColors = c);
1018
1035
  return b
1019
1036
  };
1020
1037
  axs.properties.findTextAlternatives = function(a, b, c) {
@@ -1032,18 +1049,10 @@ axs.properties.findTextAlternatives = function(a, b, c) {
1032
1049
  var e = {type:"text"};
1033
1050
  e.text = c.getAttribute("aria-label");
1034
1051
  e.lastWord = axs.properties.getLastWord(e.text);
1035
- if(a) {
1036
- e.unused = !0
1037
- }else {
1038
- if(!d || !axs.utils.elementIsHtmlControl(c)) {
1039
- a = e.text
1040
- }
1041
- }
1052
+ a ? e.unused = !0 : d && axs.utils.elementIsHtmlControl(c) || (a = e.text);
1042
1053
  b.ariaLabel = e
1043
1054
  }
1044
- if(!c.hasAttribute("role") || "presentation" != c.getAttribute("role")) {
1045
- a = axs.properties.getTextFromHostLangaugeAttributes(c, b, a)
1046
- }
1055
+ c.hasAttribute("role") && "presentation" == c.getAttribute("role") || (a = axs.properties.getTextFromHostLangaugeAttributes(c, b, a));
1047
1056
  if(d && axs.utils.elementIsHtmlControl(c)) {
1048
1057
  e = c.ownerDocument.defaultView;
1049
1058
  if(c instanceof e.HTMLInputElement) {
@@ -1262,49 +1271,49 @@ axs.AuditRule = function(a) {
1262
1271
  }
1263
1272
  this.name = a.name;
1264
1273
  this.severity = a.severity;
1265
- this.relevantNodesSelector_ = a.relevantNodesSelector;
1274
+ this.relevantElementMatcher_ = a.relevantElementMatcher;
1266
1275
  this.test_ = a.test;
1267
1276
  this.code = a.code;
1268
1277
  this.heading = a.heading || "";
1269
1278
  this.url = a.url || "";
1270
1279
  this.requiresConsoleAPI = !!a.opt_requiresConsoleAPI
1271
1280
  };
1272
- axs.AuditRule.requiredFields = "name severity relevantNodesSelector test code heading".split(" ");
1281
+ axs.AuditRule.requiredFields = "name severity relevantElementMatcher test code heading".split(" ");
1273
1282
  axs.AuditRule.NOT_APPLICABLE = {result:axs.constants.AuditResult.NA};
1274
- axs.AuditRule.prototype.addNode = function(a, b) {
1283
+ axs.AuditRule.prototype.addElement = function(a, b) {
1275
1284
  a.push(b)
1276
1285
  };
1277
- axs.AuditRule.prototype.run = function(a, b) {
1278
- function c(a) {
1279
- for(var b = 0;b < d.length;b++) {
1280
- if(axs.browserUtils.matchSelector(a, d[b])) {
1281
- return!0
1282
- }
1283
- }
1284
- return!1
1286
+ axs.AuditRule.collectMatchingElements = function(a, b, c) {
1287
+ if(a.nodeType == Node.ELEMENT_NODE) {
1288
+ var d = a
1285
1289
  }
1286
- var d = a || [], e = this.relevantNodesSelector_(b || document), f = [];
1287
- if(e instanceof XPathResult) {
1288
- if(e.resultType == XPathResult.ORDERED_NODE_SNAPSHOT_TYPE) {
1289
- if(!e.snapshotLength) {
1290
- return axs.AuditRule.NOT_APPLICABLE
1291
- }
1292
- for(var g = 0;g < e.snapshotLength;g++) {
1293
- var h = e.snapshotItem(g);
1294
- this.test_(h) && !c(h) && this.addNode(f, h)
1290
+ d && b.call(null, d) && c.push(d);
1291
+ for(a = a.firstChild;null != a;) {
1292
+ axs.AuditRule.collectMatchingElements(a, b, c), a = a.nextSibling
1293
+ }
1294
+ };
1295
+ axs.AuditRule.prototype.run = function(a, b) {
1296
+ var c = a || [], d = [];
1297
+ axs.AuditRule.collectMatchingElements(b || document, this.relevantElementMatcher_, d);
1298
+ var e = [];
1299
+ if(!d.length) {
1300
+ return{result:axs.constants.AuditResult.NA}
1301
+ }
1302
+ for(var f = 0;f < d.length;f++) {
1303
+ var g = d[f], h;
1304
+ a: {
1305
+ h = g;
1306
+ for(var k = 0;k < c.length;k++) {
1307
+ if(axs.browserUtils.matchSelector(h, c[k])) {
1308
+ h = !0;
1309
+ break a
1310
+ }
1295
1311
  }
1296
- }else {
1297
- return console.warn("Unknown XPath result type", e), null
1298
- }
1299
- }else {
1300
- if(!e.length) {
1301
- return{result:axs.constants.AuditResult.NA}
1302
- }
1303
- for(g = 0;g < e.length;g++) {
1304
- h = e[g], this.test_(h) && !c(h) && this.addNode(f, h)
1312
+ h = !1
1305
1313
  }
1314
+ !h && this.test_(g) && this.addElement(e, g)
1306
1315
  }
1307
- return{result:f.length ? axs.constants.AuditResult.FAIL : axs.constants.AuditResult.PASS, elements:f}
1316
+ return{result:e.length ? axs.constants.AuditResult.FAIL : axs.constants.AuditResult.PASS, elements:e}
1308
1317
  };
1309
1318
  axs.AuditRule.specs = {};
1310
1319
  axs.AuditRules = {};
@@ -1429,71 +1438,94 @@ axs.Audit.accessibilityErrorMessage = function(a) {
1429
1438
  return b
1430
1439
  };
1431
1440
  goog.exportSymbol("axs.Audit.accessibilityErrorMessage", axs.Audit.accessibilityErrorMessage);
1432
- axs.AuditRule.specs.mainRoleOnInappropriateElement = {name:"mainRoleOnInappropriateElement", heading:"role=main should only appear on significant elements", url:"", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
1433
- return a.querySelectorAll("[role~=main]")
1434
- }, test:function(a) {
1435
- return axs.utils.isInlineElement(a) || 50 > axs.properties.findTextAlternatives(a, {}).length ? !0 : !1
1436
- }, code:"AX_ARIA_04"};
1437
- axs.AuditRule.specs.audioWithoutControls = {name:"audioWithoutControls", heading:"Audio elements should have controls", url:"", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
1438
- return a.querySelectorAll("audio[autoplay]")
1441
+ axs.AuditRule.specs.audioWithoutControls = {name:"audioWithoutControls", heading:"Audio elements should have controls", url:"", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
1442
+ return axs.browserUtils.matchSelector(a, "audio[autoplay]")
1439
1443
  }, test:function(a) {
1440
1444
  return!a.querySelectorAll("[controls]").length && 3 < a.duration
1441
1445
  }, code:"AX_AUDIO_01"};
1442
- axs.AuditRule.specs.pageWithoutTitle = {name:"pageWithoutTitle", heading:"The web page should have a title that describes topic or purpose", url:"", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
1443
- return a
1444
- }, test:function(a) {
1445
- a = a.querySelector("head");
1446
- if(!a) {
1447
- return!0
1446
+ axs.AuditRule.specs.badAriaAttributeValue = {name:"badAriaAttributeValue", heading:"ARIA state and property values must be valid", url:"", severity:axs.constants.Severity.SEVERE, relevantElementMatcher:function(a) {
1447
+ var b = "", c;
1448
+ for(c in axs.constants.ARIA_PROPERTIES) {
1449
+ b += "[aria-" + c + "],"
1448
1450
  }
1449
- a = a.querySelector("title");
1450
- return!a.length || !a[0].textContent
1451
- }, code:"AX_TITLE_01"};
1452
- axs.AuditRule.specs.lowContrastElements = {name:"lowContrastElements", heading:"Text elements should have a reasonable contrast ratio", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_color_01--text-elements-should-have-a-reasonable-contrast-ratio", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
1453
- return document.evaluate('.//text()[normalize-space(.)!=""]/parent::*[name()!="script"]', a, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
1454
- }, test:function(a) {
1455
- var b = window.getComputedStyle(a, null);
1456
- return(a = axs.utils.getContrastRatioForElementWithComputedStyle(b, a)) && axs.utils.isLowContrast(a, b)
1457
- }, code:"AX_COLOR_01"};
1458
- axs.AuditRule.specs.nonExistentAriaLabelledbyElement = {name:"nonExistentAriaLabelledbyElement", heading:"aria-labelledby attributes should refer to an element which exists in the DOM", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_aria_02--aria-labelledby-attributes-should-refer-to-an-element-which-exists-in-the-dom", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
1459
- return a.querySelectorAll("[aria-labelledby]")
1451
+ b = b.substring(0, b.length - 1);
1452
+ return axs.browserUtils.matchSelector(a, b)
1460
1453
  }, test:function(a) {
1461
- a = a.getAttribute("aria-labelledby").split(/\s+/);
1462
- for(var b = 0;b < a.length;b++) {
1463
- if(!document.getElementById(a[b])) {
1464
- return!0
1454
+ for(var b in axs.constants.ARIA_PROPERTIES) {
1455
+ var c = "aria-" + b;
1456
+ if(a.hasAttribute(c)) {
1457
+ var d = a.getAttribute(c);
1458
+ if(!axs.utils.getAriaPropertyValue(c, d, a).valid) {
1459
+ return!0
1460
+ }
1465
1461
  }
1466
1462
  }
1467
1463
  return!1
1468
- }, code:"AX_ARIA_02"};
1469
- axs.AuditRule.specs.requiredAriaAttributeMissing = {name:"requiredAriaAttributeMissing", heading:"Elements with ARIA roles must have all required attributes for that role", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_aria_03--elements-with-aria-roles-must-have-all-required-attributes-for-that-role", severity:axs.constants.Severity.SEVERE, relevantNodesSelector:function(a) {
1470
- return a.querySelectorAll("[role]")
1464
+ }, code:"AX_ARIA_04"};
1465
+ axs.AuditRule.specs.badAriaRole = {name:"badAriaRole", heading:"Elements with ARIA roles must use a valid, non-abstract ARIA role", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_aria_01--elements-with-aria-roles-must-use-a-valid-non-abstract-aria-role", severity:axs.constants.Severity.SEVERE, relevantElementMatcher:function(a) {
1466
+ return axs.browserUtils.matchSelector(a, "[role]")
1471
1467
  }, test:function(a) {
1472
- var b = axs.utils.getRoles(a);
1473
- if(!b.valid) {
1468
+ return!axs.utils.getRoles(a).valid
1469
+ }, code:"AX_ARIA_01"};
1470
+ axs.AuditRule.specs.controlsWithoutLabel = {name:"controlsWithoutLabel", heading:"Controls and media elements should have labels", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_text_01--controls-and-media-elements-should-have-labels", severity:axs.constants.Severity.SEVERE, relevantElementMatcher:function(a) {
1471
+ if(!axs.browserUtils.matchSelector(a, 'input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), video:not([disabled])')) {
1474
1472
  return!1
1475
1473
  }
1476
- for(var c = 0;c < b.roles.length;c++) {
1477
- var d = b.roles[c].details.requiredPropertiesSet, e;
1478
- for(e in d) {
1479
- if(d = e.replace(/^aria-/, ""), !("defaultValue" in axs.constants.ARIA_PROPERTIES[d]) && !a.hasAttribute(e)) {
1480
- return!0
1481
- }
1474
+ if(0 <= a.tabIndex) {
1475
+ return!0
1476
+ }
1477
+ for(a = a.parentElement;null != a;a = a.parentElement) {
1478
+ if(axs.utils.elementIsAriaWidget(a)) {
1479
+ return!1
1482
1480
  }
1483
1481
  }
1484
- }, code:"AX_ARIA_03"};
1485
- axs.AuditRule.specs.focusableElementNotVisibleAndNotAriaHidden = {name:"focusableElementNotVisibleAndNotAriaHidden", heading:"These elements are focusable but either invisible or obscured by another element", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_focus_01--these-elements-are-focusable-but-either-invisible-or-obscured-by-another-element", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
1486
- return a.querySelectorAll(axs.utils.FOCUSABLE_ELEMENTS_SELECTOR)
1482
+ return!0
1483
+ }, test:function(a) {
1484
+ return axs.utils.isElementOrAncestorHidden(a) || "input" == a.tagName.toLowerCase() && "button" == a.type && a.value.length || "button" == a.tagName.toLowerCase() && a.textContent.replace(/^\s+|\s+$/g, "").length ? !1 : axs.utils.hasLabel(a) ? !1 : !0
1485
+ }, code:"AX_TEXT_01", ruleName:"Controls and media elements should have labels"};
1486
+ axs.AuditRule.specs.focusableElementNotVisibleAndNotAriaHidden = {name:"focusableElementNotVisibleAndNotAriaHidden", heading:"These elements are focusable but either invisible or obscured by another element", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_focus_01--these-elements-are-focusable-but-either-invisible-or-obscured-by-another-element", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
1487
+ if(!axs.browserUtils.matchSelector(a, axs.utils.FOCUSABLE_ELEMENTS_SELECTOR)) {
1488
+ return!1
1489
+ }
1490
+ if(0 <= a.tabIndex) {
1491
+ return!0
1492
+ }
1493
+ for(a = a.parentElement;null != a;a = a.parentElement) {
1494
+ if(axs.utils.elementIsAriaWidget(a)) {
1495
+ return!1
1496
+ }
1497
+ }
1498
+ return!0
1487
1499
  }, test:function(a) {
1488
1500
  return axs.utils.isElementOrAncestorHidden(a) ? !1 : !axs.utils.elementIsVisible(a)
1489
1501
  }, code:"AX_FOCUS_01"};
1490
- axs.AuditRule.specs.elementsWithMeaningfulBackgroundImage = {name:"elementsWithMeaningfulBackgroundImage", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
1491
- a = a.querySelectorAll("*");
1492
- for(var b = [], c = 0;c < a.length;c++) {
1493
- var d = a[c];
1494
- axs.utils.isElementOrAncestorHidden(d) || b.push(d)
1502
+ axs.AuditRule.specs.imagesWithoutAltText = {name:"imagesWithoutAltText", heading:"Images should have an alt attribute", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_text_02--images-should-have-an-alt-attribute-unless-they-have-an-aria-role-of-presentation", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
1503
+ return axs.browserUtils.matchSelector(a, "img") && !axs.utils.isElementOrAncestorHidden(a)
1504
+ }, test:function(a) {
1505
+ return!a.hasAttribute("alt") && "presentation" != a.getAttribute("role")
1506
+ }, code:"AX_TEXT_02"};
1507
+ axs.AuditRule.specs.linkWithUnclearPurpose = {name:"linkWithUnclearPurpose", heading:"The purpose of each link should be clear from the link text", url:"", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
1508
+ return axs.browserUtils.matchSelector(a, "a")
1509
+ }, test:function(a) {
1510
+ return/^\s*click\s*here\s*[^a-z]?$/i.test(a.textContent)
1511
+ }, code:"AX_TITLE_01"};
1512
+ axs.AuditRule.specs.lowContrastElements = {name:"lowContrastElements", heading:"Text elements should have a reasonable contrast ratio", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_color_01--text-elements-should-have-a-reasonable-contrast-ratio", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
1513
+ return axs.properties.hasDirectTextDescendant(a)
1514
+ }, test:function(a) {
1515
+ var b = window.getComputedStyle(a, null);
1516
+ return(a = axs.utils.getContrastRatioForElementWithComputedStyle(b, a)) && axs.utils.isLowContrast(a, b)
1517
+ }, code:"AX_COLOR_01"};
1518
+ axs.AuditRule.specs.mainRoleOnInappropriateElement = {name:"mainRoleOnInappropriateElement", heading:"role=main should only appear on significant elements", url:"", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
1519
+ return axs.browserUtils.matchSelector(a, "[role~=main]")
1520
+ }, test:function(a) {
1521
+ if(axs.utils.isInlineElement(a)) {
1522
+ return!0
1495
1523
  }
1496
- return b
1524
+ a = axs.properties.findTextAlternatives(a, {});
1525
+ return!a || 50 > a.length ? !0 : !1
1526
+ }, code:"AX_ARIA_04"};
1527
+ axs.AuditRule.specs.elementsWithMeaningfulBackgroundImage = {name:"elementsWithMeaningfulBackgroundImage", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
1528
+ return!axs.utils.isElementOrAncestorHidden(a)
1497
1529
  }, heading:"Meaningful images should not be used in element backgrounds", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_image_01--meaningful-images-should-not-be-used-in-element-backgrounds", test:function(a) {
1498
1530
  if(a.textContent && 0 < a.textContent.length) {
1499
1531
  return!1
@@ -1507,63 +1539,48 @@ axs.AuditRule.specs.elementsWithMeaningfulBackgroundImage = {name:"elementsWithM
1507
1539
  a = parseInt(a.height, 10);
1508
1540
  return 150 > b && 150 > a
1509
1541
  }, code:"AX_IMAGE_01"};
1510
- axs.AuditRule.specs.linkWithUnclearPurpose = {name:"linkWithUnclearPurpose", heading:"The purpose of each link should be clear from the link text", url:"", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
1511
- return a.querySelectorAll("a")
1542
+ axs.AuditRule.specs.nonExistentAriaLabelledbyElement = {name:"nonExistentAriaLabelledbyElement", heading:"aria-labelledby attributes should refer to an element which exists in the DOM", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_aria_02--aria-labelledby-attributes-should-refer-to-an-element-which-exists-in-the-dom", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
1543
+ return axs.browserUtils.matchSelector(a, "[aria-labelledby]")
1512
1544
  }, test:function(a) {
1513
- return/^\s*click\s*here\s*[^a-z]?$/i.test(a.textContent)
1514
- }, code:"AX_TITLE_01"};
1515
- axs.AuditRule.specs.controlsWithoutLabel = {name:"controlsWithoutLabel", heading:"Controls and media elements should have labels", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_text_01--controls-and-media-elements-should-have-labels", severity:axs.constants.Severity.SEVERE, relevantNodesSelector:function(a) {
1516
- return a.querySelectorAll('input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), video:not([disabled])')
1545
+ a = a.getAttribute("aria-labelledby").split(/\s+/);
1546
+ for(var b = 0;b < a.length;b++) {
1547
+ if(!document.getElementById(a[b])) {
1548
+ return!0
1549
+ }
1550
+ }
1551
+ return!1
1552
+ }, code:"AX_ARIA_02"};
1553
+ axs.AuditRule.specs.pageWithoutTitle = {name:"pageWithoutTitle", heading:"The web page should have a title that describes topic or purpose", url:"", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
1554
+ return"html" == a.tagName.toLowerCase()
1517
1555
  }, test:function(a) {
1518
- return axs.utils.isElementOrAncestorHidden(a) || "input" == a.tagName.toLowerCase() && "button" == a.type && a.value.length || "button" == a.tagName.toLowerCase() && a.textContent.replace(/^\s+|\s+$/g, "").length ? !1 : !axs.utils.hasLabel(a) ? !0 : !1
1519
- }, code:"AX_TEXT_01", ruleName:"Controls and media elements should have labels"};
1520
- axs.AuditRule.specs.videoWithoutCaptions = {name:"videoWithoutCaptions", heading:"Video elements should use <track> elements to provide captions", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_video_01--video-elements-should-use-track-elements-to-provide-captions", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
1521
- return a.querySelectorAll("video")
1556
+ a = a.querySelector("head");
1557
+ return a ? (a = a.querySelector("title")) ? !a.textContent : !0 : !0
1558
+ }, code:"AX_TITLE_01"};
1559
+ axs.AuditRule.specs.requiredAriaAttributeMissing = {name:"requiredAriaAttributeMissing", heading:"Elements with ARIA roles must have all required attributes for that role", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_aria_03--elements-with-aria-roles-must-have-all-required-attributes-for-that-role", severity:axs.constants.Severity.SEVERE, relevantElementMatcher:function(a) {
1560
+ return axs.browserUtils.matchSelector(a, "[role]")
1522
1561
  }, test:function(a) {
1523
- return!a.querySelectorAll("track[kind=captions]").length
1524
- }, code:"AX_VIDEO_01"};
1525
- axs.AuditRule.specs.badAriaAttributeValue = {name:"badAriaAttributeValue", heading:"ARIA state and property values must be valid", url:"", severity:axs.constants.Severity.SEVERE, relevantNodesSelector:function(a) {
1526
- var b = "", c;
1527
- for(c in axs.constants.ARIA_PROPERTIES) {
1528
- b += "[aria-" + c + "],"
1562
+ var b = axs.utils.getRoles(a);
1563
+ if(!b.valid) {
1564
+ return!1
1529
1565
  }
1530
- b = b.substring(0, b.length - 1);
1531
- return a.querySelectorAll(b)
1532
- }, test:function(a) {
1533
- for(var b in axs.constants.ARIA_PROPERTIES) {
1534
- var c = "aria-" + b;
1535
- if(a.hasAttribute(c)) {
1536
- var d = a.getAttribute(c);
1537
- if(!axs.utils.getAriaPropertyValue(c, d, a).valid) {
1566
+ for(var c = 0;c < b.roles.length;c++) {
1567
+ var d = b.roles[c].details.requiredPropertiesSet, e;
1568
+ for(e in d) {
1569
+ if(d = e.replace(/^aria-/, ""), !("defaultValue" in axs.constants.ARIA_PROPERTIES[d] || a.hasAttribute(e))) {
1538
1570
  return!0
1539
1571
  }
1540
1572
  }
1541
1573
  }
1542
- return!1
1543
- }, code:"AX_ARIA_04"};
1544
- axs.AuditRule.specs.badAriaRole = {name:"badAriaRole", heading:"Elements with ARIA roles must use a valid, non-abstract ARIA role", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_aria_01--elements-with-aria-roles-must-use-a-valid-non-abstract-aria-role", severity:axs.constants.Severity.SEVERE, relevantNodesSelector:function(a) {
1545
- return a.querySelectorAll("[role]")
1546
- }, test:function(a) {
1547
- return!axs.utils.getRoles(a).valid
1548
- }, code:"AX_ARIA_01"};
1549
- axs.AuditRule.specs.imagesWithoutAltText = {name:"imagesWithoutAltText", heading:"Images should have an alt attribute", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_text_02--images-should-have-an-alt-attribute-unless-they-have-an-aria-role-of-presentation", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
1550
- a = a.querySelectorAll("img");
1551
- for(var b = [], c = 0;c < a.length;c++) {
1552
- var d = a[c];
1553
- axs.utils.isElementOrAncestorHidden(d) || b.push(d)
1554
- }
1555
- return b
1556
- }, test:function(a) {
1557
- return!a.hasAttribute("alt") && "presentation" != a.getAttribute("role")
1558
- }, code:"AX_TEXT_02"};
1559
- axs.AuditRule.specs.unfocusableElementsWithOnClick = {name:"unfocusableElementsWithOnClick", heading:"Elements with onclick handlers must be focusable", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_focus_02--elements-with-onclick-handlers-must-be-focusable", severity:axs.constants.Severity.WARNING, opt_requiresConsoleAPI:!0, relevantNodesSelector:function(a) {
1560
- a = a.querySelectorAll("*");
1561
- for(var b = [], c = 0;c < a.length;c++) {
1562
- var d = a[c];
1563
- d instanceof d.ownerDocument.defaultView.HTMLBodyElement || axs.utils.isElementOrAncestorHidden(d) || "click" in getEventListeners(d) && b.push(d)
1564
- }
1565
- return b
1574
+ }, code:"AX_ARIA_03"};
1575
+ axs.AuditRule.specs.unfocusableElementsWithOnClick = {name:"unfocusableElementsWithOnClick", heading:"Elements with onclick handlers must be focusable", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_focus_02--elements-with-onclick-handlers-must-be-focusable", severity:axs.constants.Severity.WARNING, opt_requiresConsoleAPI:!0, relevantElementMatcher:function(a) {
1576
+ return a instanceof a.ownerDocument.defaultView.HTMLBodyElement || axs.utils.isElementOrAncestorHidden(a) && "selected" == a.className ? !1 : "click" in getEventListeners(a) ? !0 : !1
1566
1577
  }, test:function(a) {
1567
1578
  return!a.hasAttribute("tabindex") && !axs.utils.isElementImplicitlyFocusable(a)
1568
1579
  }, code:"AX_FOCUS_02"};
1580
+ axs.AuditRule.specs.videoWithoutCaptions = {name:"videoWithoutCaptions", heading:"Video elements should use <track> elements to provide captions", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_video_01--video-elements-should-use-track-elements-to-provide-captions", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
1581
+ return axs.browserUtils.matchSelector(a, "video")
1582
+ }, test:function(a) {
1583
+ return!a.querySelectorAll("track[kind=captions]").length
1584
+ }, code:"AX_VIDEO_01"};
1585
+
1569
1586
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: access_lint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cameron Cundiff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-22 00:00:00.000000000 Z
11
+ date: 2013-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - '>='
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  description: Run an accessibility audit on a file or URL from the command line.
56
70
  email:
57
71
  - ckundo@gmail.com