Skip to content

Commit

Permalink
feat: setup accessibility testing and linting (#134)
Browse files Browse the repository at this point in the history
<!-- Is your PR related to an issue? Then please link it via the "closes
#" below. Else, remove it. -->

closes #63 

## Description

- Setup Playwright for a11y testing
- Setup eslint plugin to show a11y violations while development
  • Loading branch information
larsrickert authored Jan 22, 2024
1 parent 7549554 commit 08673d0
Show file tree
Hide file tree
Showing 16 changed files with 201 additions and 82 deletions.
12 changes: 12 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
extends: [
"plugin:vue/vue3-recommended",
"eslint:recommended",
"plugin:vuejs-accessibility/recommended",
// @vue/eslint-config-typescript must be placed after all other configs except @vue/eslint-config-prettier
// see: https://github.com/vuejs/eslint-config-typescript#vueeslint-config-typescriptrecommended
"@vue/eslint-config-typescript/recommended",
Expand Down Expand Up @@ -39,5 +40,16 @@ module.exports = {
// this rule is only really relevant for the options API so we disable it here
// see: https://eslint.vuejs.org/rules/require-default-prop
"vue/require-default-prop": "off",
// by default all labels must have a "for" even when an input is nested inside it,
// we soften this rule to pass in this case
// see: https://vue-a11y.github.io/eslint-plugin-vuejs-accessibility/rules/label-has-for.html
"vuejs-accessibility/label-has-for": [
"error",
{
required: {
some: ["nesting", "id"],
},
},
],
},
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@playwright/experimental-ct-vue": "^1.41.1",
"@playwright/test": "^1.41.1",
"@rushstack/eslint-patch": "^1.7.0",
"@sit-onyx/eslint-plugin": "workspace:^",
"@tsconfig/node20": "^20.1.2",
"@types/jsdom": "^21.1.6",
"@types/node": "^20.11.5",
Expand All @@ -33,6 +34,7 @@
"eslint": "^8.56.0",
"eslint-plugin-playwright": "~0.22.1",
"eslint-plugin-vue": "^9.20.1",
"eslint-plugin-vuejs-accessibility": "^2.2.0",
"jsdom": "^24.0.0",
"lint-staged": "^15.2.0",
"prettier": "^3.2.4",
Expand Down
6 changes: 6 additions & 0 deletions packages/eslint-plugin/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
root: false, // will be merged with our global config
env: {
node: true,
},
};
16 changes: 16 additions & 0 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@sit-onyx/eslint-plugin",
"description": "Custom eslint rules that should be used for all packages of this monorepo",
"private": true,
"version": "0.0.0",
"type": "commonjs",
"author": "Schwarz IT KG",
"license": "Apache-2.0",
"main": "./src/index.cjs",
"peerDependencies": {
"eslint": ">= 8"
},
"devDependencies": {
"@types/eslint": "^8.56.2"
}
}
12 changes: 12 additions & 0 deletions packages/eslint-plugin/src/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// @ts-check
const packageJson = require("../package.json");

module.exports = {
meta: {
name: packageJson.name,
version: packageJson.version,
},
rules: {
"import-playwright-a11y": require("./rules/import-playwright-a11y.cjs"),
},
};
41 changes: 41 additions & 0 deletions packages/eslint-plugin/src/rules/import-playwright-a11y.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// @ts-check

/**
* @type {import('eslint').Rule.RuleModule}
*/
module.exports = {
meta: {
type: "problem",
docs: {
recommended: true,
description:
"Prevents importing test and except from Playwright when a custom fixture is available",
},
},
create: (context) => ({
ImportSpecifier(node) {
// allow the fixture itself to import from Playwright directly
if (context.filename.endsWith("/playwright-axe.ts")) return;

const hasFixture = ["expect", "test"].includes(node.imported.name);
if (!hasFixture) return;

// type check that node.parent.source.value exists
if (!("source" in node.parent) || !("value" in node.parent.source)) {
return;
}

const isDisallowedImport = ["@playwright/test", "@playwright/experimental-ct-vue"].includes(
node.parent.source.value.toString(),
);

if (isDisallowedImport) {
context.report({
node,
loc: node.loc,
message: `Import "${node.imported.name}" from "../../playwright-axe" instead because Onyx uses custom Playwright fixtures for providing a global configuration for accessability testing.`,
});
}
},
}),
};
15 changes: 9 additions & 6 deletions packages/headless/src/composables/comboBox/TestDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ defineExpose({ comboBox });
</script>
<template>
<div>
<label v-bind="label">some label:</label>
<input
v-bind="input"
@keydown.arrow-down="isExpanded = true"
@keydown.esc="isExpanded = false"
/>
<label v-bind="label">
some label:
<input
v-bind="input"
@keydown.arrow-down="isExpanded = true"
@keydown.esc="isExpanded = false"
/>
</label>

