UNPKG

ember-a11y-testing

Version:
187 lines (159 loc) 4.92 kB
import QUnit from 'qunit'; import { currentRouteName, currentURL, getContext, getTestMetadata, } from '@ember/test-helpers'; import type { AxeResults, Result } from 'axe-core'; import { setCustomReporter } from './reporter'; import { DEBUG } from '@glimmer/env'; import { setEnableA11yAudit } from './should-force-audit'; export interface TestMetadata { testName?: string; setupTypes: string[]; usedHelpers: string[]; [key: string]: any; readonly isRendering: boolean; readonly isApplication: boolean; } interface AxeTestResult { moduleName: string; testName: string; testMetaData: TestMetadata; urls: string[] | Set<string>; routes: string[] | Set<string>; helpers: string[]; stack: string; violations: Result[]; } export const TEST_SUITE_RESULTS: AxeTestResult[] = []; let currentTestResult: AxeTestResult | undefined = undefined; let currentViolationsMap: Map<string, Result> | undefined = undefined; let currentUrls: Set<string> | undefined; let currentRouteNames: Set<string> | undefined; /** * Utility to retrieve the route name corresponding to the current test. Absorbs the emitted * assertion error if route name is `null`, resulting in an empty string return value. * * @param getFn Function to use to derive the route name. * @returns Route name or empty string. */ export function _getCurrentRouteName(getFn = currentRouteName): string { let routeName = ''; try { routeName = getFn(); } catch (error: unknown) { if ( error instanceof Error && !/currentRouteName (\w|\s)+ string/.test(error.message) ) { throw error; } } return routeName; } /** * A custom reporter that is invoked once per failed a11yAudit call. This can be called * multiple times per test, and the results are accumulated until testDone. * * @param axeResults The axe results for each a11yAudit. * @returns Early returns if no violations are found. */ export async function middlewareReporter(axeResults: AxeResults) { if (axeResults.violations.length === 0) { return; } const context = getContext(); if (!currentTestResult) { let { module, testName } = QUnit.config.current; if (!context) { throw new Error( 'You tried to run ember-a11y-testing without calling one of the `setupTest` helpers from `@ember/test-helpers`. Please make sure your test setup calls `setupTest()`, `setupRenderingTest()`, or `setupApplicationTest()`!', ); } let testMetaData = getTestMetadata(context); let stack = (!DEBUG && new Error().stack) || ''; currentTestResult = { moduleName: module.name, testName, testMetaData, urls: [], routes: [], helpers: [], stack, violations: [], }; currentViolationsMap = new Map(); currentUrls = new Set(); currentRouteNames = new Set(); } if (context) { currentUrls!.add(currentURL()); currentRouteNames!.add(_getCurrentRouteName()); } axeResults.violations.forEach((violation) => { let rule = currentViolationsMap!.get(violation.id); if (rule === undefined) { currentViolationsMap!.set(violation.id, violation); } else { rule.nodes.push(...violation.nodes); } }); } /** * Invoked once per test. Accumulates the results into a set of results used for * reporting via the middleware reporter. */ export function pushTestResult() { if (typeof currentTestResult !== 'undefined') { currentTestResult.violations = [...currentViolationsMap!.values()]; currentTestResult.urls = [...currentUrls!.values()]; currentTestResult.routes = [...currentRouteNames!.values()]; currentTestResult.helpers = currentTestResult.testMetaData.usedHelpers; TEST_SUITE_RESULTS.push(currentTestResult); currentTestResult = undefined; currentViolationsMap = undefined; currentUrls = undefined; currentRouteNames = undefined; } } type TestemCallback = (config: any, data: any, callback: () => void) => void; declare global { interface Window { Testem?: { afterTests: (callback: TestemCallback) => void; }; } } /** * Sets up the middleware reporter, which reports results when the test suite is done. */ export function setupMiddlewareReporter() { setCustomReporter(middlewareReporter); setEnableA11yAudit(true); QUnit.testDone(pushTestResult); if (window.Testem) { window.Testem.afterTests(async function (_config, _data, callback) { try { await sendViolationsToServer(); } finally { callback(); } }); } else { QUnit.done(async function () { await sendViolationsToServer(); }); } } async function sendViolationsToServer() { let response = await fetch('/report-violations', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(TEST_SUITE_RESULTS), }); return response.json(); }