govuk_publishing_components 21.22.1 → 21.22.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -3
  3. data/app/assets/stylesheets/govuk_publishing_components/components/_breadcrumbs.scss +34 -0
  4. data/app/assets/stylesheets/govuk_publishing_components/components/_document-list.scss +2 -2
  5. data/app/views/govuk_publishing_components/components/_breadcrumbs.html.erb +0 -3
  6. data/app/views/govuk_publishing_components/components/_contextual_breadcrumbs.html.erb +0 -2
  7. data/lib/govuk_publishing_components/version.rb +1 -1
  8. data/node_modules/axe-core/CHANGELOG.md +70 -10
  9. data/node_modules/axe-core/axe.js +1295 -632
  10. data/node_modules/axe-core/axe.min.js +2 -2
  11. data/node_modules/axe-core/bower.json +1 -1
  12. data/node_modules/axe-core/doc/API.md +1 -0
  13. data/node_modules/axe-core/doc/aria-supported.md +0 -1
  14. data/node_modules/axe-core/doc/developer-guide.md +2 -2
  15. data/node_modules/axe-core/doc/rule-descriptions.md +91 -87
  16. data/node_modules/axe-core/lib/checks/aria/abstractrole.js +10 -1
  17. data/node_modules/axe-core/lib/checks/aria/abstractrole.json +4 -1
  18. data/node_modules/axe-core/lib/checks/aria/fallbackrole.js +1 -0
  19. data/node_modules/axe-core/lib/checks/aria/fallbackrole.json +11 -0
  20. data/node_modules/axe-core/lib/checks/aria/invalidrole.js +14 -3
  21. data/node_modules/axe-core/lib/checks/aria/invalidrole.json +4 -1
  22. data/node_modules/axe-core/lib/checks/aria/required-children.js +41 -30
  23. data/node_modules/axe-core/lib/checks/aria/valid-attr-value.js +24 -9
  24. data/node_modules/axe-core/lib/checks/aria/valid-attr-value.json +2 -2
  25. data/node_modules/axe-core/lib/checks/color/color-contrast.js +23 -7
  26. data/node_modules/axe-core/lib/checks/color/color-contrast.json +7 -1
  27. data/node_modules/axe-core/lib/checks/keyboard/focusable-disabled.js +5 -0
  28. data/node_modules/axe-core/lib/checks/keyboard/focusable-modal-open.js +14 -0
  29. data/node_modules/axe-core/lib/checks/keyboard/focusable-modal-open.json +11 -0
  30. data/node_modules/axe-core/lib/checks/keyboard/focusable-not-tabbable.js +5 -0
  31. data/node_modules/axe-core/lib/checks/keyboard/page-has-elm.js +5 -1
  32. data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-after.js +2 -0
  33. data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-banner.json +1 -0
  34. data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-contentinfo.json +1 -0
  35. data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-main.json +1 -0
  36. data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate.js +14 -2
  37. data/node_modules/axe-core/lib/checks/lists/only-listitems.js +43 -49
  38. data/node_modules/axe-core/lib/checks/lists/only-listitems.json +4 -1
  39. data/node_modules/axe-core/lib/checks/media/no-autoplay-audio.js +93 -0
  40. data/node_modules/axe-core/lib/checks/media/no-autoplay-audio.json +15 -0
  41. data/node_modules/axe-core/lib/checks/navigation/identical-links-same-purpose-after.js +100 -0
  42. data/node_modules/axe-core/lib/checks/navigation/identical-links-same-purpose.js +31 -0
  43. data/node_modules/axe-core/lib/checks/navigation/identical-links-same-purpose.json +12 -0
  44. data/node_modules/axe-core/lib/checks/navigation/region.js +42 -8
  45. data/node_modules/axe-core/lib/checks/navigation/region.json +0 -1
  46. data/node_modules/axe-core/lib/checks/shared/svg-non-empty-title.js +4 -0
  47. data/node_modules/axe-core/lib/checks/shared/svg-non-empty-title.json +11 -0
  48. data/node_modules/axe-core/lib/commons/aria/index.js +12 -4
  49. data/node_modules/axe-core/lib/commons/color/get-background-color.js +5 -157
  50. data/node_modules/axe-core/lib/commons/dom/get-element-stack.js +272 -174
  51. data/node_modules/axe-core/lib/commons/dom/is-focusable.js +3 -1
  52. data/node_modules/axe-core/lib/commons/dom/is-hidden-with-css.js +2 -2
  53. data/node_modules/axe-core/lib/commons/dom/is-modal-open.js +98 -0
  54. data/node_modules/axe-core/lib/commons/dom/is-visible.js +57 -2
  55. data/node_modules/axe-core/lib/commons/dom/url-props-from-attribute.js +143 -0
  56. data/node_modules/axe-core/lib/commons/dom/visually-contains.js +62 -12
  57. data/node_modules/axe-core/lib/commons/matches/attributes.js +12 -8
  58. data/node_modules/axe-core/lib/commons/matches/from-definition.js +15 -10
  59. data/node_modules/axe-core/lib/commons/matches/index.js +11 -9
  60. data/node_modules/axe-core/lib/commons/matches/node-name.js +11 -21
  61. data/node_modules/axe-core/lib/commons/matches/properties.js +12 -9
  62. data/node_modules/axe-core/lib/commons/text/unicode.js +27 -24
  63. data/node_modules/axe-core/lib/core/base/virtual-node/virtual-node.js +11 -3
  64. data/node_modules/axe-core/lib/core/constants.js +1 -1
  65. data/node_modules/axe-core/lib/core/reporters/v1.js +6 -3
  66. data/node_modules/axe-core/lib/core/utils/contains.js +1 -1
  67. data/node_modules/axe-core/lib/core/utils/is-hidden.js +6 -6
  68. data/node_modules/axe-core/lib/core/utils/matches.js +263 -0
  69. data/node_modules/axe-core/lib/core/utils/preload-media.js +65 -0
  70. data/node_modules/axe-core/lib/core/utils/preload.js +33 -24
  71. data/node_modules/axe-core/lib/core/utils/qsa.js +7 -208
  72. data/node_modules/axe-core/lib/rules/aria-hidden-focus.json +5 -1
  73. data/node_modules/axe-core/lib/rules/aria-roles.json +1 -1
  74. data/node_modules/axe-core/lib/rules/button-name.json +1 -1
  75. data/node_modules/axe-core/lib/rules/color-contrast-matches.js +1 -1
  76. data/node_modules/axe-core/lib/rules/color-contrast.json +0 -3
  77. data/node_modules/axe-core/lib/rules/html-namespace-matches.js +1 -0
  78. data/node_modules/axe-core/lib/rules/identical-links-same-purpose-matches.js +13 -0
  79. data/node_modules/axe-core/lib/rules/identical-links-same-purpose.json +14 -0
  80. data/node_modules/axe-core/lib/rules/landmark-no-duplicate-banner.json +1 -1
  81. data/node_modules/axe-core/lib/rules/landmark-no-duplicate-contentinfo.json +1 -1
  82. data/node_modules/axe-core/lib/rules/landmark-no-duplicate-main.json +12 -0
  83. data/node_modules/axe-core/lib/rules/landmark-one-main.json +2 -2
  84. data/node_modules/axe-core/lib/rules/link-name.json +2 -4
  85. data/node_modules/axe-core/lib/rules/meta-viewport.json +1 -1
  86. data/node_modules/axe-core/lib/rules/no-autoplay-audio-matches.js +18 -0
  87. data/node_modules/axe-core/lib/rules/no-autoplay-audio.json +15 -0
  88. data/node_modules/axe-core/lib/rules/region.json +1 -2
  89. data/node_modules/axe-core/lib/rules/role-img-alt.json +2 -1
  90. data/node_modules/axe-core/lib/rules/svg-img-alt.json +24 -0
  91. data/node_modules/axe-core/lib/rules/svg-namespace-matches.js +1 -0
  92. data/node_modules/axe-core/locales/da.json +782 -0
  93. data/node_modules/axe-core/locales/fr.json +221 -42
  94. data/node_modules/axe-core/locales/ja.json +124 -24
  95. data/node_modules/axe-core/package.json +29 -26
  96. data/node_modules/axe-core/sri-history.json +26 -10
  97. metadata +27 -9
  98. data/node_modules/axe-core/doc/examples/jasmine/package-lock.json +0 -1489
  99. data/node_modules/axe-core/doc/examples/jest_react/package-lock.json +0 -7525
  100. data/node_modules/axe-core/doc/examples/mocha/package-lock.json +0 -1671
  101. data/node_modules/axe-core/doc/examples/phantomjs/package-lock.json +0 -862
  102. data/node_modules/axe-core/doc/examples/qunit/package-lock.json +0 -2951
  103. data/node_modules/axe-core/lib/checks/navigation/region-after.js +0 -1
  104. data/node_modules/axe-core/typings/axe-core/axe-core-tests.js +0 -151