<button v-bind="button">
<template v-if="isExpanded">⬆️</template>
<template v-else>⬇️</template>
Expand Down
2 changes: 2 additions & 0 deletions packages/sit-onyx/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
module.exports = {
root: false, // will be merged with our global config
extends: ["plugin:playwright/recommended"],
plugins: ["@sit-onyx"],
rules: {
"no-console": "error",
"no-debugger": "error",
"@sit-onyx/import-playwright-a11y": "error",
},
};
1 change: 1 addition & 0 deletions packages/sit-onyx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"vue": ">= 3"
},
"devDependencies": {
"@axe-core/playwright": "^4.8.3",
"@fontsource-variable/source-code-pro": "^5.0.17",
"@fontsource-variable/source-sans-3": "^5.0.19",
"@sit-onyx/storybook-utils": "workspace:^",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 15 additions & 3 deletions packages/sit-onyx/src/components/TestInput/TestInput.ct.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { expect, test } from "@playwright/experimental-ct-vue";
import { expect, test } from "../../playwright-axe";
import TestInput from "./TestInput.vue";

test("should display label", async ({ mount }) => {
test("should display label", async ({ mount, makeAxeBuilder }) => {
// ARRANGE
const component = await mount(<TestInput label="Hello World" />);

// ASSERT
await expect(component).toContainText("Hello World");
await expect(component).toHaveScreenshot("default.png");

// ACT
const accessibilityScanResults = await makeAxeBuilder().analyze();

// ASSERT
expect(accessibilityScanResults.violations).toEqual([]);
});

test("should validate required inputs", async ({ mount }) => {
test("should validate required inputs", async ({ mount, makeAxeBuilder }) => {
// ARRANGE
const component = await mount(<TestInput label="Demo" required />);
const input = component.getByLabel('DemoModel value: "",');
Expand All @@ -21,4 +27,10 @@ test("should validate required inputs", async ({ mount }) => {

// ASSERT
await expect(component).toContainText("Please fill in this field.");

// ACT
const accessibilityScanResults = await makeAxeBuilder().analyze();

// ASSERT
expect(accessibilityScanResults.violations).toEqual([]);
});
3 changes: 0 additions & 3 deletions packages/sit-onyx/src/components/TestInput/TestInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,6 @@ watch(
color: darkred;
margin: 0;
}
&__info {
color: grey;
}
&--touched {
input:valid {
Expand Down
25 changes: 25 additions & 0 deletions packages/sit-onyx/src/playwright-axe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import AxeBuilder from "@axe-core/playwright";
import { test as base } from "@playwright/experimental-ct-vue";

export { expect } from "@playwright/experimental-ct-vue";

type AxeFixture = {
makeAxeBuilder: () => AxeBuilder;
};

/**
* Extends Playwright's base test by providing `makeAxeBuilder`
* This new `test` can be used in multiple test files, and each of them will get
* a consistently configured AxeBuilder instance.
*
* @see https://playwright.dev/docs/accessibility-testing#using-a-test-fixture-for-common-axe-configuration
*/
export const test: ReturnType<typeof base.extend<AxeFixture>> = base.extend<AxeFixture>({
makeAxeBuilder: async ({ page }, use) => {
const makeAxeBuilder = () => {
return new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]);
};

await use(makeAxeBuilder);
},
});
Loading

0 comments on commit 08673d0

Please sign in to comment.