access_lint 0.0.4 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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