@@ -11,8 +11,8 @@
11
11
  "plural": "Invalid ARIA attribute values: ${data.values}"
12
12
  },
13
13
  "incomplete": {
14
- "singular": "ARIA attribute element ID does not exist on the page: ${data.values}",
15
- "plural": "ARIA attributes element ID does not exist on the page: ${data.values}"
14
+ "noId": "ARIA attribute element ID does not exist on the page: ${data.needsReview}",
15
+ "ariaCurrent": "ARIA attribute value is invalid and will be treated as \"aria-current=true\": ${data.needsReview}"
16
16
  }
17
17
  }
18
18
  }
@@ -4,6 +4,23 @@ if (!dom.isVisible(node, false)) {
4
4
  return true;
5
5
  }
6
6
 
7
+ const visibleText = text.visibleVirtual(virtualNode, false, true);
8
+ const ignoreUnicode = !!(options || {}).ignoreUnicode;
9
+ const textContainsOnlyUnicode =
10
+ text.hasUnicode(visibleText, {
11
+ nonBmp: true
12
+ }) &&
13
+ text.sanitize(
14
+ text.removeUnicode(visibleText, {
15
+ nonBmp: true
16
+ })
17
+ ) === '';
18
+
19
+ if (textContainsOnlyUnicode && ignoreUnicode) {
20
+ this.data({ messageKey: 'nonBmp' });
21
+ return undefined;
22
+ }
23
+
7
24
  const noScroll = !!(options || {}).noScroll;
