Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added functionality of incomplete resultType #730

Merged
merged 5 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions packages/assert/__tests__/assert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { assertAccessible, getViolationsJSDOM } from '../src/assert';
import { base, extended, defaultPriority, getA11yConfig } from '@sa11y/preset-rules';
import { assertAccessible, getA11yResults } from '../src/assert';
import { base, extended, defaultPriority, getA11yConfig, adaptA11yConfigIncompleteResults } from '@sa11y/preset-rules';
import {
a11yIssuesCount,
audioURL,
beforeEachSetup,
domWithA11yIncompleteIssues,
domWithA11yIssues,
domWithNoA11yIssues,
shadowDomID,
Expand Down Expand Up @@ -69,10 +70,16 @@ describe('assertAccessible API', () => {

it.each([base, extended])('should throw no errors for dom with no a11y issues with config %#', async (config) => {
document.body.innerHTML = domWithNoA11yIssues;
expect(async () => await getViolationsJSDOM(document, config)).toHaveLength(0);
expect(async () => await getA11yResults(document, config, false)).toHaveLength(0);
await assertAccessible(document, config); // No error thrown
});

it('should throw errors for dom with incomplete issues with base config', async () => {
document.body.innerHTML = domWithA11yIncompleteIssues;
const config = adaptA11yConfigIncompleteResults(base);
expect(await getA11yResults(document, config, true)).toHaveLength(1);
});

it.each([
// DOM to test, expected assertions, expected a11y violations
[domWithNoA11yIssues, 2, 0],
Expand All @@ -82,7 +89,7 @@ describe('assertAccessible API', () => {
async (testDOM: string, expectedAssertions: number, expectedViolations: number) => {
document.body.innerHTML = testDOM;
expect.assertions(expectedAssertions);
await expect(getViolationsJSDOM()).resolves.toHaveLength(expectedViolations);
await expect(getA11yResults()).resolves.toHaveLength(expectedViolations);
if (expectedViolations > 0) {
// eslint-disable-next-line jest/no-conditional-expect
await expect(assertAccessible()).rejects.toThrow(`${expectedViolations} Accessibility issues found`);
Expand Down
17 changes: 14 additions & 3 deletions packages/assert/src/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,30 @@ import { A11yConfig, AxeResults, getViolations } from '@sa11y/common';
*/
export type A11yCheckableContext = Document | Node | string;

export async function getA11yResults(
jaig-0911 marked this conversation as resolved.
Show resolved Hide resolved
context: A11yCheckableContext = document,
rules: A11yConfig = defaultRuleset,
enableIncompleteResults = false
): Promise<AxeResults> {
return enableIncompleteResults
? getViolationsJSDOM(context, rules, 'incomplete')
: getViolationsJSDOM(context, rules, 'violations');
}
/**
* Get list of a11y violations for given element and ruleset
* Get list of a11y issues (violations or incomplete) for given element and ruleset
* @param context - DOM or HTML Node to be tested for accessibility
* @param rules - A11yConfig preset rule to use, defaults to `base` ruleset
* @param reportType - Type of report ('violations' or 'incomplete')
* @returns {@link AxeResults} - list of accessibility issues found
*/
export async function getViolationsJSDOM(
context: A11yCheckableContext = document,
rules: A11yConfig = defaultRuleset
rules: A11yConfig = defaultRuleset,
reportType: 'violations' | 'incomplete' = 'violations'
): Promise<AxeResults> {
return await getViolations(async () => {
const results = await axe.run(context as axe.ElementContext, rules as axe.RunOptions);
return results.violations;
return results[reportType];
});
}

Expand Down
41 changes: 28 additions & 13 deletions packages/browser-lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,50 @@
import * as axe from 'axe-core';
import { exceptionListFilter, appendWcag } from '@sa11y/format';
import { registerCustomRules } from '@sa11y/common';
import { defaultRuleset, changesData, rulesData, checkData } from '@sa11y/preset-rules';
export { base, extended, full } from '@sa11y/preset-rules';
import {
defaultRuleset,
changesData,
rulesData,
checkData,
adaptA11yConfigIncompleteResults,
} from '@sa11y/preset-rules';
export { base, extended, full, adaptA11yConfigIncompleteResults } from '@sa11y/preset-rules';
export const namespace = 'sa11y';

/**
* Wrapper function to check accessibility to be invoked after the sa11y minified JS file
* is injected into the browser
* @param scope - Scope of the analysis as {@link A11yCheckableContext}
* @param rules - preset sa11y rules defaulting to {@link base}
* @param exceptionList - mapping of rule to css selectors to be filtered out using {@link exceptionListFilter}
* is injected into the browser.
*
* @param {Document | HTMLElement} scope - Scope of the analysis, defaults to the document.

Check warning on line 25 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 16.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'

Check warning on line 25 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 18.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'

Check warning on line 25 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 20.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'
* @param {Object} rules - Preset sa11y rules, defaults to {@link base}.

Check warning on line 26 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 16.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'

Check warning on line 26 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 18.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'

Check warning on line 26 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 20.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'
* @param {Object} exceptionList - Mapping of rule to CSS selectors to be filtered out using {@link exceptionListFilter}.

Check warning on line 27 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 16.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'

Check warning on line 27 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 18.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'

Check warning on line 27 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 20.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'
* @param {boolean} addWcagInfo - Flag to add WCAG information to the results, defaults to false.

Check warning on line 28 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 16.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'

Check warning on line 28 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 18.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'

Check warning on line 28 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 20.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'
* @param {boolean} enableIncompleteResults - Flag to include incomplete results in the analysis, defaults to false.

Check warning on line 29 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 16.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'

Check warning on line 29 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 18.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'

Check warning on line 29 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 20.x on ubuntu-latest

tsdoc-param-tag-with-invalid-type: The @param block should not include a JSDoc-style '{type}'
* @returns {Promise<string>} JSON stringified filtered results.

Check warning on line 30 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 16.x on ubuntu-latest

tsdoc-malformed-inline-tag: Expecting a TSDoc tag starting with "{@"

Check warning on line 30 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 16.x on ubuntu-latest

tsdoc-escape-right-brace: The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag

Check warning on line 30 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 18.x on ubuntu-latest

tsdoc-malformed-inline-tag: Expecting a TSDoc tag starting with "{@"

Check warning on line 30 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 18.x on ubuntu-latest

tsdoc-escape-right-brace: The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag

Check warning on line 30 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 20.x on ubuntu-latest

tsdoc-malformed-inline-tag: Expecting a TSDoc tag starting with "{@"

Check warning on line 30 in packages/browser-lib/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, Test - Node 20.x on ubuntu-latest

tsdoc-escape-right-brace: The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export async function checkAccessibility(
scope = document,
rules = defaultRuleset,
exceptionList = {},
addWcagInfo = true
addWcagInfo = true,
reportType: 'violations' | 'incomplete' = 'violations'
) {
// TODO (debug): adding type annotations to args, return type results in error:
// "[!] Error: Unexpected token" in both rollup-plugin-typescript2 and @rollup/plugin-typescript
// Compiling the index.ts file with tsc and using the dist/index.js file results
// in error when importing the "namespace" var. This would probably be easier to fix
// which could then result in getting rid of the rollup typescript plugins.
// TODO (debug): Adding type annotations to arguments and return type results in error:
// "[!] Error: Unexpected token" in both rollup-plugin-typescript2 and @rollup/plugin-typescript.
// Compiling index.ts with tsc and using the dist/index.js file results in an error when importing
// the "namespace" variable. This would probably be easier to fix, potentially allowing us to
// remove the rollup TypeScript plugins.

// To register custom rules
registerCustomRules(changesData, rulesData, checkData);

// Adapt rules if incomplete results are enabled
if (reportType === 'incomplete') {
rules = adaptA11yConfigIncompleteResults(rules);
}
const results = await axe.run(scope || document, rules);
const filteredResults = exceptionListFilter(results.violations, exceptionList);
const filteredResults = exceptionListFilter(results[reportType], exceptionList);

if (addWcagInfo) {
appendWcag(filteredResults);
Expand Down
9 changes: 6 additions & 3 deletions packages/common/src/axe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@
*/

import * as axe from 'axe-core';
import { RuleMetadata } from 'axe-core';
import { resultGroups, RuleMetadata } from 'axe-core';

export const axeRuntimeExceptionMsgPrefix = 'Error running accessibility checks using axe:';

export const axeVersion: string | undefined = axe.version;

export type AxeResults = axe.Result[];
export type AxeResults = axe.Result[] | axeIncompleteResults[];

/**
* Interface that represents a function that runs axe and returns violations
*/
interface AxeRunner {
(): Promise<AxeResults>;
}
export interface axeIncompleteResults extends axe.Result {
message?: string;
}

/**
* A11yConfig defines options to run accessibility checks using axe specifying list of rules to test
Expand All @@ -29,7 +32,7 @@ export interface A11yConfig extends axe.RunOptions {
type: 'rule';
values: string[];
};
resultTypes: ['violations'];
resultTypes: resultGroups[];
}

/**
Expand Down
10 changes: 9 additions & 1 deletion packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

export { A11yConfig, AxeResults, axeRuntimeExceptionMsgPrefix, axeVersion, getAxeRules, getViolations } from './axe';
export {
A11yConfig,
AxeResults,
axeIncompleteResults,
axeRuntimeExceptionMsgPrefix,
axeVersion,
getAxeRules,
getViolations,
} from './axe';
export { WdioAssertFunction, WdioOptions } from './wdio';
export { errMsgHeader, ExceptionList } from './format';
export { log, useFilesToBeExempted, useCustomRules, processFiles, registerCustomRules } from './helpers';
9 changes: 9 additions & 0 deletions packages/format/__tests__/result.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ describe('a11y result', () => {
expect(A11yResults.add(a11yResults, key)).toHaveLength(a11yResults.length);
expect(A11yResults.add(a11yResults, key)).toHaveLength(0);
});
it('should handle empty nodes violations', () => {
const violationWithoutNodes: AxeResults = [];
violations.forEach((violation) => {
violation.nodes = [];
violationWithoutNodes.push(violation);
});
a11yResults = A11yResults.convert(violationWithoutNodes);
expect(a11yResults).toHaveLength(violationWithoutNodes.length);
});
});

describe('appendWcag', () => {
Expand Down
17 changes: 15 additions & 2 deletions packages/format/src/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { AxeResults } from '@sa11y/common';
import { AxeResults, axeIncompleteResults } from '@sa11y/common';
import { NodeResult, Result, CheckResult } from 'axe-core';
import { priorities, wcagLevels, WcagMetadata } from '@sa11y/preset-rules';

Expand Down Expand Up @@ -61,6 +61,17 @@ export class A11yResults {
*/
static convert(violations: AxeResults): A11yResult[] {
return A11yResults.sort(violations).flatMap((violation) => {
if (!violation.nodes?.length) {
// Handle case where nodes are empty by creating a default A11yResult
const emptyNodeResult: NodeResult = {
html: '',
target: [],
any: [],
all: [],
none: [],
};
return [new A11yResult(violation, emptyNodeResult)];
}
return violation.nodes.map((node) => {
return new A11yResult(violation, node);
});
Expand Down Expand Up @@ -92,8 +103,9 @@ export class A11yResult {
public readonly relatedNodeAll: string;
public readonly relatedNodeNone: string;
private readonly wcagData: WcagMetadata; // Used to sort results
public readonly message: string | undefined;

constructor(violation: Result, node: NodeResult) {
constructor(violation: Result | axeIncompleteResults, node: NodeResult) {
this.id = violation.id;
this.description = violation.description;
this.wcagData = new WcagMetadata(violation);
Expand All @@ -112,6 +124,7 @@ export class A11yResult {
this.relatedNodeAny = this.formatRelatedNodes(node.any);
this.relatedNodeAll = this.formatRelatedNodes(node.all);
this.relatedNodeNone = this.formatRelatedNodes(node.none);
if ('message' in violation) this.message = violation?.message;
}

/**
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions packages/jest/__tests__/automatic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
domWithDescendancyA11yIssues,
customRulesFilePath,
domWithA11yCustomIssues,
domWithA11yIncompleteIssues,
} from '@sa11y/test-utils';
import * as Sa11yCommon from '@sa11y/common';
import { expect, jest } from '@jest/globals';
Expand Down Expand Up @@ -208,6 +209,16 @@ describe('automatic checks call', () => {
await expect(automaticCheck({ filesFilter: nonExistentFilePaths })).rejects.toThrow();
});

it('should incomplete rules', async () => {
setup();
document.body.innerHTML = domWithA11yIncompleteIssues;
process.env.SA11Y_CUSTOM_RULES = customRulesFilePath;
await expect(
automaticCheck({ cleanupAfterEach: true, enableIncompleteResults: true, consolidateResults: true })
).rejects.toThrow('1 Accessibility');
delete process.env.SA11Y_CUSTOM_RULES;
});

it('should take only custom rules if specified/testing for new rule', async () => {
setup();
document.body.innerHTML = domWithA11yCustomIssues;
Expand Down
39 changes: 38 additions & 1 deletion packages/jest/__tests__/groupViolationResultsProcessor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import { A11yError, A11yResult } from '@sa11y/format';
import { getA11yError } from '@sa11y/format/__tests__/format.test';
import { domWithVisualA11yIssues } from '@sa11y/test-utils';
import { expect } from '@jest/globals';
import { resultsProcessor, resultsProcessorManualChecks } from '../src/groupViolationResultsProcessor';
import {
resultsProcessor,
resultsProcessorManualChecks,
ErrorElement,
createA11yErrorElements,
} from '../src/groupViolationResultsProcessor';

const a11yResults: A11yResult[] = [];
const aggregatedResults = makeEmptyAggregatedTestResult();
Expand Down Expand Up @@ -83,4 +88,36 @@ describe('Group Violation Results Processor', () => {
expect(processedResults).not.toEqual(aggregatedResults);
expect(processedResults.numFailedTestSuites).toBe(1);
});

it('should process error Elements as expected', () => {
const errorElements: ErrorElement[] = [
{
html: '<div role="button" tabindex="0">Click me</div>',
selectors: '.button',
hierarchy: 'body > div.button',
any: 'role button is interactive',
all: 'element should be focusable and have a click handler',
none: 'no color contrast issues',
relatedNodeAny: 'none',
relatedNodeAll: 'none',
relatedNodeNone: 'none',
message: 'Ensure the element has a valid interactive role and behavior.',
},
{
html: '<img src="image.jpg" alt="">',
selectors: 'img',
hierarchy: 'body > img',
all: 'image elements must have an alt attribute',
none: 'no missing alt attribute allowed',
relatedNodeAny: 'none',
relatedNodeAll: 'none',
relatedNodeNone: 'none',
message: 'Add an appropriate alt attribute describing the image.',
any: '',
},
];

const createdA11yErrorElements = createA11yErrorElements(errorElements);
expect(createdA11yErrorElements).toMatchSnapshot();
});
});
Loading
Loading