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 for jest custom ruleset #630

Merged
merged 5 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"wcag",
"wdio",
"webdriverio",
"webm"
"webm",
"Vidyard"
navateja-alagam marked this conversation as resolved.
Show resolved Hide resolved
],
"flagWords": ["master-slave", "slave", "blacklist", "whitelist"],
"allowCompoundWords": true
Expand Down
21 changes: 20 additions & 1 deletion packages/common/__tests__/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { useFilesToBeExempted, log } from '../src/helpers';
import { useFilesToBeExempted, log, useCustomRules } from '../src/helpers';

describe('Your Module', () => {
afterEach(() => {
Expand Down Expand Up @@ -66,4 +66,23 @@ describe('Your Module', () => {

delete process.env.SA11Y_AUTO_FILTER_LIST_PACKAGE_NAME;
});

it('should return empty array if SA11Y_CUSTOM_RULES env variable is not set', async () => {
process.env.SA11Y_CUSTOM_RULES = '';
const result = await useCustomRules();
expect(result).toEqual([]);
});

it('should return empty array if reading file encounters an error', async () => {
process.env.SA11Y_CUSTOM_RULES = 'packages/common/testMocks/WrongFile.json';
const result = await useCustomRules();
expect(result).toEqual([]);
});

it('should return rules array if file is read successfully', async () => {
process.env.SA11Y_CUSTOM_RULES = 'packages/common/testMocks/sa11y-custom-rules.json';
const mockRules = ['rule1', 'rule2'];
const result = await useCustomRules();
expect(result).toEqual(mockRules);
});
});
17 changes: 17 additions & 0 deletions packages/common/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* Logging is enabled only when environment variable `SA11Y_DEBUG` is set.
*/

import * as fs from 'fs/promises';
export function log(...args: unknown[]): void {
// Probably not worth it to mock and test console logging for this helper util
/* istanbul ignore next */
Expand All @@ -32,3 +33,19 @@ export function useFilesToBeExempted(): string[] {
}
return [];
}

export async function useCustomRules(): Promise<string[]> {
const filePath = process.env.SA11Y_CUSTOM_RULES ?? '';
if (filePath !== '') {
try {
// Read the file asynchronously
const data = await fs.readFile(filePath, 'utf-8');
const { rules } = JSON.parse(data) as { rules: string[] };
// Access the rules array
return rules;
} catch (err) {
console.error('Error reading the custom ruleset file:', err);
}
}
return [];
}
2 changes: 1 addition & 1 deletion packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
export { A11yConfig, AxeResults, axeRuntimeExceptionMsgPrefix, axeVersion, getAxeRules, getViolations } from './axe';
export { WdioAssertFunction, WdioOptions } from './wdio';
export { errMsgHeader, ExceptionList } from './format';
export { log, useFilesToBeExempted } from './helpers';
export { log, useFilesToBeExempted, useCustomRules } from './helpers';
3 changes: 3 additions & 0 deletions packages/common/testMocks/sa11y-custom-rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"rules": ["rule1", "rule2"]
}
8 changes: 8 additions & 0 deletions packages/jest/__tests__/automatic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
domWithNoA11yIssues,
domWithNoA11yIssuesChildCount,
domWithDescendancyA11yIssues,
customRulesFilePath,
} from '@sa11y/test-utils';
import * as Sa11yCommon from '@sa11y/common';
import { expect, jest } from '@jest/globals';
Expand Down Expand Up @@ -203,4 +204,11 @@ describe('automatic checks call', () => {
document.body.innerHTML = domWithA11yIssues;
await expect(automaticCheck({ filesFilter: nonExistentFilePaths })).rejects.toThrow();
});

it('should take only custom rules if specified', async () => {
document.body.innerHTML = domWithA11yIssues;
process.env.SA11Y_CUSTOM_RULES = customRulesFilePath;
await expect(automaticCheck({ cleanupAfterEach: true })).rejects.toThrow('1 Accessibility');
delete process.env.SA11Y_CUSTOM_RULES;
});
});
11 changes: 10 additions & 1 deletion packages/jest/__tests__/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { adaptA11yConfig, registerSa11yMatcher } from '../src/setup';
import { adaptA11yConfig, adaptA11yConfigCustomRules, registerSa11yMatcher } from '../src/setup';
import { base, extended } from '@sa11y/preset-rules';
import { expect, jest } from '@jest/globals';

Expand All @@ -21,9 +21,18 @@
// original ruleset is not modified
expect(config.runOnly.values).toContain('color-contrast');
});
it('should customize preset-rule as expected for custom rules', () => {
expect(base.runOnly.values).toContain('color-contrast');
const rules = adaptA11yConfigCustomRules(base, ['rule1', 'rule2']).runOnly.values;
expect(rules).toContain('rule1');
expect(rules).toContain('rule2');

// original ruleset is not modified
expect(base.runOnly.values).toContain('color-contrast');
});