8
25
  const bgNodes = [];
9
26
  const bgColor = color.getBackgroundColor(node, bgNodes, noScroll);
@@ -11,9 +28,8 @@ const fgColor = color.getForegroundColor(node, noScroll, bgColor);
11
28
 
12
29
  const nodeStyle = window.getComputedStyle(node);
13
30
  const fontSize = parseFloat(nodeStyle.getPropertyValue('font-size'));
14
- const fontWeight = nodeStyle.getPropertyValue('font-weight');
15
- const bold =
16
- ['bold', 'bolder', '600', '700', '800', '900'].indexOf(fontWeight) !== -1;
31
+ const fontWeight = parseFloat(nodeStyle.getPropertyValue('font-weight'));
32
+ const bold = !isNaN(fontWeight) && fontWeight >= 700;
17
33
 
18
34
  const cr = color.hasValidContrastRatio(bgColor, fgColor, fontSize, bold);
19
35
 
@@ -28,11 +44,11 @@ if (bgColor === null) {
28
44
  }
29
45
 
30
46
  const equalRatio = truncatedResult === 1;
31
- const shortTextContent =
32
- text.visibleVirtual(virtualNode, false, true).length === 1;
47
+ const shortTextContent = visibleText.length === 1;
48
+ const ignoreLength = !!(options || {}).ignoreLength;
33
49
  if (equalRatio) {
34
50
  missing = color.incompleteData.set('bgColor', 'equalRatio');
35
- } else if (shortTextContent) {
51
+ } else if (shortTextContent && !ignoreLength) {
36
52
  // Check that the text content is a single character long
37
53
  missing = 'shortTextContent';
38
54
  }
@@ -55,7 +71,7 @@ if (
55
71
  fgColor === null ||
56
72
  bgColor === null ||
57
73
  equalRatio ||
58
- (shortTextContent && !cr.isValid)
74
+ (shortTextContent && !ignoreLength && !cr.isValid)
59
75
  ) {
60
76
  missing = null;
61
77
  color.incompleteData.clear();
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "id": "color-contrast",
3
3
  "evaluate": "color-contrast.js",
4
+ "options": {
5
+ "noScroll": false,
6
+ "ignoreUnicode": true,
7
+ "ignoreLength": false
8
+ },
4
9
  "metadata": {
5
10
  "impact": "serious",
6
11
  "messages": {
@@ -17,7 +22,8 @@
17
22
  "elmPartiallyObscuring": "Element's background color could not be determined because it partially overlaps other elements",
18
23
  "outsideViewport": "Element's background color could not be determined because it's outside the viewport",
19
24
  "equalRatio": "Element has a 1:1 contrast ratio with the background",
20
- "shortTextContent": "Element content is too short to determine if it is actual text content"
25
+ "shortTextContent": "Element content is too short to determine if it is actual text content",
26
+ "nonBmp": "Element content contains only non-text characters"
21
27
  }
22
28
  }
23
29
  }
