Skip to content

Commit 92544e7

Browse files
skumar09Santoshkumar Sharanappa NateekarSantoshkumar Sharanappa Nateekar
authored
[MWPW-171403][Preflight][Due diligence] Integrate full accessibility report in Preflight (#3968)
* initial commit * update review comments * update review comment-2 * update review comments-3 * add tags for color-contrast * consider alt= as decorative images * merge existing img alt feature into new accessibility tab * adust the preflight-full-width css * adust violation-column css bottom padding * updated the tags * update the tags * rm color-contrast * remove console log * include wcag22a and wcag22aa support * comment update --------- Co-authored-by: Santoshkumar Sharanappa Nateekar <[email protected]> Co-authored-by: Santoshkumar Sharanappa Nateekar <[email protected]>
1 parent b3c4bc6 commit 92544e7

13 files changed

+851
-1
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export const AXE_CORE_CONFIG = {
2+
include: [['body']],
3+
exclude: [['.preflight'], ['aem-sidekick'], ['header'], ['.global-navigation'], ['footer'], ['.global-footer'], ['.mep-preview-overlay'], ['preflight-decoration']],
4+
runOnly: {
5+
type: 'tag',
6+
values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22a', 'wcag22aa'],
7+
},
8+
};
9+
10+
export const CUSTOM_CHECKS_CONFIG = {
11+
checks: ['altText', 'color-contrast'],
12+
include: [['body']],
13+
exclude: [['.preflight'], ['aem-sidekick'], ['header'], ['.global-navigation'], ['footer'], ['.global-footer'], ['.mep-preview-overlay'], ['preflight-decoration']],
14+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { getFilteredElements } from './helper.js';
2+
import checkImageAltText from './check-image-alt-text.js';
3+
import checkKeyboardNavigation from './check-keyboard-navigation.js';
4+
5+
const checkFunctions = [
6+
checkImageAltText,
7+
checkKeyboardNavigation,
8+
];
9+
10+
/**
11+
* Runs custom accessibility checks on filtered elements.
12+
* @param {Object} config - custom check config (checks, include, exclude).
13+
* @returns {Array} Violations from all custom checks.
14+
*/
15+
async function customAccessibilityChecks(config = {}) {
16+
try {
17+
// Filter DOM elements based on include/exclude
18+
const elements = getFilteredElements(config.include, config.exclude);
19+
if (!elements.length) return [];
20+
return checkFunctions.flatMap((checkFn) => checkFn(elements, config));
21+
} catch (error) {
22+
window.lana.log(`Error running custom accessibility checks: ${error.message}`);
23+
return [];
24+
}
25+
}
26+
27+
export default customAccessibilityChecks;
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { html, useState, useEffect } from '../../../deps/htm-preact.js';
2+
import '../../../deps/axe.min.js';
3+
import { AXE_CORE_CONFIG, CUSTOM_CHECKS_CONFIG } from './accessibility-config.js';
4+
import customAccessibilityChecks from './accessibility-custom-checks.js';
5+
import AuditImageAltText from './audit-image-alt-text.js';
6+
7+
/**
8+
* Runs the accessibility test using axe-core and custom checks.
9+
* @returns {Promise<Object>} Test results object (pass/fail + violations) or error.
10+
*/
11+
async function runAccessibilityTest() {
12+
try {
13+
const results = await window.axe.run(AXE_CORE_CONFIG);
14+
const customViolations = await customAccessibilityChecks(CUSTOM_CHECKS_CONFIG);
15+
results.violations.push(...customViolations);
16+
return {
17+
pass: !results.violations.length,
18+
violations: results.violations,
19+
};
20+
} catch (error) {
21+
window.lana.log(`Error running accessiblity test:, ${error}`);
22+
return { error: `Error running accessibility test: ${error.message}` };
23+
}
24+
}
25+
26+
/**
27+
* Preflight Accessibility Tab/Panel.
28+
*/
29+
export default function Accessibility() {
30+
const [pageURL, setPageURL] = useState(window.location.href);
31+
const [testResults, setTestResults] = useState(null);
32+
const [loading, setLoading] = useState(false);
33+
const [expandedViolations, setExpandedViolations] = useState([]);
34+
35+
useEffect(() => {
36+
const runTest = async () => {
37+
setLoading(true);
38+
setTestResults(null);
39+
setExpandedViolations([]);
40+
setPageURL(window.location.href);
41+
const results = await runAccessibilityTest();
42+
setTestResults(results);
43+
setLoading(false);
44+
};
45+
runTest();
46+
}, []);
47+
const toggleViolation = (index) => {
48+
setExpandedViolations((prev) => {
49+
const isExpanded = prev.includes(index);
50+
return isExpanded ? prev.filter((i) => i !== index) : [...prev, index];
51+
});
52+
};
53+
// Loading markup
54+
const loadingMarkup = () => html`
55+
<div class="preflight-columns">
56+
<div class="preflight-column">
57+
<p>Running Accessibility Test...</p>
58+
</div>
59+
</div>
60+
`;
61+
// Error markup
62+
const errorMarkup = (errorMsg) => html`
63+
<div class="preflight-columns">
64+
<div class="preflight-column">
65+
<div class="preflight-content-group">
66+
<p class="preflight-item-title">Error</p>
67+
<p class="preflight-item-description">${errorMsg}</p>
68+
</div>
69+
</div>
70+
</div>
71+
`;
72+
// Results summary markup
73+
const resultsSummary = (results, url) => {
74+
if (!results) {
75+
return html`
76+
<div class="preflight-column">
77+
<p>No accessibility results available.</p>
78+
</div>
79+
`;
80+
}
81+
return html`
82+
<div class="preflight-column">
83+
<div class="preflight-content-group">
84+
<div class="preflight-accessibility-item">
85+
<div class="result-icon ${results.pass ? 'green' : 'red'}"></div>
86+
<div class="preflight-item-text">
87+
<p class="preflight-item-title">
88+
${results.pass
89+
? 'Accessibility Test Passed'
90+
: 'Accessibility Test Failed'}
91+
</p>
92+
<p class="preflight-item-description">
93+
${results.pass
94+
? 'No accessibility issues found.'
95+
: `${results.violations.length} accessibility violations detected.`}
96+
</p>
97+
<ul class="summary-list">
98+
<li><strong>Page:</strong> <a href="${url}" target="_blank">${url}</a></li>
99+
<li><strong>Test Scope:</strong> body</li>
100+
<li><strong>WCAG Tags:</strong> ${AXE_CORE_CONFIG.runOnly?.values?.join(', ') || 'NONE'}</li>
101+
</ul>
102+
<p class="preflight-accessibility-note">
103+
<strong>Note:</strong> This test does not include screen reader behavior, focus order, or voice navigation checks.
104+
</p>
105+
</div>
106+
</div>
107+
</div>
108+
</div>
109+
`;
110+
};
111+
// Violations markup
112+
const violationsList = (violations = []) => {
113+
if (!violations.length) {
114+
return html`
115+
<div class="preflight-column">
116+
<p>No violations found.</p>
117+
</div>
118+
`;
119+
}
120+
return html`
121+
<div class="preflight-column">
122+
<div class="preflight-content-group violations-section">
123+
<div class="preflight-accessibility-item">
124+
<div class="result-icon red"></div>
125+
<div class="preflight-item-text">
126+
<p class="preflight-item-title">Accessibility Violations</p>
127+
<p class="preflight-item-description">
128+
Click each violation to view details
129+
</p>
130+
</div>
131+
</div>
132+
133+
<div class="violation-list">
134+
${violations.map((violation, index) => {
135+
const isExpanded = expandedViolations.includes(index);
136+
return html`
137+
<div class="violation-item">
138+
<div
139+
class="violation-summary ${isExpanded ? 'expanded' : ''}"
140+
onClick=${() => toggleViolation(index)}
141+
>
142+
<div class="violation-expand"></div>
143+
<div class="preflight-content-heading">
144+
<span class="violation-index">${index + 1}.</span>
145+
Violation [
146+
<span class="severity ${violation.impact}">${violation.impact}</span>
147+
]: ${violation.description}
148+
</div>
149+
</div>
150+
151+
${isExpanded && html`
152+
<div class="violation-details">
153+
<ul class="violation-details-list">
154+
<li><strong>Rule ID:</strong> ${violation.id}</li>
155+
<li><strong>Severity:</strong> <span class="severity ${violation.impact}">${violation.impact}</span></li>
156+
<li><strong>Fix: </strong>
157+
<a class="violation-link" href="${violation.helpUrl}" target="_blank">
158+
More Info
159+
</a>
160+
</li>
161+
<li><strong>Affected Elements:</strong>
162+
<ul class="affected-elements">
163+
${violation.nodes.map((node) => html`<li><code>${node.html}</code></li>`)}
164+
</ul>
165+
</li>
166+
</ul>
167+
</div>
168+
`}
169+
</div>
170+
`;
171+
})}
172+
</div>
173+
</div>
174+
</div>
175+
`;
176+
};
177+
// Conditional rendering based on state
178+
if (loading) return loadingMarkup();
179+
if (testResults?.error) return errorMarkup(testResults.error);
180+
if (!testResults) {
181+
return html`
182+
<div class="preflight-columns">
183+
<div class="preflight-column">
184+
<p>No accessibility test has been run yet.</p>
185+
</div>
186+
</div>
187+
`;
188+
}
189+
return html`
190+
<div class="preflight-columns accessibility-columns">
191+
${resultsSummary(testResults, pageURL)}
192+
<div class="preflight-column violations-column">
193+
${!testResults.pass && violationsList(testResults.violations)}
194+
</div>
195+
</div>
196+
<div class="preflight-full-width">
197+
<${AuditImageAltText} />
198+
</div>
199+
`;
200+
}

0 commit comments

Comments
 (0)