/* Skipped: Difficult to mock the global "expect" when we are `import {expect} from '@jest/globals'` */
it.skip('should throw error when global expect is undefined', () => {

Check warning on line 35 in packages/jest/__tests__/setup.test.ts

View workflow job for this annotation

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

Disabled test

Check warning on line 35 in packages/jest/__tests__/setup.test.ts

View workflow job for this annotation

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

Disabled test
const globalExpect = expect as jest.Expect;
expect(globalExpect).toBeDefined();
expect(registerSa11yMatcher).not.toThrow();
Expand Down
12 changes: 9 additions & 3 deletions packages/jest/src/automatic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { AxeResults, log } from '@sa11y/common';
import { AxeResults, log, useCustomRules } from '@sa11y/common';
import { getViolationsJSDOM } from '@sa11y/assert';
import { A11yError } from '@sa11y/format';
import { isTestUsingFakeTimer } from './matcher';
import { expect } from '@jest/globals';
import { adaptA11yConfig } from './setup';
import { adaptA11yConfig, adaptA11yConfigCustomRules } from './setup';
import { defaultRuleset } from '@sa11y/preset-rules';

/**
Expand Down Expand Up @@ -82,6 +82,7 @@ export async function automaticCheck(opts: AutoCheckOpts = defaultAutoCheckOpts)
// Create a DOM walker filtering only elements (skipping text, comment nodes etc)
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
let currNode = walker.firstChild();
const customRules = await useCustomRules();
try {
while (currNode !== null) {
// TODO (spike): Use a logger lib with log levels selectable at runtime
Expand All @@ -91,7 +92,12 @@ export async function automaticCheck(opts: AutoCheckOpts = defaultAutoCheckOpts)
// : ${testPath}`
// );
// W-10004832 - Exclude descendancy based rules from automatic checks
violations.push(...(await getViolationsJSDOM(currNode, adaptA11yConfig(defaultRuleset))));
if (customRules.length === 0)
violations.push(...(await getViolationsJSDOM(currNode, adaptA11yConfig(defaultRuleset))));
else
violations.push(
...(await getViolationsJSDOM(currNode, adaptA11yConfigCustomRules(defaultRuleset, customRules)))
);
currNode = walker.nextSibling();
}
} finally {
Expand Down
7 changes: 5 additions & 2 deletions packages/jest/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@
]);
registerSa11yAutomaticChecks(autoCheckOpts);
}

/**
* Register accessibility helpers toBeAccessible as jest matchers
*/
Expand All @@ -141,10 +140,14 @@
);
}
}

/**
* Customize sa11y preset rules specific to JSDOM
*/
export function adaptA11yConfigCustomRules(config: A11yConfig, customRules: string[]): A11yConfig {
const adaptedConfig = JSON.parse(JSON.stringify(config)) as A11yConfig;
adaptedConfig.runOnly.values = customRules;
return adaptedConfig;

Check warning on line 149 in packages/jest/src/setup.ts

View check run for this annotation

Codecov / codecov/patch

packages/jest/src/setup.ts#L148-L149

Added lines #L148 - L149 were not covered by tests
}
export function adaptA11yConfig(config: A11yConfig, filterRules = disabledRules): A11yConfig {
// TODO (refactor): Move into preset-rules pkg as a generic rules filter util
const adaptedConfig = JSON.parse(JSON.stringify(config)) as A11yConfig;
Expand Down
3 changes: 3 additions & 0 deletions packages/test-utils/__data__/sa11y-custom-rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"rules": ["link-name"]
}
1 change: 1 addition & 0 deletions packages/test-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ export {
a11yIssuesCountFiltered,
shadowDomID,
videoURL,
customRulesFilePath,
} from './test-data';
export { beforeEachSetup, cartesianProduct } from './utils';
1 change: 1 addition & 0 deletions packages/test-utils/src/test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const dataDir = path.resolve(__dirname, '../__data__/');
// DOM with a11y issues
export const domWithA11yIssuesBodyID = 'dom-with-issues';
const fileWithA11yIssues = path.resolve(dataDir, 'a11yIssues.html');
export const customRulesFilePath = path.resolve(dataDir, 'sa11y-custom-rules.json');
const fileWithDescendancyA11yIssues = path.resolve(dataDir, 'descendancyA11yIssues.html');
export const htmlFileWithA11yIssues = 'file:///' + fileWithA11yIssues;
export const domWithA11yIssues = fs.readFileSync(fileWithA11yIssues).toString();
Expand Down
Loading