@stackoverflow/stacks 1.10.1 → 1.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css/stacks.css +3 -1
- package/dist/css/stacks.min.css +1 -1
- package/dist/js/stacks.js +266 -110
- package/dist/js/stacks.min.js +1 -1
- package/lib/components/activity-indicator/activity-indicator.a11y.test.ts +7 -5
- package/lib/components/anchor/anchor.a11y.test.ts +10 -3
- package/lib/components/badge/badge.a11y.test.ts +160 -0
- package/lib/components/badge/badge.visual.test.ts +150 -0
- package/lib/components/banner/banner.a11y.test.ts +14 -0
- package/lib/components/block-link/block-link.a11y.test.ts +9 -2
- package/lib/components/breadcrumbs/breadcrumbs.a11y.test.ts +2 -0
- package/lib/components/button/button.a11y.test.ts +132 -1
- package/lib/components/card/card.a11y.test.ts +6 -0
- package/lib/components/check-control/check-control.a11y.test.ts +48 -0
- package/lib/components/check-control/check-control.visual.test.ts +38 -0
- package/lib/components/check-group/check-group.a11y.test.ts +51 -0
- package/lib/components/check-group/check-group.visual.test.ts +58 -0
- package/lib/components/checkbox_radio/checkbox_radio.a11y.test.ts +39 -0
- package/lib/components/checkbox_radio/checkbox_radio.visual.test.ts +35 -0
- package/lib/components/description/description.a11y.test.ts +34 -0
- package/lib/components/description/description.visual.test.ts +30 -0
- package/lib/components/link/link.a11y.test.ts +19 -6
- package/lib/components/toggle-switch/toggle-switch.a11y.test.ts +76 -0
- package/lib/components/toggle-switch/toggle-switch.less +2 -1
- package/lib/components/toggle-switch/toggle-switch.visual.test.ts +74 -0
- package/lib/components/uploader/uploader.ts +1 -0
- package/lib/test/axe-apca/README.md +34 -0
- package/lib/test/axe-apca/index.ts +3 -0
- package/lib/test/axe-apca/package.wip.json +30 -0
- package/lib/test/axe-apca/src/apca-w3.d.ts +3 -0
- package/lib/test/axe-apca/src/axe-apca.test.ts +155 -0
- package/lib/test/axe-apca/src/axe-apca.ts +212 -0
- package/lib/test/test-utils.ts +18 -1
- package/lib/tsconfig.json +1 -0
- package/package.json +18 -17
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { defaultOptions, runComponentTests } from "../../test/test-utils";
|
|
2
|
+
import "../../index";
|
|
3
|
+
|
|
4
|
+
type CheckGroup = "checkbox" | "radio";
|
|
5
|
+
const checkTypes: CheckGroup[] = ["checkbox", "radio"];
|
|
6
|
+
|
|
7
|
+
// Account for horizontal variant
|
|
8
|
+
[true, false].forEach((isHorizontal) => {
|
|
9
|
+
checkTypes.forEach((type) => {
|
|
10
|
+
describe("s-check-group", () => {
|
|
11
|
+
const checkEls: {
|
|
12
|
+
type: CheckGroup;
|
|
13
|
+
id: string;
|
|
14
|
+
state?: "checked" | "unchecked" | "indeterminate";
|
|
15
|
+
}[] = [
|
|
16
|
+
{ type, id: `test-${type}1`, state: "checked" },
|
|
17
|
+
{ type, id: `test-${type}2` },
|
|
18
|
+
];
|
|
19
|
+
runComponentTests({
|
|
20
|
+
type: "visual",
|
|
21
|
+
tag: "fieldset",
|
|
22
|
+
baseClass: "s-check-group",
|
|
23
|
+
attributes: {
|
|
24
|
+
class: isHorizontal ? "hs1 ws3 p8" : "hs2 ws2 p8",
|
|
25
|
+
},
|
|
26
|
+
variants: isHorizontal ? ["horizontal"] : [],
|
|
27
|
+
children: {
|
|
28
|
+
default: `
|
|
29
|
+
<legend class="s-label">${type} group</legend>
|
|
30
|
+
${checkEls
|
|
31
|
+
.map(
|
|
32
|
+
({ type, state, id }, index) => `
|
|
33
|
+
<div class="s-check-control">
|
|
34
|
+
<input
|
|
35
|
+
class="s-${type}"
|
|
36
|
+
type="${type}"
|
|
37
|
+
id="${id}-${index}"
|
|
38
|
+
name=""
|
|
39
|
+
${state === "checked" ? "checked" : ""}/>
|
|
40
|
+
<label class="s-label" for="${id}-${index}">
|
|
41
|
+
${type} label ${index}
|
|
42
|
+
<p class="s-input-message">Description</p>
|
|
43
|
+
</label>
|
|
44
|
+
</div>
|
|
45
|
+
`
|
|
46
|
+
)
|
|
47
|
+
.join("")}
|
|
48
|
+
`,
|
|
49
|
+
},
|
|
50
|
+
options: {
|
|
51
|
+
...defaultOptions,
|
|
52
|
+
includeNullVariant: !isHorizontal,
|
|
53
|
+
testidSuffix: type,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { html } from "@open-wc/testing";
|
|
2
|
+
import { defaultOptions, runComponentTests } from "../../test/test-utils";
|
|
3
|
+
import "../../index";
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
const checkboxTemplate = ({ component, testid, id }: any) =>
|
|
7
|
+
html` <div class="s-check-control" data-testid="${testid}">
|
|
8
|
+
${component}
|
|
9
|
+
<label class="s-label" for="${id}">Label</label>
|
|
10
|
+
</div>`;
|
|
11
|
+
|
|
12
|
+
["checkbox", "radio"].forEach((type) => {
|
|
13
|
+
describe(type, () => {
|
|
14
|
+
// TODO include indeterminate
|
|
15
|
+
["checked", "unchecked"].forEach((state) => {
|
|
16
|
+
runComponentTests({
|
|
17
|
+
type: "a11y",
|
|
18
|
+
tag: "input",
|
|
19
|
+
baseClass: `s-${type}`,
|
|
20
|
+
attributes: {
|
|
21
|
+
name: "test-name",
|
|
22
|
+
id: "test-id",
|
|
23
|
+
type,
|
|
24
|
+
...(state === "checked" ? { checked: "checked" } : {}),
|
|
25
|
+
},
|
|
26
|
+
template: ({ component, testid }) =>
|
|
27
|
+
checkboxTemplate({
|
|
28
|
+
component,
|
|
29
|
+
testid,
|
|
30
|
+
id: "test-id",
|
|
31
|
+
}),
|
|
32
|
+
options: {
|
|
33
|
+
...defaultOptions,
|
|
34
|
+
testidSuffix: state,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { html } from "@open-wc/testing";
|
|
2
|
+
import { defaultOptions, runComponentTests } from "../../test/test-utils";
|
|
3
|
+
import "../../index";
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
const checkboxTemplate = ({ component, testid }: any) =>
|
|
7
|
+
html`<div
|
|
8
|
+
class="d-inline-flex ai-center jc-center bg-black-100 hs1 ws1 p8"
|
|
9
|
+
data-testid="${testid}"
|
|
10
|
+
>
|
|
11
|
+
${component}
|
|
12
|
+
</div>`;
|
|
13
|
+
|
|
14
|
+
["checkbox", "radio"].forEach((type) => {
|
|
15
|
+
describe(type, () => {
|
|
16
|
+
// TODO include indeterminate
|
|
17
|
+
["checked", "unchecked"].forEach((state) => {
|
|
18
|
+
runComponentTests({
|
|
19
|
+
type: "visual",
|
|
20
|
+
tag: "input",
|
|
21
|
+
baseClass: `s-${type}`,
|
|
22
|
+
attributes: {
|
|
23
|
+
type,
|
|
24
|
+
...(state === "checked" ? { checked: "checked" } : {}),
|
|
25
|
+
},
|
|
26
|
+
template: ({ component, testid }) =>
|
|
27
|
+
checkboxTemplate({ component, testid }),
|
|
28
|
+
options: {
|
|
29
|
+
...defaultOptions,
|
|
30
|
+
testidSuffix: state,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { html } from "@open-wc/testing";
|
|
2
|
+
import { defaultOptions, runComponentTests } from "../../test/test-utils";
|
|
3
|
+
import "../../index";
|
|
4
|
+
|
|
5
|
+
// account for parent with `.is-disabled` class
|
|
6
|
+
[true, false].forEach((isDisabled) => {
|
|
7
|
+
describe("description", () => {
|
|
8
|
+
runComponentTests({
|
|
9
|
+
type: "a11y",
|
|
10
|
+
tag: "p",
|
|
11
|
+
baseClass: "s-description",
|
|
12
|
+
children: {
|
|
13
|
+
default: `Describes the site in the product, emails, integrations, and logs.`,
|
|
14
|
+
},
|
|
15
|
+
options: {
|
|
16
|
+
...defaultOptions,
|
|
17
|
+
testidSuffix: isDisabled ? "is-disabled" : "",
|
|
18
|
+
},
|
|
19
|
+
template: ({ component, testid }) => html`
|
|
20
|
+
<div
|
|
21
|
+
class="${isDisabled ? "is-disabled" : ""}"
|
|
22
|
+
data-testid="${testid}"
|
|
23
|
+
>
|
|
24
|
+
${component}
|
|
25
|
+
</div>
|
|
26
|
+
`,
|
|
27
|
+
// TODO: Most of those skipped tests should be fixed by the new Stacks 2.0 palette
|
|
28
|
+
skippedTestids: [
|
|
29
|
+
/^.*-is-disabled$/, // TODO: should these the disabled tests be excluded all together instead of skipped?
|
|
30
|
+
"s-description-dark",
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { html } from "@open-wc/testing";
|
|
2
|
+
import { defaultOptions, runComponentTests } from "../../test/test-utils";
|
|
3
|
+
import "../../index";
|
|
4
|
+
|
|
5
|
+
// account for parent with `.is-disabled` class
|
|
6
|
+
[true, false].forEach((isDisabled) => {
|
|
7
|
+
describe("description", () => {
|
|
8
|
+
runComponentTests({
|
|
9
|
+
type: "visual",
|
|
10
|
+
tag: "p",
|
|
11
|
+
baseClass: "s-description",
|
|
12
|
+
children: {
|
|
13
|
+
default: `Describes the site in the product, emails, integrations, and logs.`,
|
|
14
|
+
},
|
|
15
|
+
options: {
|
|
16
|
+
...defaultOptions,
|
|
17
|
+
testidSuffix: isDisabled ? "is-disabled" : "",
|
|
18
|
+
},
|
|
19
|
+
template: ({ component, testid }) => html`
|
|
20
|
+
<div
|
|
21
|
+
class="bg-black-100 d-inline-flex ai-center jc-center hs1 ws2 p8
|
|
22
|
+
${isDisabled ? "is-disabled" : ""}"
|
|
23
|
+
data-testid="${testid}"
|
|
24
|
+
>
|
|
25
|
+
${component}
|
|
26
|
+
</div>
|
|
27
|
+
`,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -24,13 +24,26 @@ describe("link", () => {
|
|
|
24
24
|
attributes: {
|
|
25
25
|
href: "#",
|
|
26
26
|
},
|
|
27
|
+
// TODO: Most of those skipped tests should be fixed by the new Stacks 2.0 palette
|
|
27
28
|
skippedTestids: [
|
|
28
|
-
"s-link-dark",
|
|
29
|
-
"s-link-dark-dropdown",
|
|
30
|
-
"s-link-dark-danger",
|
|
31
|
-
"s-link-dark-danger-dropdown",
|
|
32
|
-
"s-link-dark-underlined",
|
|
33
|
-
"s-link-dark-underlined-dropdown",
|
|
29
|
+
"s-link-dark",
|
|
30
|
+
"s-link-dark-dropdown",
|
|
31
|
+
"s-link-dark-danger",
|
|
32
|
+
"s-link-dark-danger-dropdown",
|
|
33
|
+
"s-link-dark-underlined",
|
|
34
|
+
"s-link-dark-underlined-dropdown",
|
|
35
|
+
"s-link-dark-muted",
|
|
36
|
+
"s-link-dark-muted-dropdown",
|
|
37
|
+
"s-link-dark-visited",
|
|
38
|
+
"s-link-dark-visited-dropdown",
|
|
39
|
+
"s-link-light",
|
|
40
|
+
"s-link-light-danger",
|
|
41
|
+
"s-link-light-danger-dropdown",
|
|
42
|
+
"s-link-light-dropdown",
|
|
43
|
+
"s-link-light-muted",
|
|
44
|
+
"s-link-light-muted-dropdown",
|
|
45
|
+
"s-link-light-underlined",
|
|
46
|
+
"s-link-light-underlined-dropdown",
|
|
34
47
|
],
|
|
35
48
|
});
|
|
36
49
|
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { html } from "@open-wc/testing";
|
|
2
|
+
import { defaultOptions, runComponentTests } from "../../test/test-utils";
|
|
3
|
+
import "../../index";
|
|
4
|
+
|
|
5
|
+
describe("toggle-switch", () => {
|
|
6
|
+
// Single toggle switch
|
|
7
|
+
[true, false].forEach((checked) => {
|
|
8
|
+
[true, false].forEach((disabled) => {
|
|
9
|
+
const idSuffix = `${checked ? "-checked" : ""}${
|
|
10
|
+
disabled ? "-disabled" : ""
|
|
11
|
+
}`;
|
|
12
|
+
const id = `toggle-switch${idSuffix}`;
|
|
13
|
+
|
|
14
|
+
runComponentTests({
|
|
15
|
+
type: "a11y",
|
|
16
|
+
baseClass: "s-toggle-switch",
|
|
17
|
+
modifiers: {
|
|
18
|
+
global: idSuffix ? [idSuffix.substring(1)] : [], // for proper testid generation
|
|
19
|
+
},
|
|
20
|
+
tag: "input",
|
|
21
|
+
attributes: {
|
|
22
|
+
id,
|
|
23
|
+
type: "checkbox",
|
|
24
|
+
...(checked ? { checked: "" } : {}),
|
|
25
|
+
...(disabled ? { disabled: "" } : {}),
|
|
26
|
+
},
|
|
27
|
+
template: ({ component, testid }) => html`
|
|
28
|
+
<div data-testid="${testid}">
|
|
29
|
+
<label for="${id}">toggle</label>
|
|
30
|
+
${component}
|
|
31
|
+
</div>
|
|
32
|
+
`,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Multiple toggle switch variant
|
|
38
|
+
[true, false].forEach((offChecked) => {
|
|
39
|
+
runComponentTests({
|
|
40
|
+
type: "a11y",
|
|
41
|
+
baseClass: "s-toggle-switch",
|
|
42
|
+
variants: ["multiple"],
|
|
43
|
+
modifiers: {
|
|
44
|
+
global: offChecked ? ["off"] : [], // for proper testid generation
|
|
45
|
+
},
|
|
46
|
+
children: {
|
|
47
|
+
default: `
|
|
48
|
+
<input type="radio" name="group" id="four" ${
|
|
49
|
+
offChecked ? 'checked=""' : ""
|
|
50
|
+
}>
|
|
51
|
+
<label for="four" class="s-toggle-switch--label-off">Off</label>
|
|
52
|
+
<input type="radio" name="group" id="one" ${
|
|
53
|
+
!offChecked ? 'checked=""' : ""
|
|
54
|
+
}>
|
|
55
|
+
<label for="one">one</label>
|
|
56
|
+
<input type="radio" name="group" id="two">
|
|
57
|
+
<label for="two">two</label>
|
|
58
|
+
`,
|
|
59
|
+
},
|
|
60
|
+
options: {
|
|
61
|
+
...defaultOptions,
|
|
62
|
+
includeNullVariant: false,
|
|
63
|
+
},
|
|
64
|
+
template: ({ component, testid }) => html`
|
|
65
|
+
<div data-testid="${testid}">${component}</div>
|
|
66
|
+
`,
|
|
67
|
+
// TODO: Most of those skipped tests should be fixed by the new Stacks 2.0 palette
|
|
68
|
+
skippedTestids: [
|
|
69
|
+
"s-toggle-switch-dark-multiple",
|
|
70
|
+
"s-toggle-switch-light-multiple",
|
|
71
|
+
"s-toggle-switch-dark-multiple-off",
|
|
72
|
+
"s-toggle-switch-light-multiple-off",
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { html } from "@open-wc/testing";
|
|
2
|
+
import { defaultOptions, runComponentTests } from "../../test/test-utils";
|
|
3
|
+
import "../../index";
|
|
4
|
+
|
|
5
|
+
describe("toggle-switch", () => {
|
|
6
|
+
// Single toggle switch
|
|
7
|
+
[true, false].forEach((checked) => {
|
|
8
|
+
[true, false].forEach((disabled) => {
|
|
9
|
+
const testidSuffix = `${checked ? "checked" : "unchecked"}${
|
|
10
|
+
disabled ? "-disabled" : "-enabled"
|
|
11
|
+
}`;
|
|
12
|
+
const id = `toggle-switch-${testidSuffix}`;
|
|
13
|
+
|
|
14
|
+
runComponentTests({
|
|
15
|
+
type: "visual",
|
|
16
|
+
baseClass: "s-toggle-switch",
|
|
17
|
+
tag: "input",
|
|
18
|
+
attributes: {
|
|
19
|
+
id,
|
|
20
|
+
type: "checkbox",
|
|
21
|
+
...(checked ? { checked: "" } : {}),
|
|
22
|
+
...(disabled ? { disabled: "" } : {}),
|
|
23
|
+
},
|
|
24
|
+
options: {
|
|
25
|
+
...defaultOptions,
|
|
26
|
+
includeNullModifier: false,
|
|
27
|
+
testidSuffix,
|
|
28
|
+
},
|
|
29
|
+
template: ({ component, testid }) => html`
|
|
30
|
+
<div data-testid="${testid}" class="p4 ws1">
|
|
31
|
+
<label class="v-visible-sr" for="${id}">toggle</label>
|
|
32
|
+
${component}
|
|
33
|
+
</div>
|
|
34
|
+
`,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Multiple toggle switch variant
|
|
40
|
+
[true, false].forEach((offChecked) => {
|
|
41
|
+
runComponentTests({
|
|
42
|
+
type: "visual",
|
|
43
|
+
baseClass: "s-toggle-switch",
|
|
44
|
+
variants: ["multiple"],
|
|
45
|
+
children: {
|
|
46
|
+
default: `
|
|
47
|
+
<input type="radio" name="group" id="off" ${
|
|
48
|
+
offChecked ? 'checked=""' : ""
|
|
49
|
+
}>
|
|
50
|
+
<label for="off" class="s-toggle-switch--label-off">Off</label>
|
|
51
|
+
<input type="radio" name="group" id="one" ${
|
|
52
|
+
!offChecked ? 'checked=""' : ""
|
|
53
|
+
}>
|
|
54
|
+
<label for="one">Weekly</label>
|
|
55
|
+
<input type="radio" name="group" id="two">
|
|
56
|
+
<label for="two">Daily</label>
|
|
57
|
+
<input type="radio" name="group" id="three">
|
|
58
|
+
<label for="three">3 hrs</label>
|
|
59
|
+
`,
|
|
60
|
+
},
|
|
61
|
+
options: {
|
|
62
|
+
...defaultOptions,
|
|
63
|
+
includeNullModifier: false,
|
|
64
|
+
includeNullVariant: false,
|
|
65
|
+
testidSuffix: offChecked ? "unchecked" : "checked",
|
|
66
|
+
},
|
|
67
|
+
template: ({ component, testid }) => html`
|
|
68
|
+
<div data-testid="${testid}" class="d-flex ai-center g8 p4 ws2">
|
|
69
|
+
${component}
|
|
70
|
+
</div>
|
|
71
|
+
`,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -136,6 +136,7 @@ export class UploaderController extends Stacks.StacksController {
|
|
|
136
136
|
|
|
137
137
|
if (file.type.match("image/*") && file.data) {
|
|
138
138
|
thumbElement = document.createElement("img");
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
139
140
|
thumbElement.src = file.data.toString();
|
|
140
141
|
thumbElement.alt = file.name;
|
|
141
142
|
} else {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# axe-apca
|
|
2
|
+
|
|
3
|
+
⚠️ *Once this package become more mature and stable it should be extracted in its own repo (e.g. StackExchange/axe-apca) and published as an npm package. This will allow to use the rule/checks in many contexts including our Core codebase.*
|
|
4
|
+
|
|
5
|
+
This package contains custom axe rules and checks for [APCA](https://readtech.org/) Bronze and Silver+ [conformance levels](https://readtech.org/ARC/tests/visual-readability-contrast/?tn=criterion).
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
### Installation
|
|
10
|
+
|
|
11
|
+
⚠️ *The following command doesn't work because this package is not published on npm yet.*
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install --save-dev axe-core axe-apca
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Setup
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import axe from "axe-core";
|
|
21
|
+
import { registerAxeAPCA } from 'axe-apca';
|
|
22
|
+
|
|
23
|
+
registerAxeAPCA('bronze'); // or registerAxeAPCA('silver');
|
|
24
|
+
|
|
25
|
+
// consider turning off default WCAG 2.1 AA color contrast rules when using APCA
|
|
26
|
+
axe.configure({
|
|
27
|
+
rules: [{ id: "color-contrast", enabled: false }]
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
axe.run(document, (err, results) => {
|
|
31
|
+
if (err) throw err;
|
|
32
|
+
console.log(results);
|
|
33
|
+
});
|
|
34
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "axe-apca",
|
|
3
|
+
"description": "Axe rules to check against APCA bronze and silver+ conformance levels.",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/StackExchange/axe-apca.git"
|
|
7
|
+
},
|
|
8
|
+
"version": "0.0.0",
|
|
9
|
+
"main": "TODO",
|
|
10
|
+
"types": "TODO",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "TODO: transpile ts to js",
|
|
13
|
+
"test": "web-test-runner"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"apca-w3": "^0.1.9"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"axe-core": "^4.0.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@open-wc/testing": "^3.2.0",
|
|
24
|
+
"@web/dev-server-esbuild": "^0.4.1",
|
|
25
|
+
"@web/dev-server-rollup": "^0.5.2",
|
|
26
|
+
"@web/test-runner": "^0.16.1",
|
|
27
|
+
"@web/test-runner-playwright": "^0.10.1",
|
|
28
|
+
"typescript": "^5.1.6"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { html, fixture, expect } from "@open-wc/testing";
|
|
2
|
+
import axe from "axe-core";
|
|
3
|
+
import type { AxeResults } from "axe-core";
|
|
4
|
+
import registerAxeAPCA from "./axe-apca";
|
|
5
|
+
|
|
6
|
+
const runAxe = async (el: HTMLElement): Promise<AxeResults> => {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
axe.run(el, (err, results) => {
|
|
9
|
+
if (err) reject(err);
|
|
10
|
+
resolve(results);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("axe-apca", () => {
|
|
16
|
+
describe("bronze conformance level", () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
registerAxeAPCA("bronze");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should check for APCA accessibility contrast violations", async () => {
|
|
22
|
+
const el: HTMLElement = await fixture(
|
|
23
|
+
html`<p
|
|
24
|
+
style="background: white; color: black; font-size: 12px; font-weight: 400;"
|
|
25
|
+
>
|
|
26
|
+
Some copy
|
|
27
|
+
</p>`
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const results = await runAxe(el);
|
|
31
|
+
|
|
32
|
+
const apcaPasses = results.passes.filter((violation) =>
|
|
33
|
+
violation.id.includes("color-contrast-apca-bronze")
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
await expect(apcaPasses.length).to.equal(1);
|
|
37
|
+
|
|
38
|
+
const passNode = apcaPasses[0].nodes[0];
|
|
39
|
+
expect(passNode.all[0].message).to.include(
|
|
40
|
+
"Element has sufficient APCA bronze level lightness contrast"
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should detect APCA accessibility contrast violations", async () => {
|
|
45
|
+
const el: HTMLElement = await fixture(
|
|
46
|
+
html`<p
|
|
47
|
+
style="background: white; color: lightgray; font-size: 12px; font-weight: 400;"
|
|
48
|
+
>
|
|
49
|
+
Some copy
|
|
50
|
+
</p>`
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const results = await runAxe(el);
|
|
54
|
+
|
|
55
|
+
const apcaViolations = results.violations.filter((violation) =>
|
|
56
|
+
violation.id.includes("color-contrast-apca-bronze")
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
await expect(apcaViolations.length).to.equal(1);
|
|
60
|
+
|
|
61
|
+
const violationNode = apcaViolations[0].nodes[0];
|
|
62
|
+
expect(violationNode.failureSummary).to.include(
|
|
63
|
+
"Element has insufficient APCA bronze level contrast"
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should check nested nodes", async () => {
|
|
68
|
+
const el: HTMLElement = await fixture(
|
|
69
|
+
html`<div style="background: black;">
|
|
70
|
+
<h2>Some title</h2>
|
|
71
|
+
<p>Some copy</p>
|
|
72
|
+
<button style="background: black;">Some button</button>
|
|
73
|
+
</div>`
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const results = await runAxe(el);
|
|
77
|
+
|
|
78
|
+
const apcaViolations = results.violations.filter((violation) =>
|
|
79
|
+
violation.id.includes("color-contrast-apca-bronze")
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
await expect(apcaViolations[0].nodes.length).to.equal(3);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("silver conformance level", () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
registerAxeAPCA("silver");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should check for APCA accessibility contrast violations", async () => {
|
|
92
|
+
const el: HTMLElement = await fixture(
|
|
93
|
+
html`<p
|
|
94
|
+
style="background: white; color: black; font-size: 14px; font-weight: 400;"
|
|
95
|
+
>
|
|
96
|
+
Some copy
|
|
97
|
+
</p>`
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const results = await runAxe(el);
|
|
101
|
+
|
|
102
|
+
const apcaPasses = results.passes.filter((violation) =>
|
|
103
|
+
violation.id.includes("color-contrast-apca-silver")
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
await expect(apcaPasses.length).to.equal(1);
|
|
107
|
+
|
|
108
|
+
const passNode = apcaPasses[0].nodes[0];
|
|
109
|
+
expect(passNode.all[0].message).to.include(
|
|
110
|
+
"Element has sufficient APCA silver level lightness contrast"
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should detect APCA accessibility contrast violations", async () => {
|
|
115
|
+
const el: HTMLElement = await fixture(
|
|
116
|
+
html`<p
|
|
117
|
+
style="background: white; color: black; font-size: 12px; font-weight: 400;"
|
|
118
|
+
>
|
|
119
|
+
Some copy
|
|
120
|
+
</p>`
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const results = await runAxe(el);
|
|
124
|
+
|
|
125
|
+
const apcaViolations = results.violations.filter((violation) =>
|
|
126
|
+
violation.id.includes("color-contrast-apca-silver")
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
await expect(apcaViolations.length).to.equal(1);
|
|
130
|
+
|
|
131
|
+
const violationNode = apcaViolations[0].nodes[0];
|
|
132
|
+
expect(violationNode.failureSummary).to.include(
|
|
133
|
+
"Element has insufficient APCA silver level contrast"
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("should check nested nodes", async () => {
|
|
138
|
+
const el: HTMLElement = await fixture(
|
|
139
|
+
html`<div style="background: black;">
|
|
140
|
+
<h2>Some title</h2>
|
|
141
|
+
<p>Some copy</p>
|
|
142
|
+
<button style="background: black;">Some button</button>
|
|
143
|
+
</div>`
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const results = await runAxe(el);
|
|
147
|
+
|
|
148
|
+
const apcaViolations = results.violations.filter((violation) =>
|
|
149
|
+
violation.id.includes("color-contrast-apca-silver")
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
await expect(apcaViolations[0].nodes.length).to.equal(3);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|