From 3e9639864e77cf4eaa90769c000c8386f27d6e01 Mon Sep 17 00:00:00 2001 From: jaig-0911 <63843872+jaig-0911@users.noreply.github.com> Date: Mon, 22 Jan 2024 10:52:33 +0530 Subject: [PATCH] feat: added functionality for jest custom ruleset (#630) * feat: added functionality for jest custom ruleset * fix: changed coverage * fix: review added * fix: reading custom rules file async --------- Co-authored-by: Navateja Alagam --- cSpell.json | 3 ++- packages/common/__tests__/helpers.test.ts | 21 ++++++++++++++++++- packages/common/src/helpers.ts | 17 +++++++++++++++ packages/common/src/index.ts | 2 +- .../common/testMocks/sa11y-custom-rules.json | 3 +++ packages/jest/__tests__/automatic.test.ts | 8 +++++++ packages/jest/__tests__/setup.test.ts | 11 +++++++++- packages/jest/src/automatic.ts | 12 ++++++++--- packages/jest/src/setup.ts | 7 +++++-- .../__data__/sa11y-custom-rules.json | 3 +++ packages/test-utils/src/index.ts | 1 + packages/test-utils/src/test-data.ts | 1 + 12 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 packages/common/testMocks/sa11y-custom-rules.json create mode 100644 packages/test-utils/__data__/sa11y-custom-rules.json diff --git a/cSpell.json b/cSpell.json index d80d9475..4d58492a 100644 --- a/cSpell.json +++ b/cSpell.json @@ -14,7 +14,8 @@ "wcag", "wdio", "webdriverio", - "webm" + "webm", + "Vidyard" ], "flagWords": ["master-slave", "slave", "blacklist", "whitelist"], "allowCompoundWords": true diff --git a/packages/common/__tests__/helpers.test.ts b/packages/common/__tests__/helpers.test.ts index 92965b16..a5e6f6ec 100644 --- a/packages/common/__tests__/helpers.test.ts +++ b/packages/common/__tests__/helpers.test.ts @@ -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(() => { @@ -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); + }); }); diff --git a/packages/common/src/helpers.ts b/packages/common/src/helpers.ts index 0f4f1be4..9418d18b 100644 --- a/packages/common/src/helpers.ts +++ b/packages/common/src/helpers.ts @@ -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 */ @@ -32,3 +33,19 @@ export function useFilesToBeExempted(): string[] { } return []; } + +export async function useCustomRules(): Promise { + 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 []; +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index a9caf163..7866297c 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -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'; diff --git a/packages/common/testMocks/sa11y-custom-rules.json b/packages/common/testMocks/sa11y-custom-rules.json new file mode 100644 index 00000000..0b2253c9 --- /dev/null +++ b/packages/common/testMocks/sa11y-custom-rules.json @@ -0,0 +1,3 @@ +{ + "rules": ["rule1", "rule2"] +} diff --git a/packages/jest/__tests__/automatic.test.ts b/packages/jest/__tests__/automatic.test.ts index f747656a..ac6ba02c 100644 --- a/packages/jest/__tests__/automatic.test.ts +++ b/packages/jest/__tests__/automatic.test.ts @@ -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'; @@ -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; + }); }); diff --git a/packages/jest/__tests__/setup.test.ts b/packages/jest/__tests__/setup.test.ts index 1330c17a..e4f2cc15 100644 --- a/packages/jest/__tests__/setup.test.ts +++ b/packages/jest/__tests__/setup.test.ts @@ -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'; @@ -21,6 +21,15 @@ describe('jest setup', () => { // 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', () => { diff --git a/packages/jest/src/automatic.ts b/packages/jest/src/automatic.ts index 77e08ae4..81879457 100644 --- a/packages/jest/src/automatic.ts +++ b/packages/jest/src/automatic.ts @@ -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'; /** @@ -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 @@ -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 { diff --git a/packages/jest/src/setup.ts b/packages/jest/src/setup.ts index 2351d44d..70432646 100644 --- a/packages/jest/src/setup.ts +++ b/packages/jest/src/setup.ts @@ -126,7 +126,6 @@ export function setup(opts: Sa11yOpts = defaultSa11yOpts): void { ]); registerSa11yAutomaticChecks(autoCheckOpts); } - /** * Register accessibility helpers toBeAccessible as jest matchers */ @@ -141,10 +140,14 @@ export function registerSa11yMatcher(): void { ); } } - /** * 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; +} 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; diff --git a/packages/test-utils/__data__/sa11y-custom-rules.json b/packages/test-utils/__data__/sa11y-custom-rules.json new file mode 100644 index 00000000..ee644394 --- /dev/null +++ b/packages/test-utils/__data__/sa11y-custom-rules.json @@ -0,0 +1,3 @@ +{ + "rules": ["link-name"] +} diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts index 52fa04ee..8d30d389 100644 --- a/packages/test-utils/src/index.ts +++ b/packages/test-utils/src/index.ts @@ -21,5 +21,6 @@ export { a11yIssuesCountFiltered, shadowDomID, videoURL, + customRulesFilePath, } from './test-data'; export { beforeEachSetup, cartesianProduct } from './utils'; diff --git a/packages/test-utils/src/test-data.ts b/packages/test-utils/src/test-data.ts index 28c92fc6..3e1b5527 100644 --- a/packages/test-utils/src/test-data.ts +++ b/packages/test-utils/src/test-data.ts @@ -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();