@@ -20,6 +20,11 @@ const relatedNodes = tabbableElements.reduce((out, { actualNode: el }) => {
20
20
  }
21
21
  return out;
22
22
  }, []);
23
+
23
24
  this.relatedNodes(relatedNodes);
24
25
 
26
+ if (relatedNodes.length && axe.commons.dom.isModalOpen()) {
27
+ return true;
28
+ }
29
+
25
30
  return relatedNodes.length === 0;
@@ -0,0 +1,14 @@
1
+ const tabbableElements = virtualNode.tabbableElements.map(
2
+ ({ actualNode }) => actualNode
3
+ );
4
+
5
+ if (!tabbableElements || !tabbableElements.length) {
6
+ return true;
7
+ }
8
+
9
+ if (axe.commons.dom.isModalOpen()) {
10
+ this.relatedNodes(tabbableElements);
11
+ return undefined;
12
+ }
13
+
14
+ return true;
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "focusable-modal-open",
3
+ "evaluate": "focusable-modal-open.js",
4
+ "metadata": {
5
+ "impact": "serious",
6
+ "messages": {
7
+ "pass": "No focusable elements while a modal is open",
8
+ "incomplete": "Check that focusable elements are not tabbable in the current state"
9
+ }
10
+ }
11
+ }
@@ -20,6 +20,11 @@ const relatedNodes = tabbableElements.reduce((out, { actualNode: el }) => {
20
20
  }
21
21
  return out;
22
22
  }, []);
23
+
23
24
  this.relatedNodes(relatedNodes);
24
25
 
26
+ if (relatedNodes.length > 0 && axe.commons.dom.isModalOpen()) {
27
+ return true;
28
+ }
29
+
25
30
  return relatedNodes.length === 0;
@@ -4,6 +4,10 @@ if (!options || !options.selector || typeof options.selector !== 'string') {
4
4
  );
5
5
  }
6
6
 
7
- const matchingElms = axe.utils.querySelectorAll(virtualNode, options.selector);
7
+ const matchingElms = axe.utils.querySelectorAllFilter(
8
+ virtualNode,
9
+ options.selector,
10
+ vNode => axe.commons.dom.isVisible(vNode.actualNode)
11
+ );
8
12
  this.relatedNodes(matchingElms.map(vNode => vNode.actualNode));
9
13
  return matchingElms.length > 0;
@@ -0,0 +1,2 @@
1
+ // ignore results
2
+ return results.filter(checkResult => checkResult.data !== 'ignored');
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "id": "page-no-duplicate-banner",
3
3
  "evaluate": "page-no-duplicate.js",
