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.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/app/assets/stylesheets/govuk_publishing_components/components/_breadcrumbs.scss +34 -0
- data/app/assets/stylesheets/govuk_publishing_components/components/_document-list.scss +2 -2
- data/app/views/govuk_publishing_components/components/_breadcrumbs.html.erb +0 -3
- data/app/views/govuk_publishing_components/components/_contextual_breadcrumbs.html.erb +0 -2
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/axe-core/CHANGELOG.md +70 -10
- data/node_modules/axe-core/axe.js +1295 -632
- data/node_modules/axe-core/axe.min.js +2 -2
- data/node_modules/axe-core/bower.json +1 -1
- data/node_modules/axe-core/doc/API.md +1 -0
- data/node_modules/axe-core/doc/aria-supported.md +0 -1
- data/node_modules/axe-core/doc/developer-guide.md +2 -2
- data/node_modules/axe-core/doc/rule-descriptions.md +91 -87
- data/node_modules/axe-core/lib/checks/aria/abstractrole.js +10 -1
- data/node_modules/axe-core/lib/checks/aria/abstractrole.json +4 -1
- data/node_modules/axe-core/lib/checks/aria/fallbackrole.js +1 -0
- data/node_modules/axe-core/lib/checks/aria/fallbackrole.json +11 -0
- data/node_modules/axe-core/lib/checks/aria/invalidrole.js +14 -3
- data/node_modules/axe-core/lib/checks/aria/invalidrole.json +4 -1
- data/node_modules/axe-core/lib/checks/aria/required-children.js +41 -30
- data/node_modules/axe-core/lib/checks/aria/valid-attr-value.js +24 -9
- data/node_modules/axe-core/lib/checks/aria/valid-attr-value.json +2 -2
- data/node_modules/axe-core/lib/checks/color/color-contrast.js +23 -7
- data/node_modules/axe-core/lib/checks/color/color-contrast.json +7 -1
- data/node_modules/axe-core/lib/checks/keyboard/focusable-disabled.js +5 -0
- data/node_modules/axe-core/lib/checks/keyboard/focusable-modal-open.js +14 -0
- data/node_modules/axe-core/lib/checks/keyboard/focusable-modal-open.json +11 -0
- data/node_modules/axe-core/lib/checks/keyboard/focusable-not-tabbable.js +5 -0
- data/node_modules/axe-core/lib/checks/keyboard/page-has-elm.js +5 -1
- data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-after.js +2 -0
- data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-banner.json +1 -0
- data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-contentinfo.json +1 -0
- data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate-main.json +1 -0
- data/node_modules/axe-core/lib/checks/keyboard/page-no-duplicate.js +14 -2
- data/node_modules/axe-core/lib/checks/lists/only-listitems.js +43 -49
- data/node_modules/axe-core/lib/checks/lists/only-listitems.json +4 -1
- data/node_modules/axe-core/lib/checks/media/no-autoplay-audio.js +93 -0
- data/node_modules/axe-core/lib/checks/media/no-autoplay-audio.json +15 -0
- data/node_modules/axe-core/lib/checks/navigation/identical-links-same-purpose-after.js +100 -0
- data/node_modules/axe-core/lib/checks/navigation/identical-links-same-purpose.js +31 -0
- data/node_modules/axe-core/lib/checks/navigation/identical-links-same-purpose.json +12 -0
- data/node_modules/axe-core/lib/checks/navigation/region.js +42 -8
- data/node_modules/axe-core/lib/checks/navigation/region.json +0 -1
- data/node_modules/axe-core/lib/checks/shared/svg-non-empty-title.js +4 -0
- data/node_modules/axe-core/lib/checks/shared/svg-non-empty-title.json +11 -0
- data/node_modules/axe-core/lib/commons/aria/index.js +12 -4
- data/node_modules/axe-core/lib/commons/color/get-background-color.js +5 -157
- data/node_modules/axe-core/lib/commons/dom/get-element-stack.js +272 -174
- data/node_modules/axe-core/lib/commons/dom/is-focusable.js +3 -1
- data/node_modules/axe-core/lib/commons/dom/is-hidden-with-css.js +2 -2
- data/node_modules/axe-core/lib/commons/dom/is-modal-open.js +98 -0
- data/node_modules/axe-core/lib/commons/dom/is-visible.js +57 -2
- data/node_modules/axe-core/lib/commons/dom/url-props-from-attribute.js +143 -0
- data/node_modules/axe-core/lib/commons/dom/visually-contains.js +62 -12
- data/node_modules/axe-core/lib/commons/matches/attributes.js +12 -8
- data/node_modules/axe-core/lib/commons/matches/from-definition.js +15 -10
- data/node_modules/axe-core/lib/commons/matches/index.js +11 -9
- data/node_modules/axe-core/lib/commons/matches/node-name.js +11 -21
- data/node_modules/axe-core/lib/commons/matches/properties.js +12 -9
- data/node_modules/axe-core/lib/commons/text/unicode.js +27 -24
- data/node_modules/axe-core/lib/core/base/virtual-node/virtual-node.js +11 -3
- data/node_modules/axe-core/lib/core/constants.js +1 -1
- data/node_modules/axe-core/lib/core/reporters/v1.js +6 -3
- data/node_modules/axe-core/lib/core/utils/contains.js +1 -1
- data/node_modules/axe-core/lib/core/utils/is-hidden.js +6 -6
- data/node_modules/axe-core/lib/core/utils/matches.js +263 -0
- data/node_modules/axe-core/lib/core/utils/preload-media.js +65 -0
- data/node_modules/axe-core/lib/core/utils/preload.js +33 -24
- data/node_modules/axe-core/lib/core/utils/qsa.js +7 -208
- data/node_modules/axe-core/lib/rules/aria-hidden-focus.json +5 -1
- data/node_modules/axe-core/lib/rules/aria-roles.json +1 -1
- data/node_modules/axe-core/lib/rules/button-name.json +1 -1
- data/node_modules/axe-core/lib/rules/color-contrast-matches.js +1 -1
- data/node_modules/axe-core/lib/rules/color-contrast.json +0 -3
- data/node_modules/axe-core/lib/rules/html-namespace-matches.js +1 -0
- data/node_modules/axe-core/lib/rules/identical-links-same-purpose-matches.js +13 -0
- data/node_modules/axe-core/lib/rules/identical-links-same-purpose.json +14 -0
- data/node_modules/axe-core/lib/rules/landmark-no-duplicate-banner.json +1 -1
- data/node_modules/axe-core/lib/rules/landmark-no-duplicate-contentinfo.json +1 -1
- data/node_modules/axe-core/lib/rules/landmark-no-duplicate-main.json +12 -0
- data/node_modules/axe-core/lib/rules/landmark-one-main.json +2 -2
- data/node_modules/axe-core/lib/rules/link-name.json +2 -4
- data/node_modules/axe-core/lib/rules/meta-viewport.json +1 -1
- data/node_modules/axe-core/lib/rules/no-autoplay-audio-matches.js +18 -0
- data/node_modules/axe-core/lib/rules/no-autoplay-audio.json +15 -0
- data/node_modules/axe-core/lib/rules/region.json +1 -2
- data/node_modules/axe-core/lib/rules/role-img-alt.json +2 -1
- data/node_modules/axe-core/lib/rules/svg-img-alt.json +24 -0
- data/node_modules/axe-core/lib/rules/svg-namespace-matches.js +1 -0
- data/node_modules/axe-core/locales/da.json +782 -0
- data/node_modules/axe-core/locales/fr.json +221 -42
- data/node_modules/axe-core/locales/ja.json +124 -24
- data/node_modules/axe-core/package.json +29 -26
- data/node_modules/axe-core/sri-history.json +26 -10
- metadata +27 -9
- data/node_modules/axe-core/doc/examples/jasmine/package-lock.json +0 -1489
- data/node_modules/axe-core/doc/examples/jest_react/package-lock.json +0 -7525
- data/node_modules/axe-core/doc/examples/mocha/package-lock.json +0 -1671
- data/node_modules/axe-core/doc/examples/phantomjs/package-lock.json +0 -862
- data/node_modules/axe-core/doc/examples/qunit/package-lock.json +0 -2951
- data/node_modules/axe-core/lib/checks/navigation/region-after.js +0 -1
- 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
|
-
"
|
15
|
-
"
|
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
|
-
|
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.
|
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;
|
@@ -4,7 +4,19 @@ if (!options || !options.selector || typeof options.selector !== 'string') {
|
|
4
4
|
);
|
5
5
|
}
|
6
6
|
|
7
|
-
|
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
|
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
|
-
|
7
|
-
|
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
|
-
|
11
|
-
|
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
|
-
|
19
|
-
|
13
|
+
if (actualNode.nodeType === 3 && actualNode.nodeValue.trim() !== '') {
|
14
|
+
hasNonEmptyTextNode = true;
|
15
|
+
return;
|
16
|
+
}
|
20
17
|
|
21
|
-
if (actualNode.nodeType
|
22
|
-
|
23
|
-
|
18
|
+
if (actualNode.nodeType !== 1 || !dom.isVisible(actualNode, true, false)) {
|
19
|
+
return;
|
20
|
+
}
|
24
21
|
|
25
|
-
|
22
|
+
isEmpty = false;
|
23
|
+
const isLi = actualNode.nodeName.toUpperCase() === 'LI';
|
24
|
+
const role = aria.getRole(vNode);
|
25
|
+
const isListItemRole = role === 'listitem';
|
26
26
|
|
27
|
-
|
28
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
44
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
44
|
+
if (hasNonEmptyTextNode || badNodes.length) {
|
45
|
+
this.relatedNodes(badNodes);
|
46
|
+
return true;
|
47
|
+
}
|
55
48
|
|
56
|
-
if (
|
57
|
-
|
49
|
+
if (isEmpty || atLeastOneListitem) {
|
50
|
+
return false;
|
58
51
|
}
|
59
52
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
)
|
64
|
-
|
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":
|
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
|
+
}
|