@storybook/addon-a11y
Version:
Storybook Addon A11y: Test UI component compliance with WCAG web accessibility standards
187 lines (178 loc) • 7.07 kB
JavaScript
import {
EVENTS,
PANEL_ID,
filterDefs,
filters
} from "./chunk-7OPMK7TU.js";
import {
__export
} from "./chunk-4BE7D4DS.js";
// src/preview.tsx
var preview_exports = {};
__export(preview_exports, {
afterEach: () => afterEach,
decorators: () => decorators,
initialGlobals: () => initialGlobals,
parameters: () => parameters
});
import { expect } from "storybook/test";
// src/a11yRunner.ts
import { ElementA11yParameterError } from "storybook/internal/preview-errors";
import { global as global2 } from "@storybook/global";
import { addons, waitForAnimations } from "storybook/preview-api";
// src/a11yRunnerUtils.ts
import { global } from "@storybook/global";
var { document: document2 } = global, withLinkPaths = (results, storyId) => {
let pathname = document2.location.pathname.replace(/iframe\.html$/, ""), enhancedResults = { ...results };
return ["incomplete", "passes", "violations"].forEach((key) => {
Array.isArray(results[key]) && (enhancedResults[key] = results[key].map((result) => ({
...result,
nodes: result.nodes.map((node, index) => {
let id = `${key}.${result.id}.${index + 1}`, linkPath = `${pathname}?path=/story/${storyId}&addonPanel=${PANEL_ID}&a11ySelection=${id}`;
return { id, ...node, linkPath };
})
})));
}), enhancedResults;
};
// src/a11yRunner.ts
var { document: document3 } = global2, channel = addons.getChannel(), DEFAULT_PARAMETERS = { config: {}, options: {} }, DISABLED_RULES = [
// In component testing, landmarks are not always present
// and the rule check can cause false positives
"region"
], queue = [], isRunning = !1, runNext = async () => {
if (queue.length === 0) {
isRunning = !1;
return;
}
isRunning = !0;
let next = queue.shift();
next && await next(), runNext();
}, run = async (input = DEFAULT_PARAMETERS, storyId) => {
let axe = (await import("axe-core"))?.default || globalThis.axe, { config = {}, options = {} } = input;
if (input.element)
throw new ElementA11yParameterError();
let context = {
include: document3?.body,
exclude: [".sb-wrapper", "#storybook-docs", "#storybook-highlights-root"]
// Internal Storybook elements that are always in the document
};
if (input.context) {
let hasInclude = typeof input.context == "object" && "include" in input.context && input.context.include !== void 0, hasExclude = typeof input.context == "object" && "exclude" in input.context && input.context.exclude !== void 0;
hasInclude ? context.include = input.context.include : !hasInclude && !hasExclude && (context.include = input.context), hasExclude && (context.exclude = context.exclude.concat(input.context.exclude));
}
axe.reset();
let configWithDefault = {
...config,
rules: [...DISABLED_RULES.map((id) => ({ id, enabled: !1 })), ...config?.rules ?? []]
};
return axe.configure(configWithDefault), new Promise((resolve, reject) => {
let highlightsRoot = document3?.getElementById("storybook-highlights-root");
highlightsRoot && (highlightsRoot.style.display = "none");
let task = async () => {
try {
let result = await axe.run(context, options), resultWithLinks = withLinkPaths(result, storyId);
resolve(resultWithLinks);
} catch (error) {
reject(error);
}
};
queue.push(task), isRunning || runNext(), highlightsRoot && (highlightsRoot.style.display = "");
});
};
channel.on(EVENTS.MANUAL, async (storyId, input = DEFAULT_PARAMETERS) => {
try {
await waitForAnimations();
let result = await run(input, storyId), resultJson = JSON.parse(JSON.stringify(result));
channel.emit(EVENTS.RESULT, resultJson, storyId);
} catch (error) {
channel.emit(EVENTS.ERROR, error);
}
});
// src/utils.ts
function getIsVitestStandaloneRun() {
try {
return import.meta.env.VITEST_STORYBOOK === "false";
} catch {
return !1;
}
}
// src/withVisionSimulator.ts
import { useCallback, useEffect } from "storybook/preview-api";
var knownFilters = Object.values(filters).map((f) => f.filter), knownFiltersRegExp = new RegExp(`\\b(${knownFilters.join("|")})\\b`, "g"), withVisionSimulator = (StoryFn, { globals }) => {
let { vision } = globals, applyVisionFilter = useCallback(() => {
let existingFilters = document.body.style.filter.replaceAll(knownFiltersRegExp, "").trim(), visionFilter = filters[vision]?.filter;
return visionFilter && document.body.classList.contains("sb-show-main") ? !existingFilters || existingFilters === "none" ? document.body.style.filter = visionFilter : document.body.style.filter = `${existingFilters} ${visionFilter}` : document.body.style.filter = existingFilters || "none", () => document.body.style.filter = existingFilters || "none";
}, [vision]);
return useEffect(() => {
let cleanup = applyVisionFilter(), observer = new MutationObserver(() => applyVisionFilter());
return observer.observe(document.body, { attributeFilter: ["class"] }), () => {
cleanup(), observer.disconnect();
};
}, [applyVisionFilter]), useEffect(() => (document.body.insertAdjacentHTML("beforeend", filterDefs), () => {
let filterDefsElement = document.getElementById("storybook-a11y-vision-filters");
filterDefsElement?.parentElement?.removeChild(filterDefsElement);
}), []), StoryFn();
};
// src/preview.tsx
var vitestMatchersExtended = !1, decorators = [withVisionSimulator], afterEach = async ({
id: storyId,
reporting,
parameters: parameters2,
globals,
viewMode
}) => {
let a11yParameter = parameters2.a11y, a11yGlobals = globals.a11y, shouldRunEnvironmentIndependent = !!!globals.ghostStories && a11yParameter?.disable !== !0 && a11yParameter?.test !== "off" && a11yGlobals?.manual !== !0, getMode = () => {
switch (a11yParameter?.test) {
case "todo":
return "warning";
case "error":
default:
return "failed";
}
};
if (shouldRunEnvironmentIndependent && viewMode === "story")
try {
let result = await run(a11yParameter, storyId);
if (result) {
let hasViolations = (result?.violations.length ?? 0) > 0;
if (reporting.addReport({
type: "a11y",
version: 1,
result,
status: hasViolations ? getMode() : "passed"
}), getIsVitestStandaloneRun() && hasViolations && getMode() === "failed") {
if (!vitestMatchersExtended) {
let { toHaveNoViolations } = await import("./matchers-5TDFFDYO.js");
expect.extend({ toHaveNoViolations }), vitestMatchersExtended = !0;
}
expect(result).toHaveNoViolations();
}
}
} catch (e) {
if (reporting.addReport({
type: "a11y",
version: 1,
result: {
error: e
},
status: "failed"
}), getIsVitestStandaloneRun())
throw e;
}
}, initialGlobals = {
a11y: {
manual: !1
},
vision: void 0
}, parameters = {
a11y: {
test: "todo"
}
};
export {
decorators,
afterEach,
initialGlobals,
parameters,
preview_exports
};