4
+ "after": "page-no-duplicate-after.js",
4
5
  "options": {
5
6
  "selector": "header:not([role]), [role=banner]",
6
7
  "nativeScopeFilter": "article, aside, main, nav, section"
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "id": "page-no-duplicate-contentinfo",
3
3
  "evaluate": "page-no-duplicate.js",
4
+ "after": "page-no-duplicate-after.js",
4
5
  "options": {
5
6
  "selector": "footer:not([role]), [role=contentinfo]",
6
7
  "nativeScopeFilter": "article, aside, main, nav, section"
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "id": "page-no-duplicate-main",
3
3
  "evaluate": "page-no-duplicate.js",
4
+ "after": "page-no-duplicate-after.js",
4
5
  "options": {
5
6
  "selector": "main:not([role]), [role='main']"
6
7
  },
@@ -4,7 +4,19 @@ if (!options || !options.selector || typeof options.selector !== 'string') {
4
4
  );
5
5
  }
6
6
 
7
- let elms = axe.utils.querySelectorAll(virtualNode, options.selector);
7
+ // only look at the first node and it's related nodes
8
+ const key = 'page-no-duplicate;' + options.selector;
9
+ if (axe._cache.get(key)) {
10
+ this.data('ignored');
11
+ return;
12
+ }
13
+ axe._cache.set(key, true);
14
+
15
+ let elms = axe.utils.querySelectorAllFilter(
16
+ axe._tree[0],
17
+ options.selector,
18
+ elm => elm !== virtualNode && axe.commons.dom.isVisible(elm.actualNode)
19
+ );
8
20
 
9
21
  // Filter elements that, within certain contexts, don't map their role.
10
22
  // e.g. a <footer> inside a <main> is not a banner, but in the <body> context it is
@@ -19,4 +31,4 @@ if (typeof options.nativeScopeFilter === 'string') {
19
31
 
20
32
  this.relatedNodes(elms.map(elm => elm.actualNode));
21
33
 
22
- return elms.length <= 1;
34
+ return elms.length === 0;
@@ -1,64 +1,58 @@
1
- const { dom } = axe.commons;
2
- const getIsListItemRole = (role, tagName) => {
3
- return role === 'listitem' || (tagName === 'LI' && !role);
4
- };
1
+ const { dom, aria } = axe.commons;
5
2
 
6
- const getHasListItem = (hasListItem, tagName, isListItemRole) => {
7
- return hasListItem || (tagName === 'LI' && isListItemRole) || isListItemRole;
8
- };
3
+ let hasNonEmptyTextNode = false;
4
+ let atLeastOneListitem = false;
5
+ let isEmpty = true;
6
+ let badNodes = [];
7
+ let badRoleNodes = [];
8
+ let badRoles = [];
9
9
 
10
- let base = {
11
- badNodes: [],
12
- isEmpty: true,
13
- hasNonEmptyTextNode: false,
14
- hasListItem: false,
15
- liItemsWithRole: 0
16
- };
10
+ virtualNode.children.forEach(vNode => {
11
+ const { actualNode } = vNode;
17
12
 
18
- let out = virtualNode.children.reduce((out, { actualNode }) => {
19
- const tagName = actualNode.nodeName.toUpperCase();
13
+ if (actualNode.nodeType === 3 && actualNode.nodeValue.trim() !== '') {
14
+ hasNonEmptyTextNode = true;
15
+ return;
16
+ }
20
17
 
21
- if (actualNode.nodeType === 1 && dom.isVisible(actualNode, true, false)) {
22
- const role = (actualNode.getAttribute('role') || '').toLowerCase();
23
- const isListItemRole = getIsListItemRole(role, tagName);
18
+ if (actualNode.nodeType !== 1 || !dom.isVisible(actualNode, true, false)) {
19
+ return;
20
+ }
24
21
 
25
- out.hasListItem = getHasListItem(out.hasListItem, tagName, isListItemRole);
22
+ isEmpty = false;
23
+ const isLi = actualNode.nodeName.toUpperCase() === 'LI';
24
+ const role = aria.getRole(vNode);
25
+ const isListItemRole = role === 'listitem';
26
26
 
27
- if (isListItemRole) {
28
- out.isEmpty = false;
29
- }
30
- if (tagName === 'LI' && !isListItemRole) {
31
- out.liItemsWithRole++;
32
- }
33
- if (tagName !== 'LI' && !isListItemRole) {
34
- out.badNodes.push(actualNode);
35
- }
27
+ if (!isLi && !isListItemRole) {
28
+ badNodes.push(actualNode);
36
29
  }
37
- if (actualNode.nodeType === 3) {
38
- if (actualNode.nodeValue.trim() !== '') {
39
- out.hasNonEmptyTextNode = true;
30
+
31
+ if (isLi && !isListItemRole) {
32
+ badRoleNodes.push(actualNode);
33
+
34
+ if (!badRoles.includes(role)) {
35
+ badRoles.push(role);
40
36
  }
41
37
  }
42
38
 
43
- return out;
44
- }, base);
45
-
46
- const virtualNodeChildrenOfTypeLi = virtualNode.children.filter(
47
- ({ actualNode }) => {
48
- return actualNode.nodeName.toUpperCase() === 'LI';
39
+ if (isListItemRole) {
40
+ atLeastOneListitem = true;
49
41
  }
50
- );
42
+ });
51
43
 
52
- const allLiItemsHaveRole =
53
- out.liItemsWithRole > 0 &&
54
- virtualNodeChildrenOfTypeLi.length === out.liItemsWithRole;
44
+ if (hasNonEmptyTextNode || badNodes.length) {
45
+ this.relatedNodes(badNodes);
46
+ return true;
47
+ }
55
48
 
56
- if (out.badNodes.length) {
57
- this.relatedNodes(out.badNodes);
49
+ if (isEmpty || atLeastOneListitem) {
50
+ return false;
58
51
  }
59
52
 
60
- const isInvalidListItem = !(
61
- out.hasListItem ||
62
- (out.isEmpty && !allLiItemsHaveRole)
63
- );
64
- return isInvalidListItem || !!out.badNodes.length || out.hasNonEmptyTextNode;
53
+ this.relatedNodes(badRoleNodes);
54
+ this.data({
55
+ messageKey: 'roleNotValid',
56
+ roles: badRoles.join(', ')
57
+ });
58
+ return true;
@@ -5,7 +5,10 @@
5
5
  "impact": "serious",
6
6
  "messages": {
7
7
  "pass": "List element only has direct children that are allowed inside <li> elements",
8
- "fail": "List element has direct children that are not allowed inside <li> elements"
8
+ "fail": {
9
+ "default": "List element has direct children that are not allowed inside <li> elements",
10
+ "roleNotValid": "List element has direct children with a role that is not allowed: ${data.roles}"
11
+ }
9
12
  }
10
13
  }
11
14
  }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * if duration cannot be read, this means `preloadMedia` has failed
3
+ */
4
+ if (!node.duration) {
5
+ console.warn(`axe.utils.preloadMedia did not load metadata`);
6
+ return undefined;
7
+ }
8
+
9
+ /**
10
+ * Compute playable duration and verify if it within allowed duration
11
+ */
12
+ const { allowedDuration = 3 } = options;
13
+ const playableDuration = getPlayableDuration(node);
14
+ if (playableDuration <= allowedDuration && !node.hasAttribute('loop')) {
15
+ return true;
16
+ }
17
+
18
+ /**
19
+ * if media element does not provide controls mechanism
20
+ * -> fail
21
+ */
22
+ if (!node.hasAttribute('controls')) {
23
+ return false;
24
+ }
25
+
26
+ return true;
27
+
28
+ /**
29
+ * Compute playback duration
30
+ * @param {HTMLMediaElement} elm media element
31
+ */
32
+ function getPlayableDuration(elm) {
33
+ if (!elm.currentSrc) {
34
+ return 0;
35
+ }
36
+
37
+ const playbackRange = getPlaybackRange(elm.currentSrc);
38
+ if (!playbackRange) {
39
+ return Math.abs(elm.duration - (elm.currentTime || 0));
40
+ }
41
+
42
+ if (playbackRange.length === 1) {
43
+ return Math.abs(elm.duration - playbackRange[0]);
44
+ }
45
+
46
+ return Math.abs(playbackRange[1] - playbackRange[0]);
47
+ }
48
+
49
+ /**
50
+ * Get playback range from a media elements source, if specified
51
+ * See - https://developer.mozilla.org/de/docs/Web/HTML/Using_HTML5_audio_and_video#Specifying_playback_range
52
+ *
53
+ * Eg:
54
+ * src='....someMedia.mp3#t=8'
55
+ * -> should yeild [8]
56
+ * src='....someMedia.mp3#t=10,12'
57
+ * -> should yeild [10,12]
58
+ * @param {String} src media src
59
+ * @returns {Array|undefined}
60
+ */
61
+ function getPlaybackRange(src) {
62
+ const match = src.match(/#t=(.*)/);
63
+ if (!match) {
64
+ return;
65
+ }
66
+ const [, value] = match;
67
+ const ranges = value.split(',');
68
+
69
+ return ranges.map(range => {
70
+ // range is denoted in HH:MM:SS -> convert to seconds
71
+ if (/:/.test(range)) {
72
+ return convertHourMinSecToSeconds(range);
73
+ }
74
+ return parseFloat(range);
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Add HH, MM, SS to seconds
80
+ * @param {String} hhMmSs time expressed in HH:MM:SS
81
+ */
82
+ function convertHourMinSecToSeconds(hhMmSs) {
83
+ let parts = hhMmSs.split(':');
84
+ let secs = 0;
85
+ let mins = 1;
86
+
87
+ while (parts.length > 0) {
88
+ secs += mins * parseInt(parts.pop(), 10);
89
+ mins *= 60;
90
+ }
91
+
92
+ return parseFloat(secs);
93
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "id": "no-autoplay-audio",
3
+ "evaluate": "no-autoplay-audio.js",
4
+ "options": {
5
+ "allowedDuration": 3
6
+ },
7
+ "metadata": {
8
+ "impact": "moderate",
9
+ "messages": {
10
+ "pass": "<video> or <audio> does not output audio for more than allowed duration or has controls mechanism",
11
+ "fail": "<video> or <audio> outputs audio for more than allowed duration and does not have a controls mechanism",
12
+ "incomplete": "Check that the <video> or <audio> does not output audio for more than allowed duration or provides a controls mechanism"
13
+ }
14
+ }
15
+ }