@surea11y/core 1.2.0 → 1.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/CHANGELOG.md +81 -7
- package/LICENSE +373 -21
- package/README.md +175 -35
- package/bin/surea11y-core.js +20 -0
- package/docs/API_STABILITY.md +27 -1
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/ENGINE_OPTIONS.md +2 -0
- package/docs/I18N.md +12 -9
- package/docs/INTEGRATION.md +19 -1
- package/docs/LIMITATIONS.md +1 -1
- package/docs/OUTPUT_SCHEMA.md +1 -1
- package/docs/REPORT.md +1 -1
- package/docs/RULE_CATALOG.md +1 -1
- package/docs/SARIF.md +59 -0
- package/package.json +63 -18
- package/src/baseline.js +0 -0
- package/src/checks/automatic/area-alt-present.js +63 -31
- package/src/checks/automatic/aria-allowed-attr.js +204 -80
- package/src/checks/automatic/aria-allowed-role.js +23 -7
- package/src/checks/automatic/aria-braille-equivalent.js +34 -10
- package/src/checks/automatic/aria-conditional-attr.js +32 -14
- package/src/checks/automatic/aria-deprecated-role.js +26 -11
- package/src/checks/automatic/aria-hidden-body.js +48 -23
- package/src/checks/automatic/aria-hidden-focus.js +420 -66
- package/src/checks/automatic/aria-prohibited-attr.js +327 -60
- package/src/checks/automatic/aria-prohibited-children.js +111 -103
- package/src/checks/automatic/aria-required-attr.js +29 -15
- package/src/checks/automatic/aria-required-children.js +44 -24
- package/src/checks/automatic/aria-required-parent.js +64 -35
- package/src/checks/automatic/aria-role-name-present.js +49 -21
- package/src/checks/automatic/aria-roles-valid.js +24 -12
- package/src/checks/automatic/aria-valid-attr-value.js +46 -22
- package/src/checks/automatic/aria-valid-attr.js +19 -5
- package/src/checks/automatic/autocomplete-valid.js +76 -16
- package/src/checks/automatic/avoid-inline-spacing.js +23 -8
- package/src/checks/automatic/binary-control-name-present.js +62 -50
- package/src/checks/automatic/button-name-present.js +54 -24
- package/src/checks/automatic/bypass-blocks-present.js +51 -32
- package/src/checks/automatic/canvas-text-alternative-present.js +59 -26
- package/src/checks/automatic/combobox-name-present.js +40 -45
- package/src/checks/automatic/contrast-computable.js +363 -341
- package/src/checks/automatic/contrast-enhanced.js +489 -466
- package/src/checks/automatic/contrast-minimum.js +488 -465
- package/src/checks/automatic/css-orientation-lock.js +51 -35
- package/src/checks/automatic/definition-list-children-valid.js +46 -25
- package/src/checks/automatic/deprecated-elements-not-used.js +25 -9
- package/src/checks/automatic/dialog-name-present.js +47 -85
- package/src/checks/automatic/dlitem-parent-valid.js +25 -8
- package/src/checks/automatic/duplicate-id-aria.js +28 -9
- package/src/checks/automatic/embed-text-alternative-present.js +88 -35
- package/src/checks/automatic/form-control-programmatic-label-present.js +81 -196
- package/src/checks/automatic/form-control-single-label.js +50 -14
- package/src/checks/automatic/html-xml-lang-mismatch.js +36 -18
- package/src/checks/automatic/iframe-focusable-content.js +265 -22
- package/src/checks/automatic/iframe-name-present.js +33 -9
- package/src/checks/automatic/iframe-title-unique.js +32 -9
- package/src/checks/automatic/img-alt-present.js +54 -52
- package/src/checks/automatic/input-image-alt-present.js +141 -112
- package/src/checks/automatic/label-in-name.js +65 -41
- package/src/checks/automatic/language-page-present.js +111 -109
- package/src/checks/automatic/link-in-text-block.js +61 -19
- package/src/checks/automatic/link-name-present.js +47 -14
- package/src/checks/automatic/list-children-valid.js +40 -33
- package/src/checks/automatic/listbox-name-present.js +41 -19
- package/src/checks/automatic/listitem-parent-valid.js +48 -13
- package/src/checks/automatic/menuitem-name-present.js +41 -61
- package/src/checks/automatic/meta-refresh-no-exceptions.js +32 -11
- package/src/checks/automatic/meta-refresh-timing-absent.js +22 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +26 -7
- package/src/checks/automatic/meter-name-present.js +40 -36
- package/src/checks/automatic/nested-interactive-controls-absent.js +58 -15
- package/src/checks/automatic/object-text-alternative-present.js +93 -39
- package/src/checks/automatic/option-name-present.js +40 -21
- package/src/checks/automatic/page-title-present.js +19 -6
- package/src/checks/automatic/progressbar-name-present.js +49 -44
- package/src/checks/automatic/role-img-alt-present.js +211 -159
- package/src/checks/automatic/searchbox-name-present.js +41 -19
- package/src/checks/automatic/server-side-image-map-absent.js +27 -11
- package/src/checks/automatic/slider-name-present.js +42 -47
- package/src/checks/automatic/spinbutton-name-present.js +41 -19
- package/src/checks/automatic/summary-name-present.js +39 -17
- package/src/checks/automatic/svg-image-text-alternative-present.js +116 -47
- package/src/checks/automatic/svg-text-alternative-present.js +262 -230
- package/src/checks/automatic/tab-name-present.js +39 -60
- package/src/checks/automatic/table-headers-attr-valid.js +27 -10
- package/src/checks/automatic/table-th-has-data-cells.js +24 -8
- package/src/checks/automatic/target-size-minimum.js +123 -48
- package/src/checks/automatic/td-has-header.js +53 -12
- package/src/checks/automatic/textbox-name-present.js +41 -19
- package/src/checks/automatic/tooltip-name-present.js +39 -18
- package/src/checks/automatic/treeitem-name-present.js +40 -21
- package/src/checks/automatic/valid-lang.js +22 -6
- package/src/checks/automatic/video-poster-text-alternative-present.js +81 -36
- package/src/checks/manual/accesskeys-manual.js +17 -6
- package/src/checks/manual/area-alt-decorative-manual.js +194 -193
- package/src/checks/manual/area-alt-quality-manual.js +184 -141
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +48 -34
- package/src/checks/manual/aria-text-manual.js +20 -11
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +151 -114
- package/src/checks/manual/css-hidden-focus.js +375 -169
- package/src/checks/manual/embed-text-alternative-quality-manual.js +178 -162
- package/src/checks/manual/empty-heading-manual.js +41 -24
- package/src/checks/manual/empty-table-header-manual.js +69 -31
- package/src/checks/manual/focus-order-semantics-manual.js +60 -13
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +209 -246
- package/src/checks/manual/heading-order-manual.js +50 -8
- package/src/checks/manual/identical-links-same-purpose-manual.js +36 -12
- package/src/checks/manual/image-redundant-alt-manual.js +38 -8
- package/src/checks/manual/img-alt-decorative-manual.js +133 -96
- package/src/checks/manual/img-alt-quality-manual.js +178 -127
- package/src/checks/manual/input-image-alt-decorative-manual.js +127 -92
- package/src/checks/manual/input-image-alt-quality-manual.js +127 -92
- package/src/checks/manual/label-title-only-manual.js +44 -28
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -38
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +85 -32
- package/src/checks/manual/landmark-main-is-top-level-manual.js +69 -27
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +45 -33
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +43 -31
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +27 -21
- package/src/checks/manual/landmark-one-main-manual.js +38 -43
- package/src/checks/manual/landmark-unique-manual.js +78 -67
- package/src/checks/manual/link-name-quality-manual.js +45 -12
- package/src/checks/manual/media-transcript-present-manual.js +37 -22
- package/src/checks/manual/meta-viewport-large-manual.js +19 -6
- package/src/checks/manual/mouse-only-event-handlers-manual.js +40 -11
- package/src/checks/manual/no-autoplay-audio-manual.js +22 -6
- package/src/checks/manual/object-text-alternative-quality-manual.js +177 -154
- package/src/checks/manual/p-as-heading-manual.js +24 -7
- package/src/checks/manual/page-has-heading-one-manual.js +42 -32
- package/src/checks/manual/page-title-patterns-manual.js +80 -50
- package/src/checks/manual/presentation-role-conflict-manual.js +101 -47
- package/src/checks/manual/region-manual.js +244 -60
- package/src/checks/manual/scope-attr-valid-manual.js +13 -4
- package/src/checks/manual/scrollable-region-focusable-manual.js +39 -11
- package/src/checks/manual/skip-link-manual.js +42 -18
- package/src/checks/manual/svg-text-alternative-quality-manual.js +208 -165
- package/src/checks/manual/tabindex-manual.js +13 -4
- package/src/checks/manual/table-duplicate-name-manual.js +22 -11
- package/src/checks/manual/table-fake-caption-manual.js +48 -10
- package/src/checks/manual/video-caption-manual.js +17 -4
- package/src/checks/manual-review.js +58 -12
- package/src/core.js +41705 -29650
- package/src/index.js +2 -0
- package/src/report.js +109 -47
- package/src/sarif.js +190 -0
- package/surea11y.browser.js +37774 -0
- package/bin/core.js +0 -348
- package/docs/CLI.md +0 -75
- package/src/catalogs/composites.wcag.js +0 -490
- package/src/checks/rules-and-tags.full.csv +0 -19
- package/src/checks/rules-and-tags.full.json +0 -259
- package/src/core/aria-helpers.js +0 -970
- package/src/core/contrast-helpers.js +0 -1147
- package/src/core/dom-helpers.js +0 -4235
- package/src/core/dom-runner.js +0 -671
- package/src/core/frame-messaging.js +0 -210
- package/src/core/frame-scan.js +0 -178
- package/src/core/rollup-composites.js +0 -135
- package/src/core/rule-meta.js +0 -159
- package/src/coverage/wcag-facets.js +0 -1079
- package/src/coverage/wcag-version-map.js +0 -84
- package/src/i18n/en.js +0 -923
- package/src/i18n/fr.js +0 -844
- package/src/policy/contracts.js +0 -18
- package/src/policy/resolvePolicy.js +0 -55
- package/src/policy/schemas/engine-options.schema.json +0 -103
- package/src/policy/schemas/policy-contract.schema.json +0 -40
|
@@ -1,378 +1,400 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
const id = 'contrast-computable';
|
|
4
6
|
|
|
5
7
|
const meta = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
title: 'Color contrast is computable for rendered text',
|
|
9
|
+
description:
|
|
10
|
+
'Determines whether sufficient information is available to compute WCAG color contrast for visible text (e.g., no gradients/images/blend modes that make background indeterminate).',
|
|
11
|
+
i18n: {
|
|
12
|
+
titleKey: 'contrastComputable_title',
|
|
13
|
+
descriptionKey: 'contrastComputable_description'
|
|
14
|
+
},
|
|
15
|
+
helpUrl: null,
|
|
16
|
+
tags: [
|
|
17
|
+
'wcag2aa',
|
|
18
|
+
'wcag2aaa',
|
|
19
|
+
'wcag143',
|
|
20
|
+
'wcag146',
|
|
21
|
+
'contrast',
|
|
22
|
+
'color',
|
|
23
|
+
'structure',
|
|
24
|
+
'atomic',
|
|
25
|
+
'automatic',
|
|
26
|
+
'dom'
|
|
27
|
+
],
|
|
28
|
+
wcagSc: ['1.4.3', '1.4.6'],
|
|
29
|
+
normativeMappings: [
|
|
30
|
+
{
|
|
31
|
+
standard: 'WCAG',
|
|
32
|
+
version: '2.2',
|
|
33
|
+
requirement: '1.4.3',
|
|
34
|
+
title: 'Contrast (Minimum)',
|
|
35
|
+
conformanceLevel: 'AA'
|
|
12
36
|
},
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
'contrast',
|
|
20
|
-
'color',
|
|
21
|
-
'structure',
|
|
22
|
-
'atomic',
|
|
23
|
-
'automatic',
|
|
24
|
-
'dom'
|
|
25
|
-
],
|
|
26
|
-
wcagSc: ['1.4.3', '1.4.6'],
|
|
27
|
-
normativeMappings: [
|
|
28
|
-
{
|
|
29
|
-
standard: 'WCAG',
|
|
30
|
-
version: '2.2',
|
|
31
|
-
requirement: '1.4.3',
|
|
32
|
-
title: 'Contrast (Minimum)',
|
|
33
|
-
conformanceLevel: 'AA'
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
standard: 'WCAG',
|
|
37
|
-
version: '2.2',
|
|
38
|
-
requirement: '1.4.6',
|
|
39
|
-
title: 'Contrast (Enhanced)',
|
|
40
|
-
conformanceLevel: 'AAA'
|
|
41
|
-
}
|
|
42
|
-
],
|
|
43
|
-
defaultSeverity: 'serious',
|
|
44
|
-
category: 'perceivable',
|
|
45
|
-
type: 'automatic',
|
|
46
|
-
defaultConfidence: 'high',
|
|
47
|
-
coverage: {
|
|
48
|
-
facetsBySc: {
|
|
49
|
-
'1.4.3': ['contrast-computability-143'],
|
|
50
|
-
'1.4.6': ['contrast-computability-146']
|
|
51
|
-
}
|
|
37
|
+
{
|
|
38
|
+
standard: 'WCAG',
|
|
39
|
+
version: '2.2',
|
|
40
|
+
requirement: '1.4.6',
|
|
41
|
+
title: 'Contrast (Enhanced)',
|
|
42
|
+
conformanceLevel: 'AAA'
|
|
52
43
|
}
|
|
44
|
+
],
|
|
45
|
+
defaultSeverity: 'serious',
|
|
46
|
+
category: 'perceivable',
|
|
47
|
+
type: 'automatic',
|
|
48
|
+
defaultConfidence: 'high',
|
|
49
|
+
coverage: {
|
|
50
|
+
facetsBySc: {
|
|
51
|
+
'1.4.3': ['contrast-computability-143'],
|
|
52
|
+
'1.4.6': ['contrast-computability-146']
|
|
53
|
+
}
|
|
54
|
+
}
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
function runInPage(ctx) {
|
|
56
|
-
|
|
58
|
+
const { helpers, rule, engineOptions } = ctx;
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
const __contrastSharedCache =
|
|
61
|
+
helpers && helpers.contrast && helpers.contrast.sharedCache
|
|
62
|
+
? helpers.contrast.sharedCache
|
|
63
|
+
: null;
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
const contrast =
|
|
66
|
+
engineOptions && typeof engineOptions.contrast === 'object' && engineOptions.contrast
|
|
67
|
+
? engineOptions.contrast
|
|
68
|
+
: {};
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
const mode = contrast.mode === 'auditorAssist' ? 'auditorAssist' : 'strictConformance';
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
const rootCanvasFallback =
|
|
73
|
+
typeof contrast.rootCanvasFallback === 'string' && contrast.rootCanvasFallback.trim()
|
|
74
|
+
? contrast.rootCanvasFallback.trim()
|
|
75
|
+
: '#ffffff';
|
|
70
76
|
|
|
71
|
-
|
|
77
|
+
const MAX_OCCURRENCES = 50;
|
|
72
78
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
const occurrences = [];
|
|
80
|
+
let eligibleTextCount = 0;
|
|
81
|
+
let cantTellCount = 0;
|
|
82
|
+
let assumptionsCount = 0;
|
|
83
|
+
const assumptionsAppliedSet = new Set();
|
|
78
84
|
|
|
79
|
-
|
|
85
|
+
const seenFailEls = new Set();
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
if (!el || seenFailEls.has(el)) return;
|
|
84
|
-
if (occurrences.length >= MAX_OCCURRENCES) return;
|
|
85
|
-
seenFailEls.add(el);
|
|
86
|
-
|
|
87
|
-
const rc = String(reasonCode || 'UNKNOWN');
|
|
88
|
-
|
|
89
|
-
// Match test contract: choose a specific summaryKey per computability blocker.
|
|
90
|
-
// Fall back to the generic notComputable key.
|
|
91
|
-
let summaryKey = 'contrastComputable_cantTell_notComputable';
|
|
92
|
-
if (rc === 'BACKGROUND_IMAGE_OR_GRADIENT') {
|
|
93
|
-
const t =
|
|
94
|
-
extraDetails && typeof extraDetails === 'object'
|
|
95
|
-
? String(extraDetails.backgroundFillType || '')
|
|
96
|
-
: '';
|
|
97
|
-
|
|
98
|
-
if (t === 'image') summaryKey = 'contrastComputable_cantTell_bgImage';
|
|
99
|
-
else if (t === 'gradient') summaryKey = 'contrastComputable_cantTell_bgGradient';
|
|
100
|
-
else if (t === 'imageAndGradient') summaryKey = 'contrastComputable_cantTell_bgImageAndGradient';
|
|
101
|
-
else summaryKey = 'contrastComputable_cantTell_bgImageOrGradient';
|
|
102
|
-
} else if (rc === 'MIX_BLEND_MODE') summaryKey = 'contrastComputable_cantTell_mixBlendMode';
|
|
103
|
-
else if (rc === 'BACKGROUND_FILTER_OR_BACKDROP_FILTER') {
|
|
104
|
-
const bp = extraDetails && typeof extraDetails === 'object' ? String(extraDetails.blockerProperty || '') : '';
|
|
105
|
-
if (bp === 'filter') summaryKey = 'contrastComputable_cantTell_filter';
|
|
106
|
-
else if (bp === 'backdrop-filter') summaryKey = 'contrastComputable_cantTell_backdropFilter';
|
|
107
|
-
else summaryKey = 'contrastComputable_cantTell_filterOrBackdropFilter';
|
|
108
|
-
} else if (rc === 'BACKGROUND_NOT_OPAQUE_AT_ROOT') summaryKey = 'contrastComputable_cantTell_rootNotOpaque';
|
|
109
|
-
|
|
110
|
-
const details =
|
|
111
|
-
Object.assign(
|
|
112
|
-
{reasonCode: rc},
|
|
113
|
-
extraDetails && typeof extraDetails === 'object' ? extraDetails : {}
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
const occBase = {
|
|
117
|
-
selector: '',
|
|
118
|
-
html: '',
|
|
119
|
-
summary: '',
|
|
120
|
-
hint: '',
|
|
121
|
-
i18n: {
|
|
122
|
-
summaryKey,
|
|
123
|
-
hintKey: '',
|
|
124
|
-
params: Object.assign(
|
|
125
|
-
{reasonCode: rc},
|
|
126
|
-
(details && typeof details === 'object')
|
|
127
|
-
? {
|
|
128
|
-
blockerProperty: details.blockerProperty ? String(details.blockerProperty) : '',
|
|
129
|
-
blockerValue: details.blockerValue ? String(details.blockerValue) : '',
|
|
130
|
-
backgroundAlpha:
|
|
131
|
-
(details.backgroundAlpha !== undefined && details.backgroundAlpha !== null)
|
|
132
|
-
? String(details.backgroundAlpha)
|
|
133
|
-
: ''
|
|
134
|
-
}
|
|
135
|
-
: {}
|
|
136
|
-
)
|
|
137
|
-
},
|
|
138
|
-
data: {details}
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
if (helpers && typeof helpers.reportOccurrence === 'function') {
|
|
142
|
-
occurrences.push(helpers.reportOccurrence(el, occBase));
|
|
143
|
-
} else {
|
|
144
|
-
// Never compute selector/snippet in the rule.
|
|
145
|
-
occurrences.push({...occBase});
|
|
146
|
-
}
|
|
147
|
-
} catch {
|
|
148
|
-
// no-throw
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// perf: cache per-element analysis so multiple text nodes in same element don't repeat expensive work
|
|
153
|
-
const __elBlockerCache = __contrastSharedCache
|
|
154
|
-
? (__contrastSharedCache.__elBlockerCache || (__contrastSharedCache.__elBlockerCache = new WeakMap()))
|
|
155
|
-
: new WeakMap(); // Element -> { ok:boolean, reasonCode, blockerSelector, blockerProperty, blockerValue }
|
|
156
|
-
const __elBgCache = __contrastSharedCache
|
|
157
|
-
? (__contrastSharedCache.__elBgCache || (__contrastSharedCache.__elBgCache = new WeakMap()))
|
|
158
|
-
: new WeakMap(); // Element -> { ok, rgba, alpha, reasonCode } (no stack)
|
|
159
|
-
const __elFgCache = __contrastSharedCache
|
|
160
|
-
? (__contrastSharedCache.__elFgCache || (__contrastSharedCache.__elFgCache = new WeakMap()))
|
|
161
|
-
: new WeakMap(); // Element -> { rgba, alpha, opacityProduct }
|
|
162
|
-
|
|
163
|
-
// perf: fast-path memo for self-opaque background (common) to avoid ancestor walk
|
|
164
|
-
const __elBgSelfOpaqueCache = __contrastSharedCache
|
|
165
|
-
? (__contrastSharedCache.__elBgSelfOpaqueCache || (__contrastSharedCache.__elBgSelfOpaqueCache = new WeakMap()))
|
|
166
|
-
: new WeakMap(); // Element -> { ok, rgba, alpha, reasonCode }
|
|
167
|
-
|
|
168
|
-
// Shared deterministic text scan (computed once per run and reused across contrast checks)
|
|
169
|
-
let scan = null;
|
|
87
|
+
function pushCantTellOccurrence(el, reasonCode, extraDetails) {
|
|
170
88
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
89
|
+
if (!el || seenFailEls.has(el)) return;
|
|
90
|
+
if (occurrences.length >= MAX_OCCURRENCES) return;
|
|
91
|
+
seenFailEls.add(el);
|
|
92
|
+
|
|
93
|
+
const rc = String(reasonCode || 'UNKNOWN');
|
|
94
|
+
|
|
95
|
+
// Match test contract: choose a specific summaryKey per computability blocker.
|
|
96
|
+
// Fall back to the generic notComputable key.
|
|
97
|
+
let summaryKey = 'contrastComputable_cantTell_notComputable';
|
|
98
|
+
if (rc === 'BACKGROUND_IMAGE_OR_GRADIENT') {
|
|
99
|
+
const t =
|
|
100
|
+
extraDetails && typeof extraDetails === 'object'
|
|
101
|
+
? String(extraDetails.backgroundFillType || '')
|
|
102
|
+
: '';
|
|
103
|
+
|
|
104
|
+
if (t === 'image') summaryKey = 'contrastComputable_cantTell_bgImage';
|
|
105
|
+
else if (t === 'gradient') summaryKey = 'contrastComputable_cantTell_bgGradient';
|
|
106
|
+
else if (t === 'imageAndGradient')
|
|
107
|
+
summaryKey = 'contrastComputable_cantTell_bgImageAndGradient';
|
|
108
|
+
else summaryKey = 'contrastComputable_cantTell_bgImageOrGradient';
|
|
109
|
+
} else if (rc === 'MIX_BLEND_MODE') summaryKey = 'contrastComputable_cantTell_mixBlendMode';
|
|
110
|
+
else if (rc === 'BACKGROUND_FILTER_OR_BACKDROP_FILTER') {
|
|
111
|
+
const bp =
|
|
112
|
+
extraDetails && typeof extraDetails === 'object'
|
|
113
|
+
? String(extraDetails.blockerProperty || '')
|
|
114
|
+
: '';
|
|
115
|
+
if (bp === 'filter') summaryKey = 'contrastComputable_cantTell_filter';
|
|
116
|
+
else if (bp === 'backdrop-filter')
|
|
117
|
+
summaryKey = 'contrastComputable_cantTell_backdropFilter';
|
|
118
|
+
else summaryKey = 'contrastComputable_cantTell_filterOrBackdropFilter';
|
|
119
|
+
} else if (rc === 'BACKGROUND_NOT_OPAQUE_AT_ROOT')
|
|
120
|
+
summaryKey = 'contrastComputable_cantTell_rootNotOpaque';
|
|
121
|
+
|
|
122
|
+
const details = Object.assign(
|
|
123
|
+
{ reasonCode: rc },
|
|
124
|
+
extraDetails && typeof extraDetails === 'object' ? extraDetails : {}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const occBase = {
|
|
128
|
+
selector: '',
|
|
129
|
+
html: '',
|
|
130
|
+
summary: '',
|
|
131
|
+
hint: '',
|
|
132
|
+
i18n: {
|
|
133
|
+
summaryKey,
|
|
134
|
+
hintKey: '',
|
|
135
|
+
params: Object.assign(
|
|
136
|
+
{ reasonCode: rc },
|
|
137
|
+
details && typeof details === 'object'
|
|
138
|
+
? {
|
|
139
|
+
blockerProperty: details.blockerProperty ? String(details.blockerProperty) : '',
|
|
140
|
+
blockerValue: details.blockerValue ? String(details.blockerValue) : '',
|
|
141
|
+
backgroundAlpha:
|
|
142
|
+
details.backgroundAlpha !== undefined && details.backgroundAlpha !== null
|
|
143
|
+
? String(details.backgroundAlpha)
|
|
144
|
+
: ''
|
|
145
|
+
}
|
|
146
|
+
: {}
|
|
147
|
+
)
|
|
148
|
+
},
|
|
149
|
+
data: { details }
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (helpers && typeof helpers.reportOccurrence === 'function') {
|
|
153
|
+
occurrences.push(helpers.reportOccurrence(el, occBase));
|
|
154
|
+
} else {
|
|
155
|
+
// Never compute selector/snippet in the rule.
|
|
156
|
+
occurrences.push({ ...occBase });
|
|
157
|
+
}
|
|
174
158
|
} catch {
|
|
175
|
-
|
|
159
|
+
// no-throw
|
|
176
160
|
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// perf: cache per-element analysis so multiple text nodes in same element don't repeat expensive work
|
|
164
|
+
const __elBlockerCache = __contrastSharedCache
|
|
165
|
+
? __contrastSharedCache.__elBlockerCache ||
|
|
166
|
+
(__contrastSharedCache.__elBlockerCache = new WeakMap())
|
|
167
|
+
: new WeakMap(); // Element -> { ok:boolean, reasonCode, blockerSelector, blockerProperty, blockerValue }
|
|
168
|
+
const __elBgCache = __contrastSharedCache
|
|
169
|
+
? __contrastSharedCache.__elBgCache || (__contrastSharedCache.__elBgCache = new WeakMap())
|
|
170
|
+
: new WeakMap(); // Element -> { ok, rgba, alpha, reasonCode } (no stack)
|
|
171
|
+
const __elFgCache = __contrastSharedCache
|
|
172
|
+
? __contrastSharedCache.__elFgCache || (__contrastSharedCache.__elFgCache = new WeakMap())
|
|
173
|
+
: new WeakMap(); // Element -> { rgba, alpha, opacityProduct }
|
|
174
|
+
|
|
175
|
+
// perf: fast-path memo for self-opaque background (common) to avoid ancestor walk
|
|
176
|
+
const __elBgSelfOpaqueCache = __contrastSharedCache
|
|
177
|
+
? __contrastSharedCache.__elBgSelfOpaqueCache ||
|
|
178
|
+
(__contrastSharedCache.__elBgSelfOpaqueCache = new WeakMap())
|
|
179
|
+
: new WeakMap(); // Element -> { ok, rgba, alpha, reasonCode }
|
|
180
|
+
|
|
181
|
+
// Shared deterministic text scan (computed once per run and reused across contrast checks)
|
|
182
|
+
let scan;
|
|
183
|
+
try {
|
|
184
|
+
scan =
|
|
185
|
+
helpers && helpers.contrast && typeof helpers.contrast.getTextScan === 'function'
|
|
186
|
+
? helpers.contrast.getTextScan(ctx, helpers, engineOptions)
|
|
187
|
+
: null;
|
|
188
|
+
} catch {
|
|
189
|
+
scan = null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Walk eligible visible text nodes (counted per text node), but compute blockers/bg/fg once per element.
|
|
193
|
+
if (scan && scan.elements && Array.isArray(scan.elements)) {
|
|
194
|
+
try {
|
|
195
|
+
eligibleTextCount = Number(scan.eligibleTextCount) || 0;
|
|
196
|
+
|
|
197
|
+
for (const rec of scan.elements) {
|
|
198
|
+
// If we're already capped on occurrences and already know the final outcome is cantTell,
|
|
199
|
+
// stop scanning to avoid wasting time. Deterministic: we don't randomize; we just stop
|
|
200
|
+
// once further work cannot change the output (cantTell + capped occurrences).
|
|
201
|
+
if (cantTellCount > 0 && occurrences.length >= MAX_OCCURRENCES) break;
|
|
202
|
+
|
|
203
|
+
const el = rec && rec.el;
|
|
204
|
+
const textCount = rec && Number(rec.textCount) ? Number(rec.textCount) : 0;
|
|
205
|
+
if (!el || textCount <= 0) continue;
|
|
206
|
+
|
|
207
|
+
// 1) Blockers in ancestor chain (blend/filter/bg-image/gradient)
|
|
208
|
+
let blocker = __elBlockerCache.get(el);
|
|
209
|
+
if (!blocker) {
|
|
210
|
+
blocker = helpers.contrast.getComputabilityBlocker(el);
|
|
211
|
+
// Normalize to stable shape
|
|
212
|
+
blocker = blocker || {
|
|
213
|
+
ok: true,
|
|
214
|
+
reasonCode: null,
|
|
215
|
+
blockerSelector: '',
|
|
216
|
+
blockerProperty: '',
|
|
217
|
+
blockerValue: ''
|
|
218
|
+
};
|
|
219
|
+
__elBlockerCache.set(el, blocker);
|
|
220
|
+
}
|
|
221
|
+
if (blocker && blocker.ok === false) {
|
|
222
|
+
cantTellCount += textCount;
|
|
223
|
+
// occurrence is deduped per element (seenFailEls)
|
|
224
|
+
pushCantTellOccurrence(el, blocker.reasonCode, {
|
|
225
|
+
blockerProperty: blocker.blockerProperty,
|
|
226
|
+
blockerValue: blocker.blockerValue,
|
|
227
|
+
blockerSelector: blocker.blockerSelector,
|
|
228
|
+
backgroundFillType: blocker.backgroundFillType
|
|
229
|
+
});
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
177
232
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
233
|
+
// 2) Background resolution (CSS-only), strict vs. reference-engine-compatible mode
|
|
234
|
+
let bg = __elBgCache.get(el);
|
|
235
|
+
if (!bg) {
|
|
236
|
+
// 2a) Fast-path: if the element itself paints an opaque background-color and opacity is 1,
|
|
237
|
+
// then the background behind its text is computable without walking ancestors.
|
|
238
|
+
// This is a strict subset of the full algorithm, so it is behavior-preserving:
|
|
239
|
+
// if it doesn't match, we fall back to computeEffectiveBackground.
|
|
240
|
+
let bgSelfOpaque = __elBgSelfOpaqueCache.get(el);
|
|
241
|
+
if (bgSelfOpaque === undefined) {
|
|
242
|
+
bgSelfOpaque = null;
|
|
243
|
+
try {
|
|
244
|
+
if (
|
|
245
|
+
helpers &&
|
|
246
|
+
typeof helpers.getComputedStyle === 'function' &&
|
|
247
|
+
helpers.contrast &&
|
|
248
|
+
typeof helpers.contrast.parseCssColorToRgba === 'function'
|
|
249
|
+
) {
|
|
250
|
+
const cs = helpers.getComputedStyle(el);
|
|
251
|
+
const op = Number.parseFloat(cs && cs.opacity != null ? cs.opacity : '1');
|
|
252
|
+
// Only treat as self-opaque if opacity is exactly 1 (string '1' or number 1)
|
|
253
|
+
// to avoid float/serialization quirks and preserve determinism.
|
|
254
|
+
if (Number.isFinite(op) && op === 1) {
|
|
255
|
+
const c = helpers.contrast.parseCssColorToRgba(cs && cs.backgroundColor);
|
|
256
|
+
if (c && typeof c.a === 'number' && c.a === 1) {
|
|
257
|
+
bgSelfOpaque = {
|
|
258
|
+
ok: true,
|
|
259
|
+
rgba: { r: c.r, g: c.g, b: c.b, a: 1 },
|
|
260
|
+
alpha: 1,
|
|
261
|
+
reasonCode: null
|
|
204
262
|
};
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
if (blocker && blocker.ok === false) {
|
|
208
|
-
cantTellCount += textCount;
|
|
209
|
-
// occurrence is deduped per element (seenFailEls)
|
|
210
|
-
pushCantTellOccurrence(el, blocker.reasonCode, {
|
|
211
|
-
blockerProperty: blocker.blockerProperty,
|
|
212
|
-
blockerValue: blocker.blockerValue,
|
|
213
|
-
blockerSelector: blocker.blockerSelector,
|
|
214
|
-
backgroundFillType: blocker.backgroundFillType
|
|
215
|
-
});
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// 2) Background resolution (CSS-only), strict vs. reference-engine-compatible mode
|
|
220
|
-
let bg = __elBgCache.get(el);
|
|
221
|
-
if (!bg) {
|
|
222
|
-
// 2a) Fast-path: if the element itself paints an opaque background-color and opacity is 1,
|
|
223
|
-
// then the background behind its text is computable without walking ancestors.
|
|
224
|
-
// This is a strict subset of the full algorithm, so it is behavior-preserving:
|
|
225
|
-
// if it doesn't match, we fall back to computeEffectiveBackground.
|
|
226
|
-
let bgSelfOpaque = __elBgSelfOpaqueCache.get(el);
|
|
227
|
-
if (bgSelfOpaque === undefined) {
|
|
228
|
-
bgSelfOpaque = null;
|
|
229
|
-
try {
|
|
230
|
-
if (helpers && typeof helpers.getComputedStyle === 'function' && helpers.contrast && typeof helpers.contrast.parseCssColorToRgba === 'function') {
|
|
231
|
-
const cs = helpers.getComputedStyle(el);
|
|
232
|
-
const op = Number.parseFloat(cs && cs.opacity != null ? cs.opacity : '1');
|
|
233
|
-
// Only treat as self-opaque if opacity is exactly 1 (string '1' or number 1)
|
|
234
|
-
// to avoid float/serialization quirks and preserve determinism.
|
|
235
|
-
if (Number.isFinite(op) && op === 1) {
|
|
236
|
-
const c = helpers.contrast.parseCssColorToRgba(cs && cs.backgroundColor);
|
|
237
|
-
if (c && typeof c.a === 'number' && c.a === 1) {
|
|
238
|
-
bgSelfOpaque = {
|
|
239
|
-
ok: true,
|
|
240
|
-
rgba: {r: c.r, g: c.g, b: c.b, a: 1},
|
|
241
|
-
alpha: 1,
|
|
242
|
-
reasonCode: null
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
} catch {
|
|
248
|
-
bgSelfOpaque = null;
|
|
249
|
-
}
|
|
250
|
-
__elBgSelfOpaqueCache.set(el, bgSelfOpaque);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (bgSelfOpaque && bgSelfOpaque.ok === true) {
|
|
254
|
-
bg = bgSelfOpaque;
|
|
255
|
-
} else {
|
|
256
|
-
bg = helpers.contrast.computeEffectiveBackground(el, {
|
|
257
|
-
contrast: {mode, rootCanvasFallback},
|
|
258
|
-
collectStack: false
|
|
259
|
-
});
|
|
260
|
-
bg = bg || {ok: false, reasonCode: 'BACKGROUND_NOT_COMPUTABLE', rgba: null, alpha: 0};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
__elBgCache.set(el, bg);
|
|
264
|
-
}
|
|
265
|
-
if (!bg || bg.ok === false) {
|
|
266
|
-
cantTellCount += textCount;
|
|
267
|
-
pushCantTellOccurrence(el, (bg && bg.reasonCode) || 'BACKGROUND_NOT_COMPUTABLE', {
|
|
268
|
-
background: bg && bg.rgba ? helpers.contrast.rgbaToString(bg.rgba) : '',
|
|
269
|
-
backgroundAlpha: bg && typeof bg.alpha === 'number' ? helpers.contrast.round2(bg.alpha) : ''
|
|
270
|
-
});
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Track any explicit assumptions applied by background resolution.
|
|
275
|
-
try {
|
|
276
|
-
if (bg && Array.isArray(bg.assumptionsApplied) && bg.assumptionsApplied.length) {
|
|
277
|
-
assumptionsCount += textCount;
|
|
278
|
-
for (const a of bg.assumptionsApplied) assumptionsAppliedSet.add(String(a));
|
|
279
|
-
}
|
|
280
|
-
} catch { /* no-throw */
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// 3) Foreground parsability (computed color should be rgb/rgba)
|
|
284
|
-
let fg = __elFgCache.get(el);
|
|
285
|
-
if (!fg) {
|
|
286
|
-
fg = helpers.contrast.computeEffectiveForeground(el);
|
|
287
|
-
fg = fg || {rgba: null, alpha: 0, opacityProduct: 1};
|
|
288
|
-
__elFgCache.set(el, fg);
|
|
263
|
+
}
|
|
289
264
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
background: bg && bg.rgba ? helpers.contrast.rgbaToString(bg.rgba) : '',
|
|
294
|
-
backgroundAlpha: bg && typeof bg.alpha === 'number' ? helpers.contrast.round2(bg.alpha) : ''
|
|
295
|
-
});
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// If we got here, this element's eligible text is computable.
|
|
300
|
-
// (We intentionally do not compute contrast ratio in this gatekeeper rule.)
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
bgSelfOpaque = null;
|
|
301
268
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
269
|
+
__elBgSelfOpaqueCache.set(el, bgSelfOpaque);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (bgSelfOpaque && bgSelfOpaque.ok === true) {
|
|
273
|
+
bg = bgSelfOpaque;
|
|
274
|
+
} else {
|
|
275
|
+
bg = helpers.contrast.computeEffectiveBackground(el, {
|
|
276
|
+
contrast: { mode, rootCanvasFallback },
|
|
277
|
+
collectStack: false
|
|
278
|
+
});
|
|
279
|
+
bg = bg || { ok: false, reasonCode: 'BACKGROUND_NOT_COMPUTABLE', rgba: null, alpha: 0 };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
__elBgCache.set(el, bg);
|
|
283
|
+
}
|
|
284
|
+
if (!bg || bg.ok === false) {
|
|
285
|
+
cantTellCount += textCount;
|
|
286
|
+
pushCantTellOccurrence(el, (bg && bg.reasonCode) || 'BACKGROUND_NOT_COMPUTABLE', {
|
|
287
|
+
background: bg && bg.rgba ? helpers.contrast.rgbaToString(bg.rgba) : '',
|
|
288
|
+
backgroundAlpha:
|
|
289
|
+
bg && typeof bg.alpha === 'number' ? helpers.contrast.round2(bg.alpha) : ''
|
|
290
|
+
});
|
|
291
|
+
continue;
|
|
324
292
|
}
|
|
325
|
-
}
|
|
326
293
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
294
|
+
// Track any explicit assumptions applied by background resolution.
|
|
295
|
+
try {
|
|
296
|
+
if (bg && Array.isArray(bg.assumptionsApplied) && bg.assumptionsApplied.length) {
|
|
297
|
+
assumptionsCount += textCount;
|
|
298
|
+
for (const a of bg.assumptionsApplied) assumptionsAppliedSet.add(String(a));
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
/* no-throw */
|
|
302
|
+
}
|
|
336
303
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
304
|
+
// 3) Foreground parsability (computed color should be rgb/rgba)
|
|
305
|
+
let fg = __elFgCache.get(el);
|
|
306
|
+
if (!fg) {
|
|
307
|
+
fg = helpers.contrast.computeEffectiveForeground(el);
|
|
308
|
+
fg = fg || { rgba: null, alpha: 0, opacityProduct: 1 };
|
|
309
|
+
__elFgCache.set(el, fg);
|
|
310
|
+
}
|
|
311
|
+
if (!fg || !fg.rgba) {
|
|
312
|
+
cantTellCount += textCount;
|
|
313
|
+
pushCantTellOccurrence(el, 'FOREGROUND_UNPARSABLE', {
|
|
314
|
+
background: bg && bg.rgba ? helpers.contrast.rgbaToString(bg.rgba) : '',
|
|
315
|
+
backgroundAlpha:
|
|
316
|
+
bg && typeof bg.alpha === 'number' ? helpers.contrast.round2(bg.alpha) : ''
|
|
317
|
+
});
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
346
320
|
|
|
347
|
-
|
|
348
|
-
|
|
321
|
+
// If we got here, this element's eligible text is computable.
|
|
322
|
+
// (We intentionally do not compute contrast ratio in this gatekeeper rule.)
|
|
323
|
+
}
|
|
324
|
+
} catch {
|
|
325
|
+
// If scan processing fails unexpectedly, keep determinism: treat as cantTell with one occurrence
|
|
326
|
+
return {
|
|
349
327
|
ruleId: rule.ruleId,
|
|
350
|
-
outcome: '
|
|
328
|
+
outcome: 'cantTell',
|
|
351
329
|
severity: rule.defaultSeverity || 'serious',
|
|
352
330
|
confidence: rule.defaultConfidence || 'high',
|
|
353
331
|
occurrences: [
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
data: {
|
|
367
|
-
details: {
|
|
368
|
-
eligibleTextCount,
|
|
369
|
-
assumptionsCount,
|
|
370
|
-
assumptionsApplied: Array.from(assumptionsAppliedSet).sort()
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
332
|
+
{
|
|
333
|
+
selector: '',
|
|
334
|
+
summary: '',
|
|
335
|
+
hint: '',
|
|
336
|
+
html: '',
|
|
337
|
+
i18n: {
|
|
338
|
+
summaryKey: 'contrastComputable_cantTell_engineFailure',
|
|
339
|
+
hintKey: '',
|
|
340
|
+
params: { reasonCode: 'ENGINE_EXCEPTION' }
|
|
341
|
+
},
|
|
342
|
+
data: { details: { reasonCode: 'ENGINE_EXCEPTION' } }
|
|
343
|
+
}
|
|
374
344
|
]
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (eligibleTextCount === 0) {
|
|
350
|
+
return {
|
|
351
|
+
ruleId: rule.ruleId,
|
|
352
|
+
outcome: 'notApplicable',
|
|
353
|
+
severity: rule.defaultSeverity || 'serious',
|
|
354
|
+
confidence: rule.defaultConfidence || 'high',
|
|
355
|
+
occurrences: []
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (cantTellCount > 0) {
|
|
360
|
+
return {
|
|
361
|
+
ruleId: rule.ruleId,
|
|
362
|
+
outcome: 'cantTell',
|
|
363
|
+
severity: rule.defaultSeverity || 'serious',
|
|
364
|
+
confidence: rule.defaultConfidence || 'high',
|
|
365
|
+
occurrences
|
|
375
366
|
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// All eligible text is computable
|
|
370
|
+
return {
|
|
371
|
+
ruleId: rule.ruleId,
|
|
372
|
+
outcome: 'pass',
|
|
373
|
+
severity: rule.defaultSeverity || 'serious',
|
|
374
|
+
confidence: rule.defaultConfidence || 'high',
|
|
375
|
+
occurrences: [
|
|
376
|
+
{
|
|
377
|
+
selector: '',
|
|
378
|
+
summary: '',
|
|
379
|
+
hint: '',
|
|
380
|
+
html: '',
|
|
381
|
+
i18n: {
|
|
382
|
+
summaryKey: 'contrastComputable_pass_allComputable',
|
|
383
|
+
hintKey: '',
|
|
384
|
+
params: {
|
|
385
|
+
eligibleTextCount: String(eligibleTextCount)
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
data: {
|
|
389
|
+
details: {
|
|
390
|
+
eligibleTextCount,
|
|
391
|
+
assumptionsCount,
|
|
392
|
+
assumptionsApplied: Array.from(assumptionsAppliedSet).sort()
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
};
|
|
376
398
|
}
|
|
377
399
|
|
|
378
|
-
module.exports = {id, meta, runInPage};
|
|
400
|
+
module.exports = { id, meta, runInPage };
|