@setsquare/review-sdk 0.0.1 → 0.1.1
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/NOTICE +3 -2
- package/README.md +287 -3
- package/dist/autoscan.d.ts +23 -1
- package/dist/autoscan.d.ts.map +1 -1
- package/dist/autoscan.js +220 -15
- package/dist/autoscan.js.map +1 -1
- package/dist/brand.d.ts +51 -2
- package/dist/brand.d.ts.map +1 -1
- package/dist/brand.js +560 -5
- package/dist/brand.js.map +1 -1
- package/dist/checks/context-change.d.ts +96 -0
- package/dist/checks/context-change.d.ts.map +1 -0
- package/dist/checks/context-change.js +655 -0
- package/dist/checks/context-change.js.map +1 -0
- package/dist/checks/focus.d.ts +154 -0
- package/dist/checks/focus.d.ts.map +1 -0
- package/dist/checks/focus.js +1123 -0
- package/dist/checks/focus.js.map +1 -0
- package/dist/checks/heuristics.d.ts +117 -0
- package/dist/checks/heuristics.d.ts.map +1 -0
- package/dist/checks/heuristics.js +961 -0
- package/dist/checks/heuristics.js.map +1 -0
- package/dist/checks/keyboard.d.ts +106 -0
- package/dist/checks/keyboard.d.ts.map +1 -0
- package/dist/checks/keyboard.js +452 -0
- package/dist/checks/keyboard.js.map +1 -0
- package/dist/checks/reflow.d.ts +75 -0
- package/dist/checks/reflow.d.ts.map +1 -0
- package/dist/checks/reflow.js +285 -0
- package/dist/checks/reflow.js.map +1 -0
- package/dist/checks/rule-pack.d.ts +1 -1
- package/dist/checks/rule-pack.d.ts.map +1 -1
- package/dist/checks/rule-pack.js +350 -45
- package/dist/checks/rule-pack.js.map +1 -1
- package/dist/checks/shortcuts.d.ts +71 -0
- package/dist/checks/shortcuts.d.ts.map +1 -0
- package/dist/checks/shortcuts.js +196 -0
- package/dist/checks/shortcuts.js.map +1 -0
- package/dist/checks/versions.d.ts.map +1 -1
- package/dist/checks/versions.js +33 -1
- package/dist/checks/versions.js.map +1 -1
- package/dist/contract.d.ts +32 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/covers.d.ts +56 -0
- package/dist/covers.d.ts.map +1 -0
- package/dist/covers.js +135 -0
- package/dist/covers.js.map +1 -0
- package/dist/dedupe.d.ts +23 -0
- package/dist/dedupe.d.ts.map +1 -1
- package/dist/dedupe.js +96 -2
- package/dist/dedupe.js.map +1 -1
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +144 -0
- package/dist/document.js.map +1 -1
- package/dist/envelope.d.ts +8 -0
- package/dist/envelope.d.ts.map +1 -1
- package/dist/envelope.js +106 -16
- package/dist/envelope.js.map +1 -1
- package/dist/fixture.d.ts.map +1 -1
- package/dist/fixture.js +33 -4
- package/dist/fixture.js.map +1 -1
- package/dist/html-report.d.ts +24 -7
- package/dist/html-report.d.ts.map +1 -1
- package/dist/html-report.js +608 -136
- package/dist/html-report.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts.map +1 -1
- package/dist/insights.js +9 -12
- package/dist/insights.js.map +1 -1
- package/dist/links.d.ts +43 -0
- package/dist/links.d.ts.map +1 -1
- package/dist/links.js +43 -0
- package/dist/links.js.map +1 -1
- package/dist/observe.d.ts +8 -0
- package/dist/observe.d.ts.map +1 -1
- package/dist/observe.js +45 -1
- package/dist/observe.js.map +1 -1
- package/dist/options.d.ts +53 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +3 -0
- package/dist/options.js.map +1 -1
- package/dist/reading-level.d.ts +22 -1
- package/dist/reading-level.d.ts.map +1 -1
- package/dist/reading-level.js +122 -19
- package/dist/reading-level.js.map +1 -1
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +31 -15
- package/dist/reporter.js.map +1 -1
- package/dist/scan.d.ts +36 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +16 -2
- package/dist/scan.js.map +1 -1
- package/dist/screens.d.ts +55 -4
- package/dist/screens.d.ts.map +1 -1
- package/dist/screens.js +243 -43
- package/dist/screens.js.map +1 -1
- package/dist/settle.d.ts +35 -0
- package/dist/settle.d.ts.map +1 -1
- package/dist/settle.js +43 -2
- package/dist/settle.js.map +1 -1
- package/dist/summary.d.ts +35 -0
- package/dist/summary.d.ts.map +1 -1
- package/dist/summary.js +248 -28
- package/dist/summary.js.map +1 -1
- package/dist/unscannable-reasons.d.ts +30 -0
- package/dist/unscannable-reasons.d.ts.map +1 -0
- package/dist/unscannable-reasons.js +53 -0
- package/dist/unscannable-reasons.js.map +1 -0
- package/dist/viewports.d.ts +105 -0
- package/dist/viewports.d.ts.map +1 -0
- package/dist/viewports.js +134 -0
- package/dist/viewports.js.map +1 -0
- package/package.json +5 -4
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
import type { DynamicCheckEvidence } from './dynamic.js';
|
|
3
|
+
/**
|
|
4
|
+
* WCAG 1.4.10 Reflow (AA): no two-dimensional scrolling at 320 CSS pixels.
|
|
5
|
+
*
|
|
6
|
+
* axe sees one viewport. Reflow is a statement about a DIFFERENT viewport, and
|
|
7
|
+
* resizing is something only the runner can do — so this is a rule no
|
|
8
|
+
* in-page analyser can ever have, and one every customer's suite could have had
|
|
9
|
+
* for fifteen lines of code.
|
|
10
|
+
*
|
|
11
|
+
* ---------------------------------------------------------------------------
|
|
12
|
+
* **This rule CHANGES the thing it measures, and that decision is the design.**
|
|
13
|
+
*
|
|
14
|
+
* Every other check we run reads the page. This one resizes it, which fires the
|
|
15
|
+
* page's resize handlers, re-runs its media queries, and can re-render half of
|
|
16
|
+
* a responsive application. Three options were on the table: restore the
|
|
17
|
+
* viewport afterwards, run it only inside an explicit `setsquare.scan()`, or
|
|
18
|
+
* open a second context.
|
|
19
|
+
*
|
|
20
|
+
* It runs at TEARDOWN and restores the viewport, and neither half of that is
|
|
21
|
+
* negotiable. Teardown, because a resize mid-test is not a race we could win —
|
|
22
|
+
* a customer's next assertion would run against a layout we chose, and being
|
|
23
|
+
* the flake in someone else's suite is the one thing this SDK must never be.
|
|
24
|
+
* Restore, because Playwright's page outlives the check and a page left at
|
|
25
|
+
* 320px would poison anything that ran after it.
|
|
26
|
+
*
|
|
27
|
+
* A second context was rejected rather than overlooked: it would need the
|
|
28
|
+
* session, the cookies and the application state the test built up, and
|
|
29
|
+
* reconstructing those is exactly the work the fixture exists to avoid. The
|
|
30
|
+
* point of measuring inside the customer's own suite is that the page is
|
|
31
|
+
* already in a state no crawler could reach.
|
|
32
|
+
* ---------------------------------------------------------------------------
|
|
33
|
+
*/
|
|
34
|
+
export declare const SETSQUARE_REFLOW_CHECK_VERSION = "setsquare-reflow@1";
|
|
35
|
+
export declare const REFLOW_RULE_ID = "setsquare-reflow";
|
|
36
|
+
export interface ReflowOverflow {
|
|
37
|
+
selector: string;
|
|
38
|
+
/** How far past the viewport this element's right edge reaches, in CSS px. */
|
|
39
|
+
overhangPx: number;
|
|
40
|
+
/**
|
|
41
|
+
* True when the element is one the SC permits to scroll in two dimensions —
|
|
42
|
+
* a data table, a map, an image, a diagram, code. Those are `incomplete`, not
|
|
43
|
+
* failures: whether this particular one "requires two-dimensional layout for
|
|
44
|
+
* usage or meaning" is a judgement about the content, not about the geometry.
|
|
45
|
+
*/
|
|
46
|
+
permittedException: boolean;
|
|
47
|
+
/** The tag or role that made it a permitted exception, for the evidence. */
|
|
48
|
+
exceptionKind?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface ReflowMeasurement {
|
|
51
|
+
/** Viewport width the measurement was taken at. */
|
|
52
|
+
width: number;
|
|
53
|
+
/** `documentElement.scrollWidth - clientWidth`, before the tolerance. */
|
|
54
|
+
documentOverhangPx: number;
|
|
55
|
+
/** The outermost overflowing elements, at most ten. */
|
|
56
|
+
overflowing: ReflowOverflow[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Measure at 320px and put the viewport back.
|
|
60
|
+
*
|
|
61
|
+
* Returns nothing at all when the page has no viewport we control — a run
|
|
62
|
+
* driving a real device, say. That is honest: we could not look, so we say
|
|
63
|
+
* nothing, rather than reporting a pass we did not earn.
|
|
64
|
+
*/
|
|
65
|
+
export declare function runReflow(page: Page): Promise<DynamicCheckEvidence>;
|
|
66
|
+
/**
|
|
67
|
+
* The verdict, as a pure function of the measurement.
|
|
68
|
+
*
|
|
69
|
+
* An overflow made entirely of permitted exceptions is needs-review rather
|
|
70
|
+
* than a failure: whether a particular table or diagram "requires
|
|
71
|
+
* two-dimensional layout for usage or meaning" is a judgement about the
|
|
72
|
+
* content, and we can see the geometry but not the meaning.
|
|
73
|
+
*/
|
|
74
|
+
export declare function reflowEvidence(measurement: ReflowMeasurement): DynamicCheckEvidence;
|
|
75
|
+
//# sourceMappingURL=reflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reflow.d.ts","sourceRoot":"","sources":["../../src/checks/reflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAI7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AAEnE,eAAO,MAAM,cAAc,qBAAqB,CAAC;AAajD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAC5B,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,WAAW,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA0BzE;AAyJD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,iBAAiB,GAAG,oBAAoB,CAyDnF"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { REFLOW_HELP_URL } from '../links.js';
|
|
2
|
+
import { sanitiseSelectors } from '../sanitise.js';
|
|
3
|
+
/**
|
|
4
|
+
* WCAG 1.4.10 Reflow (AA): no two-dimensional scrolling at 320 CSS pixels.
|
|
5
|
+
*
|
|
6
|
+
* axe sees one viewport. Reflow is a statement about a DIFFERENT viewport, and
|
|
7
|
+
* resizing is something only the runner can do — so this is a rule no
|
|
8
|
+
* in-page analyser can ever have, and one every customer's suite could have had
|
|
9
|
+
* for fifteen lines of code.
|
|
10
|
+
*
|
|
11
|
+
* ---------------------------------------------------------------------------
|
|
12
|
+
* **This rule CHANGES the thing it measures, and that decision is the design.**
|
|
13
|
+
*
|
|
14
|
+
* Every other check we run reads the page. This one resizes it, which fires the
|
|
15
|
+
* page's resize handlers, re-runs its media queries, and can re-render half of
|
|
16
|
+
* a responsive application. Three options were on the table: restore the
|
|
17
|
+
* viewport afterwards, run it only inside an explicit `setsquare.scan()`, or
|
|
18
|
+
* open a second context.
|
|
19
|
+
*
|
|
20
|
+
* It runs at TEARDOWN and restores the viewport, and neither half of that is
|
|
21
|
+
* negotiable. Teardown, because a resize mid-test is not a race we could win —
|
|
22
|
+
* a customer's next assertion would run against a layout we chose, and being
|
|
23
|
+
* the flake in someone else's suite is the one thing this SDK must never be.
|
|
24
|
+
* Restore, because Playwright's page outlives the check and a page left at
|
|
25
|
+
* 320px would poison anything that ran after it.
|
|
26
|
+
*
|
|
27
|
+
* A second context was rejected rather than overlooked: it would need the
|
|
28
|
+
* session, the cookies and the application state the test built up, and
|
|
29
|
+
* reconstructing those is exactly the work the fixture exists to avoid. The
|
|
30
|
+
* point of measuring inside the customer's own suite is that the page is
|
|
31
|
+
* already in a state no crawler could reach.
|
|
32
|
+
* ---------------------------------------------------------------------------
|
|
33
|
+
*/
|
|
34
|
+
export const SETSQUARE_REFLOW_CHECK_VERSION = 'setsquare-reflow@1';
|
|
35
|
+
export const REFLOW_RULE_ID = 'setsquare-reflow';
|
|
36
|
+
/** The SC's own number: content must reflow to a 320 CSS pixel viewport width. */
|
|
37
|
+
const REFLOW_WIDTH = 320;
|
|
38
|
+
/**
|
|
39
|
+
* Sub-pixel layout rounding produces `scrollWidth = clientWidth + 0.5` on
|
|
40
|
+
* layouts that are perfectly fine, and a gate that cries wolf gets switched
|
|
41
|
+
* off. One pixel, learned the hard way in the marketing site's own spec before
|
|
42
|
+
* this rule existed.
|
|
43
|
+
*/
|
|
44
|
+
const TOLERANCE_PX = 1;
|
|
45
|
+
/**
|
|
46
|
+
* Measure at 320px and put the viewport back.
|
|
47
|
+
*
|
|
48
|
+
* Returns nothing at all when the page has no viewport we control — a run
|
|
49
|
+
* driving a real device, say. That is honest: we could not look, so we say
|
|
50
|
+
* nothing, rather than reporting a pass we did not earn.
|
|
51
|
+
*/
|
|
52
|
+
export async function runReflow(page) {
|
|
53
|
+
const original = page.viewportSize();
|
|
54
|
+
if (!original)
|
|
55
|
+
return {};
|
|
56
|
+
try {
|
|
57
|
+
if (original.width > REFLOW_WIDTH) {
|
|
58
|
+
await page.setViewportSize({ width: REFLOW_WIDTH, height: original.height });
|
|
59
|
+
// One frame, so layout and any resize handler have run before we measure.
|
|
60
|
+
await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => resolve())));
|
|
61
|
+
}
|
|
62
|
+
const measurement = await page.evaluate(measureOverflow, { tolerance: TOLERANCE_PX });
|
|
63
|
+
return reflowEvidence(measurement);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Never throws into a customer's test, and never reports a guess.
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
if (original.width > REFLOW_WIDTH) {
|
|
71
|
+
await page.setViewportSize(original).catch(() => {
|
|
72
|
+
// The page is about to be discarded; a failed restore is not worth
|
|
73
|
+
// reddening anyone's suite over, and there is nothing left to poison.
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Injected into the page. Finds the OUTERMOST elements sticking past the
|
|
80
|
+
* viewport, because a wide container reports every one of its children as
|
|
81
|
+
* overflowing too and a finding listing forty nested divs sends nobody anywhere.
|
|
82
|
+
*/
|
|
83
|
+
function measureOverflow({ tolerance }) {
|
|
84
|
+
const doc = document.documentElement;
|
|
85
|
+
const limit = doc.clientWidth + tolerance;
|
|
86
|
+
/*
|
|
87
|
+
* Content the SC permits to scroll in two dimensions: "parts of the content
|
|
88
|
+
* which require two-dimensional layout for usage or meaning". The list is
|
|
89
|
+
* from the SC's own examples — data tables, images, maps, diagrams, video,
|
|
90
|
+
* and code, whose meaning depends on line breaks staying where they are.
|
|
91
|
+
*/
|
|
92
|
+
const exceptionOf = (el) => {
|
|
93
|
+
const tag = el.tagName.toLowerCase();
|
|
94
|
+
if (['table', 'pre', 'code', 'svg', 'canvas', 'img', 'video', 'iframe', 'map'].includes(tag)) {
|
|
95
|
+
return tag;
|
|
96
|
+
}
|
|
97
|
+
const role = el.getAttribute('role');
|
|
98
|
+
if (role && ['grid', 'table', 'treegrid', 'img', 'application'].includes(role)) {
|
|
99
|
+
return `role=${role}`;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
};
|
|
103
|
+
const step = (el) => {
|
|
104
|
+
const tag = el.tagName.toLowerCase();
|
|
105
|
+
if (el.id)
|
|
106
|
+
return `${tag}#${el.id}`;
|
|
107
|
+
if (el.classList.length > 0)
|
|
108
|
+
return `${tag}.${el.classList[0]}`;
|
|
109
|
+
const parent = el.parentElement;
|
|
110
|
+
if (!parent)
|
|
111
|
+
return tag;
|
|
112
|
+
return `${tag}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`;
|
|
113
|
+
};
|
|
114
|
+
const describe = (el) => {
|
|
115
|
+
const parts = [];
|
|
116
|
+
let node = el;
|
|
117
|
+
for (let depth = 0; node && depth < 3; depth += 1) {
|
|
118
|
+
parts.unshift(step(node));
|
|
119
|
+
if (node.id || node.classList.length > 0)
|
|
120
|
+
break;
|
|
121
|
+
node = node.parentElement;
|
|
122
|
+
if (!node || node === document.body)
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
return parts.join(' > ');
|
|
126
|
+
};
|
|
127
|
+
const overflowing = [];
|
|
128
|
+
const claimed = [];
|
|
129
|
+
for (const el of Array.from(document.body.querySelectorAll('*'))) {
|
|
130
|
+
if (overflowing.length >= 10)
|
|
131
|
+
break;
|
|
132
|
+
const cs = window.getComputedStyle(el);
|
|
133
|
+
if (cs.display === 'none' || cs.visibility === 'hidden')
|
|
134
|
+
continue;
|
|
135
|
+
/*
|
|
136
|
+
* AN ELEMENT WHOSE CONTENTS ARE SKIPPED REPORTS THE BOX IT HAD LAST TIME,
|
|
137
|
+
* AND THAT BOX IS A LIE ABOUT THIS VIEWPORT.
|
|
138
|
+
*
|
|
139
|
+
* `content-visibility: hidden` — what the UA applies to
|
|
140
|
+
* `hidden="until-found"`, which is how every component library ships a
|
|
141
|
+
* collapsed disclosure — stops the subtree being laid out without removing
|
|
142
|
+
* it. `display` and `visibility` on the descendants read as ordinary, and
|
|
143
|
+
* `getBoundingClientRect()` hands back geometry from whenever they WERE
|
|
144
|
+
* last laid out.
|
|
145
|
+
*
|
|
146
|
+
* This rule resizes to 320 and then measures, so those stale boxes are
|
|
147
|
+
* measured at the width the page used to be. A collapsed mobile nav laid
|
|
148
|
+
* out at 390 reports a right edge of 372 and is announced as reflow
|
|
149
|
+
* overflow of 52 pixels at 320 — a level AA failure, on an element nobody
|
|
150
|
+
* can see, caused entirely by the resize this rule performs.
|
|
151
|
+
*
|
|
152
|
+
* THERE IS NO FIXTURE PAGE FOR THIS, and the reason is worth writing down.
|
|
153
|
+
* On a small page Chromium re-lays the skipped subtree out as soon as the
|
|
154
|
+
* viewport changes, so every reduction of the real case — the same markup,
|
|
155
|
+
* the same attribute, the panel opened and closed first, a link inside it
|
|
156
|
+
* focused to trigger the reveal — measures the NEW width and passes whether
|
|
157
|
+
* this guard is present or not. A fixture that passes for the wrong reason
|
|
158
|
+
* is worse than no fixture, so one was written, found to prove nothing, and
|
|
159
|
+
* deleted. The regression test is `apps/marketing`'s own scan, which is in
|
|
160
|
+
* CI and which was run both ways: with this guard removed it reports the
|
|
161
|
+
* finding on three surfaces, and with it in place on none.
|
|
162
|
+
*
|
|
163
|
+
* Guarded, because a customer's browser may predate `checkVisibility`. The
|
|
164
|
+
* cost of not having it is this false positive, not a crash.
|
|
165
|
+
*/
|
|
166
|
+
if (typeof el.checkVisibility === 'function' &&
|
|
167
|
+
!el.checkVisibility({ contentVisibilityAuto: true, visibilityProperty: true })) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const rect = el.getBoundingClientRect();
|
|
171
|
+
if (rect.width === 0 && rect.height === 0)
|
|
172
|
+
continue;
|
|
173
|
+
if (rect.right <= limit)
|
|
174
|
+
continue;
|
|
175
|
+
// An ancestor already reported: this is that ancestor's overflow, not a
|
|
176
|
+
// second finding. Reporting both sends someone to fix the same thing twice.
|
|
177
|
+
if (claimed.some((ancestor) => ancestor.contains(el)))
|
|
178
|
+
continue;
|
|
179
|
+
/*
|
|
180
|
+
* A scroll container is allowed to be wider than itself INSIDE — that is
|
|
181
|
+
* what it is for. Its own box is what has to fit, and its box is measured
|
|
182
|
+
* on the ancestor path like everything else, so skipping the contents here
|
|
183
|
+
* loses nothing and removes the commonest false positive there is.
|
|
184
|
+
*/
|
|
185
|
+
let contained = false;
|
|
186
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
187
|
+
const parentStyle = window.getComputedStyle(node);
|
|
188
|
+
if (parentStyle.overflowX === 'auto' || parentStyle.overflowX === 'scroll') {
|
|
189
|
+
contained = true;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
/*
|
|
193
|
+
* `overflow: hidden` CLIPS, so nothing inside it can make the document
|
|
194
|
+
* scroll — which is the only thing 1.4.10 is about.
|
|
195
|
+
*
|
|
196
|
+
* Missing this was a false positive on the first real run: a decorative
|
|
197
|
+
* mark that bleeds off the corner of a hero, inside a container that
|
|
198
|
+
* clips it precisely so it cannot. The element's rect still reaches past
|
|
199
|
+
* the viewport, and none of it is painted there.
|
|
200
|
+
*/
|
|
201
|
+
if (parentStyle.overflowX === 'hidden' || parentStyle.overflowX === 'clip') {
|
|
202
|
+
contained = true;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (contained)
|
|
207
|
+
continue;
|
|
208
|
+
const kind = exceptionOf(el);
|
|
209
|
+
claimed.push(el);
|
|
210
|
+
overflowing.push({
|
|
211
|
+
selector: describe(el),
|
|
212
|
+
overhangPx: Math.round(rect.right - doc.clientWidth),
|
|
213
|
+
permittedException: kind !== undefined,
|
|
214
|
+
...(kind ? { exceptionKind: kind } : {}),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
width: doc.clientWidth,
|
|
219
|
+
documentOverhangPx: Math.round(doc.scrollWidth - doc.clientWidth),
|
|
220
|
+
overflowing,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const REFLOW_WCAG = { criteria: ['1.4.10'], level: 'AA', versionTags: [] };
|
|
224
|
+
/**
|
|
225
|
+
* The verdict, as a pure function of the measurement.
|
|
226
|
+
*
|
|
227
|
+
* An overflow made entirely of permitted exceptions is needs-review rather
|
|
228
|
+
* than a failure: whether a particular table or diagram "requires
|
|
229
|
+
* two-dimensional layout for usage or meaning" is a judgement about the
|
|
230
|
+
* content, and we can see the geometry but not the meaning.
|
|
231
|
+
*/
|
|
232
|
+
export function reflowEvidence(measurement) {
|
|
233
|
+
if (measurement.documentOverhangPx <= TOLERANCE_PX && measurement.overflowing.length === 0) {
|
|
234
|
+
return { pass: { ruleId: REFLOW_RULE_ID, nodeCount: 1, wcag: REFLOW_WCAG } };
|
|
235
|
+
}
|
|
236
|
+
const failing = measurement.overflowing.filter((row) => !row.permittedException);
|
|
237
|
+
const exceptions = measurement.overflowing.filter((row) => row.permittedException);
|
|
238
|
+
if (failing.length > 0) {
|
|
239
|
+
const first = failing[0];
|
|
240
|
+
return {
|
|
241
|
+
finding: {
|
|
242
|
+
ruleId: REFLOW_RULE_ID,
|
|
243
|
+
status: 'violation',
|
|
244
|
+
impact: 'serious',
|
|
245
|
+
wcag: REFLOW_WCAG,
|
|
246
|
+
helpUrl: REFLOW_HELP_URL,
|
|
247
|
+
evidence: {
|
|
248
|
+
failureSummary: `At a ${REFLOW_WIDTH} CSS pixel viewport the page scrolls sideways: ${first.selector} ` +
|
|
249
|
+
`reaches ${first.overhangPx}px past the right edge` +
|
|
250
|
+
(failing.length > 1 ? `, and ${failing.length - 1} more element(s) overflow` : '') +
|
|
251
|
+
`. WCAG 1.4.10 asks for content to reflow to 320px without two-dimensional ` +
|
|
252
|
+
`scrolling. Fix: let this element wrap or shrink — a fixed width, a min-width, or ` +
|
|
253
|
+
`a long unbroken string is the usual cause.`,
|
|
254
|
+
},
|
|
255
|
+
targets: {
|
|
256
|
+
nodeCount: failing.length,
|
|
257
|
+
...sanitiseSelectors(failing.map((row) => row.selector)),
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const first = exceptions[0];
|
|
263
|
+
return {
|
|
264
|
+
finding: {
|
|
265
|
+
ruleId: REFLOW_RULE_ID,
|
|
266
|
+
status: 'incomplete',
|
|
267
|
+
impact: 'moderate',
|
|
268
|
+
wcag: REFLOW_WCAG,
|
|
269
|
+
helpUrl: REFLOW_HELP_URL,
|
|
270
|
+
evidence: {
|
|
271
|
+
failureSummary: `At a ${REFLOW_WIDTH} CSS pixel viewport ${first.selector} reaches ` +
|
|
272
|
+
`${first.overhangPx}px past the right edge. It is a <${first.exceptionKind}>, and ` +
|
|
273
|
+
`WCAG 1.4.10 excepts content that requires two-dimensional layout for usage or ` +
|
|
274
|
+
`meaning — a data table, a map, a diagram, code. Whether this one does is a judgement ` +
|
|
275
|
+
`about the content, which we can see the geometry of and not the meaning. ` +
|
|
276
|
+
`${exceptions.length} element(s) affected.`,
|
|
277
|
+
},
|
|
278
|
+
targets: {
|
|
279
|
+
nodeCount: exceptions.length,
|
|
280
|
+
...sanitiseSelectors(exceptions.map((row) => row.selector)),
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=reflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reflow.js","sourceRoot":"","sources":["../../src/checks/reflow.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* boundary. Version every behavioural change via SETSQUARE_RULE_PACK_VERSION — it
|
|
25
25
|
* feeds the ruleset digest.
|
|
26
26
|
*/
|
|
27
|
-
export declare const SETSQUARE_RULE_PACK_VERSION = "setsquare-rules@
|
|
27
|
+
export declare const SETSQUARE_RULE_PACK_VERSION = "setsquare-rules@6";
|
|
28
28
|
export declare const SETSQUARE_RULE_IDS: readonly ["setsquare-target-size-aaa", "setsquare-visual-presentation", "setsquare-link-purpose"];
|
|
29
29
|
/**
|
|
30
30
|
* An IIFE taking the axe instance. Interpolated values are limited to the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-pack.d.ts","sourceRoot":"","sources":["../../src/checks/rule-pack.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;
|
|
1
|
+
{"version":3,"file":"rule-pack.d.ts","sourceRoot":"","sources":["../../src/checks/rule-pack.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAQH,eAAO,MAAM,2BAA2B,sBAAsB,CAAC;AAE/D,eAAO,MAAM,kBAAkB,mGAIrB,CAAC;AAEX;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAkhBvC"}
|