testaro 78.0.6 → 78.4.0
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.
- package/.gitattributes +94 -0
- package/.github/workflows/ci.yml +58 -0
- package/.github/workflows/publish.yml +49 -0
- package/.github/workflows/typescript.yml +35 -0
- package/AGENTS.md +2 -2
- package/CLAUDE.md +25 -19
- package/CONTAINERS.md +10 -6
- package/CONTRIBUTING.md +29 -3
- package/Dockerfile +1 -1
- package/README.md +75 -20
- package/UPGRADES.md +4 -0
- package/actSpecs-doc.md +13 -1
- package/actSpecs.js +29 -5
- package/call.js +6 -5
- package/docker-compose.yml +8 -2
- package/docs/checkpoint-scanning.md +199 -0
- package/docs/standard-result-outcome.md +165 -0
- package/env.example +44 -10
- package/eslint.config.mjs +101 -0
- package/netWatch.js +373 -222
- package/package.json +46 -31
- package/pour/README.md +47 -0
- package/pour/pour.min.js +8 -0
- package/procs/actDo.js +808 -0
- package/procs/catalog.d.ts +17 -0
- package/procs/catalog.js +313 -221
- package/procs/catalog.ts +398 -0
- package/procs/checkpoint.js +110 -0
- package/procs/config.d.ts +35 -0
- package/procs/config.js +63 -0
- package/procs/dateTime.js +2 -1
- package/procs/doActs.js +442 -849
- package/procs/doTestAct.js +27 -121
- package/procs/flow.js +221 -0
- package/procs/generateRuleRegistry.js +94 -0
- package/procs/getSource.d.ts +26 -0
- package/procs/getSource.js +1 -0
- package/procs/job.js +116 -1
- package/procs/launch.d.ts +40 -0
- package/procs/launch.js +239 -108
- package/procs/nu.d.ts +48 -0
- package/procs/nu.js +1 -0
- package/procs/scope.js +196 -0
- package/procs/shoot.d.ts +33 -0
- package/procs/shoot.js +3 -1
- package/procs/standard.d.ts +18 -0
- package/procs/standard.js +83 -0
- package/procs/standard.ts +126 -0
- package/procs/testAct.js +145 -0
- package/procs/testaro.d.ts +20 -0
- package/procs/testaro.js +237 -189
- package/procs/testaro.ts +321 -0
- package/procs/userPath.js +126 -0
- package/procs/xPath.d.ts +4 -0
- package/procs/xPath.js +82 -64
- package/procs/xPath.ts +106 -0
- package/procs/xPathScript.d.ts +6 -0
- package/procs/xPathScript.js +70 -0
- package/run.js +3 -2
- package/surea11y/README.md +44 -0
- package/surea11y/surea11y.browser.js +14 -0
- package/testaro/adbID.d.ts +3 -0
- package/testaro/adbID.js +39 -41
- package/testaro/adbID.ts +66 -0
- package/testaro/allCapStyle.d.ts +3 -0
- package/testaro/allCapStyle.js +32 -34
- package/testaro/allCapStyle.ts +55 -0
- package/testaro/allCaps.d.ts +16 -0
- package/testaro/allCaps.js +179 -151
- package/testaro/allCaps.ts +228 -0
- package/testaro/allHidden.d.ts +13 -0
- package/testaro/allHidden.js +30 -30
- package/testaro/allHidden.ts +50 -0
- package/testaro/allSlanted.d.ts +3 -0
- package/testaro/allSlanted.js +31 -33
- package/testaro/allSlanted.ts +54 -0
- package/testaro/altScheme.d.ts +3 -0
- package/testaro/altScheme.js +28 -30
- package/testaro/altScheme.ts +50 -0
- package/testaro/attVal.d.ts +3 -0
- package/testaro/attVal.js +21 -21
- package/testaro/attVal.ts +52 -0
- package/testaro/autocomplete.d.ts +3 -0
- package/testaro/autocomplete.js +60 -75
- package/testaro/autocomplete.ts +101 -0
- package/testaro/bulk.d.ts +13 -0
- package/testaro/bulk.js +33 -33
- package/testaro/bulk.ts +55 -0
- package/testaro/buttonMenu.d.ts +9 -0
- package/testaro/buttonMenu.js +318 -319
- package/testaro/buttonMenu.ts +391 -0
- package/testaro/captionLoc.d.ts +3 -0
- package/testaro/captionLoc.js +18 -20
- package/testaro/captionLoc.ts +40 -0
- package/testaro/datalistRef.d.ts +3 -0
- package/testaro/datalistRef.js +34 -36
- package/testaro/datalistRef.ts +55 -0
- package/testaro/distortion.d.ts +3 -0
- package/testaro/distortion.js +58 -26
- package/testaro/distortion.ts +81 -0
- package/testaro/docType.d.ts +15 -0
- package/testaro/docType.js +26 -25
- package/testaro/docType.ts +45 -0
- package/testaro/dupAtt.d.ts +16 -0
- package/testaro/dupAtt.js +114 -104
- package/testaro/dupAtt.ts +144 -0
- package/testaro/elements.d.ts +6 -0
- package/testaro/elements.js +153 -153
- package/testaro/elements.ts +215 -0
- package/testaro/embAc.d.ts +3 -0
- package/testaro/embAc.js +20 -20
- package/testaro/embAc.ts +40 -0
- package/testaro/focAll.d.ts +13 -0
- package/testaro/focAll.js +186 -191
- package/testaro/focAll.ts +217 -0
- package/testaro/focAndOp.d.ts +3 -0
- package/testaro/focAndOp.js +101 -104
- package/testaro/focAndOp.ts +128 -0
- package/testaro/focInd.d.ts +3 -0
- package/testaro/focInd.js +65 -66
- package/testaro/focInd.ts +96 -0
- package/testaro/focVis.d.ts +3 -0
- package/testaro/focVis.js +29 -30
- package/testaro/focVis.ts +52 -0
- package/testaro/headEl.d.ts +10 -0
- package/testaro/headEl.js +62 -62
- package/testaro/headEl.ts +82 -0
- package/testaro/headingAmb.d.ts +3 -0
- package/testaro/headingAmb.js +55 -63
- package/testaro/headingAmb.ts +72 -0
- package/testaro/hovInd.d.ts +12 -0
- package/testaro/hovInd.js +164 -130
- package/testaro/hovInd.ts +199 -0
- package/testaro/hover.d.ts +3 -0
- package/testaro/hover.js +156 -126
- package/testaro/hover.ts +154 -0
- package/testaro/hr.d.ts +3 -0
- package/testaro/hr.js +15 -17
- package/testaro/hr.ts +36 -0
- package/testaro/imageLink.d.ts +3 -0
- package/testaro/imageLink.js +18 -20
- package/testaro/imageLink.ts +42 -0
- package/testaro/labClash.d.ts +3 -0
- package/testaro/labClash.js +32 -33
- package/testaro/labClash.ts +54 -0
- package/testaro/legendLoc.d.ts +3 -0
- package/testaro/legendLoc.js +18 -20
- package/testaro/legendLoc.ts +42 -0
- package/testaro/lineHeight.d.ts +3 -0
- package/testaro/lineHeight.js +44 -48
- package/testaro/lineHeight.ts +70 -0
- package/testaro/linkAmb.d.ts +7 -0
- package/testaro/linkAmb.js +80 -80
- package/testaro/linkAmb.ts +105 -0
- package/testaro/linkExt.d.ts +3 -0
- package/testaro/linkExt.js +14 -16
- package/testaro/linkExt.ts +35 -0
- package/testaro/linkOldAtt.d.ts +3 -0
- package/testaro/linkOldAtt.js +26 -28
- package/testaro/linkOldAtt.ts +48 -0
- package/testaro/linkTo.d.ts +3 -0
- package/testaro/linkTo.js +23 -22
- package/testaro/linkTo.ts +43 -0
- package/testaro/linkUl.d.ts +3 -0
- package/testaro/linkUl.js +31 -34
- package/testaro/linkUl.ts +54 -0
- package/testaro/miniText.d.ts +3 -0
- package/testaro/miniText.js +42 -44
- package/testaro/miniText.ts +68 -0
- package/testaro/motion.d.ts +10 -0
- package/testaro/motion.js +93 -96
- package/testaro/motion.ts +125 -0
- package/testaro/nonTable.d.ts +3 -0
- package/testaro/nonTable.js +40 -45
- package/testaro/nonTable.ts +66 -0
- package/testaro/optRoleSel.d.ts +3 -0
- package/testaro/optRoleSel.js +17 -19
- package/testaro/optRoleSel.ts +41 -0
- package/testaro/phOnly.d.ts +3 -0
- package/testaro/phOnly.js +19 -21
- package/testaro/phOnly.ts +43 -0
- package/testaro/pseudoP.d.ts +3 -0
- package/testaro/pseudoP.js +37 -38
- package/testaro/pseudoP.ts +59 -0
- package/testaro/radioSet.d.ts +3 -0
- package/testaro/radioSet.js +58 -59
- package/testaro/radioSet.ts +79 -0
- package/testaro/registry.d.ts +63 -0
- package/testaro/registry.js +67 -0
- package/testaro/registry.ts +141 -0
- package/testaro/role.d.ts +3 -0
- package/testaro/role.js +28 -29
- package/testaro/role.ts +53 -0
- package/testaro/secHeading.d.ts +3 -0
- package/testaro/secHeading.js +31 -33
- package/testaro/secHeading.ts +53 -0
- package/testaro/styleDiff.d.ts +25 -0
- package/testaro/styleDiff.js +249 -252
- package/testaro/styleDiff.ts +303 -0
- package/testaro/tabNav.d.ts +33 -0
- package/testaro/tabNav.js +273 -342
- package/testaro/tabNav.ts +454 -0
- package/testaro/targetsNear.d.ts +9 -0
- package/testaro/targetsNear.js +131 -132
- package/testaro/targetsNear.ts +160 -0
- package/testaro/textNodes.d.ts +6 -0
- package/testaro/textNodes.js +139 -135
- package/testaro/textNodes.ts +185 -0
- package/testaro/textSem.d.ts +3 -0
- package/testaro/textSem.js +26 -28
- package/testaro/textSem.ts +47 -0
- package/testaro/title.d.ts +9 -0
- package/testaro/title.js +16 -13
- package/testaro/title.ts +31 -0
- package/testaro/titledEl.d.ts +3 -0
- package/testaro/titledEl.js +16 -18
- package/testaro/titledEl.ts +38 -0
- package/testaro/zIndex.d.ts +3 -0
- package/testaro/zIndex.js +20 -22
- package/testaro/zIndex.ts +42 -0
- package/tests/alfa.d.ts +45 -0
- package/tests/alfa.js +139 -141
- package/tests/alfa.ts +214 -0
- package/tests/aslint.d.ts +33 -0
- package/tests/aslint.js +273 -249
- package/tests/aslint.ts +301 -0
- package/tests/axe.d.ts +27 -0
- package/tests/axe.js +200 -200
- package/tests/axe.ts +277 -0
- package/tests/ed11y.d.ts +28 -0
- package/tests/ed11y.js +142 -99
- package/tests/ed11y.ts +178 -0
- package/tests/htmlcs.d.ts +21 -0
- package/tests/htmlcs.js +175 -140
- package/tests/htmlcs.ts +181 -0
- package/tests/ibm.d.ts +52 -0
- package/tests/ibm.js +165 -166
- package/tests/ibm.ts +251 -0
- package/tests/nuVal.d.ts +13 -0
- package/tests/nuVal.js +107 -112
- package/tests/nuVal.ts +145 -0
- package/tests/nuVnu.d.ts +15 -0
- package/tests/nuVnu.js +142 -111
- package/tests/nuVnu.ts +144 -0
- package/tests/pour.d.ts +31 -0
- package/tests/pour.js +242 -0
- package/tests/pour.ts +273 -0
- package/tests/qualWeb.d.ts +39 -0
- package/tests/qualWeb.js +303 -272
- package/tests/qualWeb.ts +415 -0
- package/tests/surea11y.d.ts +33 -0
- package/tests/surea11y.js +288 -0
- package/tests/surea11y.ts +334 -0
- package/tests/testaro.d.ts +25 -0
- package/tests/testaro.js +746 -652
- package/tests/testaro.ts +862 -0
- package/tests/wave.d.ts +46 -0
- package/tests/wave.js +167 -177
- package/tests/wave.ts +252 -0
- package/tsconfig.json +17 -0
- package/types.d.ts +243 -0
- package/types.js +10 -0
- package/types.ts +376 -0
- package/validation/act/README.md +46 -0
- package/validation/act/capture.js +424 -0
- package/validation/act/chromium-issue-draft.md +66 -0
- package/validation/act/fp-triage-2026-08-22.md +86 -0
- package/validation/act/isolation-notes.md +159 -0
- package/validation/act/playwright-issue-draft.md +73 -0
- package/validation/act/propose-mappings.js +0 -0
- package/validation/act/repro-cdp-raw.js +105 -0
- package/validation/act/repro-metarefresh.js +65 -0
- package/validation/act/score.js +184 -0
- package/validation/act/stage3a-stress-report.md +69 -0
- package/validation/act/stage3b-mapping-proposals.md +94 -0
- package/validation/act/stage3b-triage-draft.md +138 -0
- package/validation/act/surea11y-track-a-2026-09-01.md +50 -0
- package/validation/executors/netWatch.js +180 -90
- package/validation/executors/test.js +17 -2
- package/validation/executors/tests.js +118 -10
- package/validation/jobs/reports/raw/260901T1000-surea11y-validation.json +964 -0
- package/validation/jobs/todo/240101T1200-simple-example.json +14 -6
- package/validation/jobs/todo/240101T1300-shoot-example.json +2 -1
- package/validation/jobs/todo/260821T1900-pour-validation.json +45 -0
- package/validation/jobs/todo/260901T1000-surea11y-validation.json +45 -0
- package/validation/knownFailures.json +1 -0
- package/validation/tests/jobProperties/adbID.json +27 -2
- package/validation/tests/jobProperties/{focOp.json → allCapStyle.json} +56 -53
- package/validation/tests/jobProperties/allCaps.json +25 -0
- package/validation/tests/jobProperties/allHidden.json +133 -13
- package/validation/tests/jobProperties/allSlanted.json +3 -3
- package/validation/tests/jobProperties/altScheme.json +23 -8
- package/validation/tests/jobProperties/attVal.json +57 -57
- package/validation/tests/jobProperties/autocomplete.json +11 -1
- package/validation/tests/jobProperties/bulk.json +6 -1
- package/validation/tests/jobProperties/buttonMenu.json +63 -42
- package/validation/tests/jobProperties/captionLoc.json +1 -6
- package/validation/tests/jobProperties/checkpoint-browser.json +401 -0
- package/validation/tests/jobProperties/checkpoint-page.json +397 -0
- package/validation/tests/jobProperties/checkpoint.json +395 -0
- package/validation/tests/jobProperties/datalistRef.json +20 -5
- package/validation/tests/jobProperties/distortion.json +28 -3
- package/validation/tests/jobProperties/docType.json +2 -2
- package/validation/tests/jobProperties/dupAtt.json +48 -33
- package/validation/tests/jobProperties/elements.json +28 -28
- package/validation/tests/jobProperties/embAc.json +36 -31
- package/validation/tests/jobProperties/focAndOp.json +284 -0
- package/validation/tests/jobProperties/focInd.json +34 -34
- package/validation/tests/jobProperties/focVis.json +1 -1
- package/validation/tests/jobProperties/hover.json +39 -37
- package/validation/tests/jobProperties/hr.json +13 -3
- package/validation/tests/jobProperties/imageLink.json +0 -5
- package/validation/tests/jobProperties/labClash.json +68 -33
- package/validation/tests/jobProperties/legendLoc.json +1 -6
- package/validation/tests/jobProperties/lineHeight.json +2 -2
- package/validation/tests/jobProperties/linkAmb.json +13 -18
- package/validation/tests/jobProperties/linkExt.json +1 -1
- package/validation/tests/jobProperties/linkOldAtt.json +12 -2
- package/validation/tests/jobProperties/linkTo.json +1 -1
- package/validation/tests/jobProperties/linkUl.json +65 -65
- package/validation/tests/jobProperties/miniText.json +7 -2
- package/validation/tests/jobProperties/motion.json +4 -50
- package/validation/tests/jobProperties/nonTable.json +48 -3
- package/validation/tests/jobProperties/optRoleSel.json +12 -2
- package/validation/tests/jobProperties/phOnly.json +6 -16
- package/validation/tests/jobProperties/pseudoP.json +17 -2
- package/validation/tests/jobProperties/radioSet.json +30 -30
- package/validation/tests/jobProperties/role.json +25 -5
- package/validation/tests/jobProperties/secHeading.json +26 -21
- package/validation/tests/jobProperties/styleDiff.json +35 -5
- package/validation/tests/jobProperties/tabNav.json +3 -1
- package/validation/tests/jobProperties/targetsNear.json +237 -0
- package/validation/tests/jobProperties/textNodes.json +29 -29
- package/validation/tests/jobProperties/textSem.json +26 -1
- package/validation/tests/jobProperties/title.json +12 -2
- package/validation/tests/jobProperties/titledEl.json +39 -9
- package/validation/tests/jobProperties/userPath.json +317 -0
- package/validation/tests/jobProperties/zIndex.json +40 -35
- package/validation/tests/targets/allCapStyle/index.html +26 -0
- package/validation/tests/targets/checkpoint/index.html +38 -0
- package/validation/tests/targets/datalistRef/index.html +1 -1
- package/validation/tests/targets/focAndOp/bad.html +29 -0
- package/validation/tests/targets/{focOp → focAndOp}/good.html +3 -1
- package/validation/tests/targets/focInd/bad.html +2 -1
- package/validation/tests/targets/headEl/index.html +10 -1
- package/validation/tests/targets/{targetSmall → targetsNear}/index.html +15 -1
- package/validation/tests/targets/userPath/index.html +42 -0
- package/validation/validateTest.js +62 -10
- package/.claude/settings.local.json +0 -11
- package/.eslintrc.json +0 -41
- package/htmlcs/.eslintrc.json +0 -67
- package/memory/MEMORY.md +0 -3
- package/memory/project_validation_pause.md +0 -10
- package/validation/tests/jobProperties/linkTitle.json +0 -127
- package/validation/tests/jobProperties/opFoc.json +0 -164
- package/validation/tests/jobProperties/targetSmall.json +0 -152
- package/validation/tests/jobProperties/targetTiny.json +0 -142
- package/validation/tests/targets/focOp/bad.html +0 -25
- package/validation/tests/targets/linkTitle/index.html +0 -24
- package/validation/tests/targets/opFoc/bad.html +0 -26
- package/validation/tests/targets/opFoc/good.html +0 -23
package/testaro/targetsNear.js
CHANGED
|
@@ -1,151 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/*
|
|
2
3
|
© 2023–2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
4
|
+
© 2026 Jeff Witt.
|
|
3
5
|
© 2025–2026 Jonathan Robert Pool.
|
|
4
6
|
|
|
5
7
|
Licensed under the MIT License. See LICENSE file at the project root or https://opensource.org/license/mit/ for details.
|
|
6
8
|
|
|
7
9
|
SPDX-License-Identifier: MIT
|
|
8
10
|
*/
|
|
9
|
-
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.reporter = void 0;
|
|
13
|
+
const xPath_1 = require("../procs/xPath");
|
|
10
14
|
/*
|
|
11
15
|
targetsNear
|
|
12
16
|
Related to Tenon rule 152.
|
|
13
17
|
This test reports visible pointer targets, i.e. labels, buttons, inputs, and links, that are near enough to other targets to make pointer interaction difficult. This test relates to WCAG 2.2 Success Criteria 2.5.5 and 2.5.8, but does not attempt to implement either of them precisely. For example, the test reports a small pointer target that is far from all other targets, although it conforms to the Success Criteria.
|
|
18
|
+
Compiled to targetsNear.js by tsc (issue #73); edit this file, not the emitted one.
|
|
14
19
|
*/
|
|
15
|
-
|
|
16
|
-
// IMPORTS
|
|
17
|
-
|
|
18
|
-
const {getXPathCatalogIndex} = require('../procs/xPath');
|
|
19
|
-
|
|
20
20
|
// FUNCTIONS
|
|
21
|
-
|
|
22
21
|
// Runs the test and returns the result.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}));
|
|
34
|
-
// For each visible one:
|
|
35
|
-
const visibleBlockTargets = visibleTargets.filter(target => {
|
|
36
|
-
const style = window.getComputedStyle(target);
|
|
37
|
-
// If it is block-displayed:
|
|
38
|
-
if (['block', 'list-item', 'table-cell'].includes(style.display)) {
|
|
39
|
-
// Include it.
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
const parent = target.parentElement;
|
|
43
|
-
// Otherwise, if it has a parent:
|
|
44
|
-
if (parent) {
|
|
45
|
-
// If the parent has no additional text:
|
|
46
|
-
if (parent.innerText === target.innerText) {
|
|
47
|
-
const parentStyle = window.getComputedStyle(parent);
|
|
48
|
-
// If the parent is block-displayed:
|
|
49
|
-
if (['block', 'list-item', 'table-cell'].includes(parentStyle.display)) {
|
|
50
|
-
// Include the target.
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
// Otherwise, exclude it.
|
|
56
|
-
return false;
|
|
57
|
-
});
|
|
58
|
-
// Initialize the data.
|
|
59
|
-
const ptsData = [];
|
|
60
|
-
// For each visible block-displayed pointer target:
|
|
61
|
-
visibleBlockTargets.forEach(element => {
|
|
62
|
-
// Get the coordinates of its centerpoint.
|
|
63
|
-
const rect = element.getBoundingClientRect();
|
|
64
|
-
const centerX = rect.left + rect.width / 2;
|
|
65
|
-
const centerY = rect.top + rect.height / 2;
|
|
66
|
-
// Add them to the data.
|
|
67
|
-
ptsData.push([centerX, centerY]);
|
|
68
|
-
});
|
|
69
|
-
// Initialize the counts of minor and major violations.
|
|
70
|
-
let violationCounts = [0, 0];
|
|
71
|
-
const protoInstances = [];
|
|
72
|
-
// For each visible block-displayed pointer target:
|
|
73
|
-
visibleBlockTargets.forEach((element, index) => {
|
|
74
|
-
const [centerX, centerY] = ptsData[index];
|
|
75
|
-
const otherPTsData = ptsData.toSpliced(index, 1);
|
|
76
|
-
// Get the minimum of the vertical distances of its centerpoint from those of the others.
|
|
77
|
-
const minYDiff = Math.min(...otherPTsData.map(ptData => Math.abs(centerY - ptData[1])));
|
|
78
|
-
// If it is close enough to make a violation possible:
|
|
79
|
-
if (minYDiff < 44) {
|
|
80
|
-
// Get the centerpoint coordinates of those within that vertical distance.
|
|
81
|
-
const yNearPTsData = otherPTsData.filter(
|
|
82
|
-
ptData => Math.abs(ptData[1] - centerY) < 44
|
|
83
|
-
);
|
|
84
|
-
// Get the minimum of their planar distances.
|
|
85
|
-
const minPlanarDistance = Math.min(...yNearPTsData.map(ptData => {
|
|
86
|
-
const planarDistance = Math.sqrt(
|
|
87
|
-
Math.pow(centerX - ptData[0], 2) + Math.pow(centerY - ptData[1], 2)
|
|
88
|
-
);
|
|
89
|
-
return planarDistance;
|
|
22
|
+
const reporter = async (page, report, _, withItems) => {
|
|
23
|
+
// Return totals and standard instances for the rule.
|
|
24
|
+
const protoResult = await page.evaluate(withItems => {
|
|
25
|
+
// Get all pointer targets.
|
|
26
|
+
const allTargets = Array.from(document.body.querySelectorAll('label, button, input, a'));
|
|
27
|
+
// Get the visible ones.
|
|
28
|
+
const visibleTargets = allTargets.filter(target => target.checkVisibility({
|
|
29
|
+
contentVisibilityAuto: true,
|
|
30
|
+
opacityProperty: true,
|
|
31
|
+
visibilityProperty: true
|
|
90
32
|
}));
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
33
|
+
// For each visible one:
|
|
34
|
+
const visibleBlockTargets = visibleTargets.filter(target => {
|
|
35
|
+
const style = window.getComputedStyle(target);
|
|
36
|
+
// If it is block-displayed:
|
|
37
|
+
if (['block', 'list-item', 'table-cell'].includes(style.display)) {
|
|
38
|
+
// Include it.
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
const parent = target.parentElement;
|
|
42
|
+
// Otherwise, if it has a parent:
|
|
43
|
+
if (parent) {
|
|
44
|
+
// If the parent has no additional text:
|
|
45
|
+
if (parent.innerText === target.innerText) {
|
|
46
|
+
const parentStyle = window.getComputedStyle(parent);
|
|
47
|
+
// If the parent is block-displayed:
|
|
48
|
+
if (['block', 'list-item', 'table-cell'].includes(parentStyle.display)) {
|
|
49
|
+
// Include the target.
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Otherwise, exclude it.
|
|
55
|
+
return false;
|
|
56
|
+
});
|
|
57
|
+
// Initialize the data.
|
|
58
|
+
const ptsData = [];
|
|
59
|
+
// For each visible block-displayed pointer target:
|
|
60
|
+
visibleBlockTargets.forEach(element => {
|
|
61
|
+
// Get the coordinates of its centerpoint.
|
|
62
|
+
const rect = element.getBoundingClientRect();
|
|
63
|
+
const centerX = rect.left + rect.width / 2;
|
|
64
|
+
const centerY = rect.top + rect.height / 2;
|
|
65
|
+
// Add them to the data.
|
|
66
|
+
ptsData.push([centerX, centerY]);
|
|
124
67
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
68
|
+
// Initialize the counts of minor and major violations.
|
|
69
|
+
let violationCounts = [0, 0];
|
|
70
|
+
const protoInstances = [];
|
|
71
|
+
// For each visible block-displayed pointer target:
|
|
72
|
+
visibleBlockTargets.forEach((element, index) => {
|
|
73
|
+
const [centerX, centerY] = ptsData[index];
|
|
74
|
+
const otherPTsData = ptsData.toSpliced(index, 1);
|
|
75
|
+
// Get the minimum of the vertical distances of its centerpoint from those of the others.
|
|
76
|
+
const minYDiff = Math.min(...otherPTsData.map(ptData => Math.abs(centerY - ptData[1])));
|
|
77
|
+
// If it is close enough to make a violation possible:
|
|
78
|
+
if (minYDiff < 44) {
|
|
79
|
+
// Get the centerpoint coordinates of those within that vertical distance.
|
|
80
|
+
const yNearPTsData = otherPTsData.filter(ptData => Math.abs(ptData[1] - centerY) < 44);
|
|
81
|
+
// Get the minimum of their planar distances.
|
|
82
|
+
const minPlanarDistance = Math.min(...yNearPTsData.map(ptData => {
|
|
83
|
+
const planarDistance = Math.sqrt(Math.pow(centerX - ptData[0], 2) + Math.pow(centerY - ptData[1], 2));
|
|
84
|
+
return planarDistance;
|
|
85
|
+
}));
|
|
86
|
+
// If the minimum planar distance is less than 44px:
|
|
87
|
+
if (minPlanarDistance < 44) {
|
|
88
|
+
// Get whether it is less than 24px.
|
|
89
|
+
const isVeryNear = minPlanarDistance < 24;
|
|
90
|
+
// Get the ordinal severity of the violation, matching the totals and the summary instances (formerly 3 or 2, which contradicted them).
|
|
91
|
+
const ordinalSeverity = isVeryNear ? 1 : 0;
|
|
92
|
+
// Increment the applicable violation count.
|
|
93
|
+
violationCounts[isVeryNear ? 1 : 0]++;
|
|
94
|
+
// If itemization is required:
|
|
95
|
+
if (withItems) {
|
|
96
|
+
const what = `Pointer-target centerpoint is only ${Math.round(minPlanarDistance)}px from another one`;
|
|
97
|
+
// Add a proto-instance to the proto-instances.
|
|
98
|
+
protoInstances.push({
|
|
99
|
+
ruleID: 'targetsNear',
|
|
100
|
+
what,
|
|
101
|
+
ordinalSeverity,
|
|
102
|
+
count: 1,
|
|
103
|
+
xPath: window.getXPath(element) ?? '/html'
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
134
108
|
});
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
109
|
+
// If itemization is not required:
|
|
110
|
+
if (!withItems) {
|
|
111
|
+
// If there were any major violations:
|
|
112
|
+
if (violationCounts[1]) {
|
|
113
|
+
// Add a summary instance to the proto-instances.
|
|
114
|
+
protoInstances.push({
|
|
115
|
+
ruleID: 'targetsNear',
|
|
116
|
+
what: 'Pointer-target centerpoints are less than 24px from others',
|
|
117
|
+
ordinalSeverity: 1,
|
|
118
|
+
count: violationCounts[1]
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
// If there were any minor violations:
|
|
122
|
+
if (violationCounts[0]) {
|
|
123
|
+
// Add a summary instance to the proto-instances.
|
|
124
|
+
protoInstances.push({
|
|
125
|
+
ruleID: 'targetsNear',
|
|
126
|
+
what: 'Pointer-target centerpoints are less than 44px from others',
|
|
127
|
+
ordinalSeverity: 0,
|
|
128
|
+
count: violationCounts[0]
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
data: {},
|
|
134
|
+
totals: [...violationCounts, 0, 0],
|
|
135
|
+
standardInstances: protoInstances
|
|
136
|
+
};
|
|
137
|
+
}, withItems);
|
|
138
|
+
// Convert the XPaths of the proto-instances to catalog indexes.
|
|
139
|
+
protoResult.standardInstances = protoResult.standardInstances.map(instance => {
|
|
140
|
+
// Summary instances (withItems false) carry no xPath and get no catalog index; formerly they were passed to getXPathCatalogIndex, which threw and marked the rule prevented.
|
|
141
|
+
if (instance.xPath !== undefined) {
|
|
142
|
+
instance.catalogIndex = (0, xPath_1.getXPathCatalogIndex)(report, instance.xPath);
|
|
143
|
+
delete instance.xPath;
|
|
144
|
+
}
|
|
145
|
+
return instance;
|
|
146
|
+
});
|
|
147
|
+
// Return the result.
|
|
148
|
+
return protoResult;
|
|
151
149
|
};
|
|
150
|
+
exports.reporter = reporter;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2023–2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2026 Jeff Witt.
|
|
4
|
+
© 2025–2026 Jonathan Robert Pool.
|
|
5
|
+
|
|
6
|
+
Licensed under the MIT License. See LICENSE file at the project root or https://opensource.org/license/mit/ for details.
|
|
7
|
+
|
|
8
|
+
SPDX-License-Identifier: MIT
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// IMPORTS
|
|
12
|
+
|
|
13
|
+
import type {Page} from 'playwright';
|
|
14
|
+
import {getXPathCatalogIndex} from '../procs/xPath';
|
|
15
|
+
import type {Report, StandardInstance} from '../types';
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
targetsNear
|
|
19
|
+
Related to Tenon rule 152.
|
|
20
|
+
This test reports visible pointer targets, i.e. labels, buttons, inputs, and links, that are near enough to other targets to make pointer interaction difficult. This test relates to WCAG 2.2 Success Criteria 2.5.5 and 2.5.8, but does not attempt to implement either of them precisely. For example, the test reports a small pointer target that is far from all other targets, although it conforms to the Success Criteria.
|
|
21
|
+
Compiled to targetsNear.js by tsc (issue #73); edit this file, not the emitted one.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// FUNCTIONS
|
|
25
|
+
|
|
26
|
+
// Runs the test and returns the result.
|
|
27
|
+
export const reporter = async (page: Page, report: Report, _: unknown, withItems: boolean) => {
|
|
28
|
+
// Return totals and standard instances for the rule.
|
|
29
|
+
const protoResult = await page.evaluate(withItems => {
|
|
30
|
+
// Get all pointer targets.
|
|
31
|
+
const allTargets = Array.from(
|
|
32
|
+
document.body.querySelectorAll<HTMLElement>('label, button, input, a')
|
|
33
|
+
);
|
|
34
|
+
// Get the visible ones.
|
|
35
|
+
const visibleTargets = allTargets.filter(target => target.checkVisibility({
|
|
36
|
+
contentVisibilityAuto: true,
|
|
37
|
+
opacityProperty: true,
|
|
38
|
+
visibilityProperty: true
|
|
39
|
+
}));
|
|
40
|
+
// For each visible one:
|
|
41
|
+
const visibleBlockTargets = visibleTargets.filter(target => {
|
|
42
|
+
const style = window.getComputedStyle(target);
|
|
43
|
+
// If it is block-displayed:
|
|
44
|
+
if (['block', 'list-item', 'table-cell'].includes(style.display)) {
|
|
45
|
+
// Include it.
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const parent = target.parentElement;
|
|
49
|
+
// Otherwise, if it has a parent:
|
|
50
|
+
if (parent) {
|
|
51
|
+
// If the parent has no additional text:
|
|
52
|
+
if (parent.innerText === target.innerText) {
|
|
53
|
+
const parentStyle = window.getComputedStyle(parent);
|
|
54
|
+
// If the parent is block-displayed:
|
|
55
|
+
if (['block', 'list-item', 'table-cell'].includes(parentStyle.display)) {
|
|
56
|
+
// Include the target.
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Otherwise, exclude it.
|
|
62
|
+
return false;
|
|
63
|
+
});
|
|
64
|
+
// Initialize the data.
|
|
65
|
+
const ptsData: [number, number][] = [];
|
|
66
|
+
// For each visible block-displayed pointer target:
|
|
67
|
+
visibleBlockTargets.forEach(element => {
|
|
68
|
+
// Get the coordinates of its centerpoint.
|
|
69
|
+
const rect = element.getBoundingClientRect();
|
|
70
|
+
const centerX = rect.left + rect.width / 2;
|
|
71
|
+
const centerY = rect.top + rect.height / 2;
|
|
72
|
+
// Add them to the data.
|
|
73
|
+
ptsData.push([centerX, centerY]);
|
|
74
|
+
});
|
|
75
|
+
// Initialize the counts of minor and major violations.
|
|
76
|
+
let violationCounts = [0, 0];
|
|
77
|
+
const protoInstances: (StandardInstance & {xPath?: string})[] = [];
|
|
78
|
+
// For each visible block-displayed pointer target:
|
|
79
|
+
visibleBlockTargets.forEach((element, index) => {
|
|
80
|
+
const [centerX, centerY] = ptsData[index];
|
|
81
|
+
const otherPTsData = ptsData.toSpliced(index, 1);
|
|
82
|
+
// Get the minimum of the vertical distances of its centerpoint from those of the others.
|
|
83
|
+
const minYDiff = Math.min(...otherPTsData.map(ptData => Math.abs(centerY - ptData[1])));
|
|
84
|
+
// If it is close enough to make a violation possible:
|
|
85
|
+
if (minYDiff < 44) {
|
|
86
|
+
// Get the centerpoint coordinates of those within that vertical distance.
|
|
87
|
+
const yNearPTsData = otherPTsData.filter(
|
|
88
|
+
ptData => Math.abs(ptData[1] - centerY) < 44
|
|
89
|
+
);
|
|
90
|
+
// Get the minimum of their planar distances.
|
|
91
|
+
const minPlanarDistance = Math.min(...yNearPTsData.map(ptData => {
|
|
92
|
+
const planarDistance = Math.sqrt(
|
|
93
|
+
Math.pow(centerX - ptData[0], 2) + Math.pow(centerY - ptData[1], 2)
|
|
94
|
+
);
|
|
95
|
+
return planarDistance;
|
|
96
|
+
}));
|
|
97
|
+
// If the minimum planar distance is less than 44px:
|
|
98
|
+
if (minPlanarDistance < 44) {
|
|
99
|
+
// Get whether it is less than 24px.
|
|
100
|
+
const isVeryNear = minPlanarDistance < 24;
|
|
101
|
+
// Get the ordinal severity of the violation, matching the totals and the summary instances (formerly 3 or 2, which contradicted them).
|
|
102
|
+
const ordinalSeverity = isVeryNear ? 1 : 0;
|
|
103
|
+
// Increment the applicable violation count.
|
|
104
|
+
violationCounts[isVeryNear ? 1 : 0]++;
|
|
105
|
+
// If itemization is required:
|
|
106
|
+
if (withItems) {
|
|
107
|
+
const what = `Pointer-target centerpoint is only ${Math.round(minPlanarDistance)}px from another one`;
|
|
108
|
+
// Add a proto-instance to the proto-instances.
|
|
109
|
+
protoInstances.push({
|
|
110
|
+
ruleID: 'targetsNear',
|
|
111
|
+
what,
|
|
112
|
+
ordinalSeverity,
|
|
113
|
+
count: 1,
|
|
114
|
+
xPath: window.getXPath(element) ?? '/html'
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// If itemization is not required:
|
|
121
|
+
if (! withItems) {
|
|
122
|
+
// If there were any major violations:
|
|
123
|
+
if (violationCounts[1]) {
|
|
124
|
+
// Add a summary instance to the proto-instances.
|
|
125
|
+
protoInstances.push({
|
|
126
|
+
ruleID: 'targetsNear',
|
|
127
|
+
what: 'Pointer-target centerpoints are less than 24px from others',
|
|
128
|
+
ordinalSeverity: 1,
|
|
129
|
+
count: violationCounts[1]
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// If there were any minor violations:
|
|
133
|
+
if (violationCounts[0]) {
|
|
134
|
+
// Add a summary instance to the proto-instances.
|
|
135
|
+
protoInstances.push({
|
|
136
|
+
ruleID: 'targetsNear',
|
|
137
|
+
what: 'Pointer-target centerpoints are less than 44px from others',
|
|
138
|
+
ordinalSeverity: 0,
|
|
139
|
+
count: violationCounts[0]
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
data: {},
|
|
145
|
+
totals: [...violationCounts, 0, 0],
|
|
146
|
+
standardInstances: protoInstances
|
|
147
|
+
};
|
|
148
|
+
}, withItems);
|
|
149
|
+
// Convert the XPaths of the proto-instances to catalog indexes.
|
|
150
|
+
protoResult.standardInstances = protoResult.standardInstances.map(instance => {
|
|
151
|
+
// Summary instances (withItems false) carry no xPath and get no catalog index; formerly they were passed to getXPathCatalogIndex, which threw and marked the rule prevented.
|
|
152
|
+
if (instance.xPath !== undefined) {
|
|
153
|
+
instance.catalogIndex = getXPathCatalogIndex(report, instance.xPath);
|
|
154
|
+
delete instance.xPath;
|
|
155
|
+
}
|
|
156
|
+
return instance;
|
|
157
|
+
});
|
|
158
|
+
// Return the result.
|
|
159
|
+
return protoResult;
|
|
160
|
+
};
|