qa: flag empty alt (not just missing) — decorative-only exemption removed
An empty alt is valid only for purely decorative images; a content image (photo/cover/screenshot) with empty alt is a real a11y gap. QA now surfaces empty and missing alt as distinct findings.
This commit is contained in:
parent
18db0de37b
commit
182b1485ca
1 changed files with 22 additions and 10 deletions
|
|
@ -638,15 +638,22 @@ const qaNorm = (p) => { p = String(p).split("#")[0].split("?")[0]; if (p.length
|
|||
const qaAbs = (href, pagePath) => { try { return new URL(href, QA_BASE + pagePath).href; } catch { return null; } };
|
||||
const qaGrabAll = (re, html) => [...String(html).matchAll(re)].map((m) => m[1]);
|
||||
const qaFirst = (re, html) => { const m = String(html).match(re); return m ? m[1].trim() : ""; };
|
||||
function qaImgsNoAlt(html) {
|
||||
// An image "has alt" if it carries an alt attribute at all — including an
|
||||
// empty one (alt="" or a bare `alt`), which is the valid, deliberate signal
|
||||
// for a decorative image. Only a total absence of alt is a finding.
|
||||
const hasAlt = (tag) => /\salt(\s*=|[\s>\/])/i.test(tag);
|
||||
return [...String(html).matchAll(/<img\b[^>]*>/gi)].map((m) => m[0])
|
||||
.filter((tag) => !hasAlt(tag))
|
||||
.map((tag) => (tag.match(/\bsrc=["']([^"']+)["']/i) || [])[1])
|
||||
.filter(Boolean);
|
||||
// Classify each <img>: alt missing entirely, or present-but-empty (alt="" or a
|
||||
// bare `alt`). Both are surfaced — an empty alt is only correct for a purely
|
||||
// decorative image, so a content image (photo, cover, screenshot) with empty
|
||||
// alt is a real accessibility gap, not a pass.
|
||||
function qaAltIssues(html) {
|
||||
const out = [];
|
||||
for (const m of String(html).matchAll(/<img\b[^>]*>/gi)) {
|
||||
const tag = m[0];
|
||||
const src = (tag.match(/\bsrc=["']([^"']+)["']/i) || [])[1];
|
||||
if (!src) continue;
|
||||
const withVal = tag.match(/\salt\s*=\s*["']([^"']*)["']/i);
|
||||
if (withVal) { if (withVal[1].trim() === "") out.push({ src, kind: "empty" }); }
|
||||
else if (/\salt(\s|>|\/)/i.test(tag)) out.push({ src, kind: "empty" }); // bare `alt`
|
||||
else out.push({ src, kind: "missing" });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async function runQa() {
|
||||
|
|
@ -693,7 +700,12 @@ async function runQa() {
|
|||
else if (/^https?:\/\//i.test(noHash) && !external.has(noHash)) external.set(noHash, path);
|
||||
}
|
||||
for (const src of qaGrabAll(/<img\b[^>]*\bsrc=["']([^"']+)["']/gi, html)) { const u = qaAbs(src, path); if (u && /^https?:/i.test(u)) images.add(u.split("#")[0]); }
|
||||
for (const src of qaImgsNoAlt(html)) add("a11y", "warning", path, `Image without alt: ${src}`, `On ${path}, the image "${src}" has no alt text. Add descriptive alt text.`);
|
||||
for (const a of qaAltIssues(html)) {
|
||||
if (a.kind === "empty") add("a11y", "warning", path, `Empty alt: ${a.src}`,
|
||||
`On ${path}, the image "${a.src}" has an empty alt attribute. If it conveys meaning (a photo, cover, or screenshot), add descriptive alt text that explains what it shows; leave it empty only if it is purely decorative.`);
|
||||
else add("a11y", "warning", path, `Missing alt: ${a.src}`,
|
||||
`On ${path}, the image "${a.src}" has no alt attribute. Add descriptive alt text that explains what it shows.`);
|
||||
}
|
||||
}
|
||||
|
||||
// duplicate titles across pages
|
||||
|
|
|
|||
Loading…
Reference in a new issue