aria-ease 2.8.0 → 2.8.2
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/bin/cli.js +3 -4
- package/dist/chunk-PCORWVIQ.js +213 -0
- package/dist/{chunk-4F6O5RKZ.js → chunk-SSBW5VAA.js} +0 -1
- package/dist/{contractTestRunnerPlaywright-FM6MK6DY.js → contractTestRunnerPlaywright-SE6TPWZZ.js} +1 -2
- package/{bin/contractTestRunnerPlaywright-2LQHVMXT.js → dist/contractTestRunnerPlaywright-YNHMLHQ2.js} +6 -178
- package/dist/contractTestRunnerPlaywright-ZY2T4UTV.js +254 -0
- package/dist/index.cjs +23 -8
- package/dist/index.d.cts +11 -14
- package/dist/index.d.ts +11 -14
- package/dist/index.js +24 -9
- package/dist/src/{Types.d-BbztRe-S.d.cts → Types.d-w1KLKLcA.d.cts} +8 -1
- package/dist/src/{Types.d-BbztRe-S.d.ts → Types.d-w1KLKLcA.d.ts} +8 -1
- package/dist/src/accordion/index.cjs +0 -2
- package/dist/src/accordion/index.d.cts +1 -1
- package/dist/src/accordion/index.d.ts +1 -1
- package/dist/src/accordion/index.js +0 -2
- package/dist/src/block/index.cjs +13 -5
- package/dist/src/block/index.d.cts +4 -3
- package/dist/src/block/index.d.ts +4 -3
- package/dist/src/block/index.js +14 -6
- package/dist/src/checkbox/index.cjs +0 -2
- package/dist/src/checkbox/index.d.cts +1 -1
- package/dist/src/checkbox/index.d.ts +1 -1
- package/dist/src/checkbox/index.js +0 -2
- package/dist/src/{chunk-DF4OR64G.js → chunk-TBJ6MIC7.js} +0 -2
- package/dist/src/menu/index.cjs +9 -4
- package/dist/src/menu/index.d.cts +4 -11
- package/dist/src/menu/index.d.ts +4 -11
- package/dist/src/menu/index.js +10 -5
- package/dist/src/radio/index.cjs +0 -2
- package/dist/src/radio/index.d.cts +1 -1
- package/dist/src/radio/index.d.ts +1 -1
- package/dist/src/radio/index.js +0 -2
- package/dist/src/toggle/index.cjs +0 -2
- package/dist/src/toggle/index.d.cts +1 -1
- package/dist/src/toggle/index.d.ts +1 -1
- package/dist/src/toggle/index.js +0 -2
- package/dist/src/utils/test/{chunk-UAS6V5MH.js → chunk-SSBW5VAA.js} +0 -2
- package/dist/src/utils/test/{contractTestRunnerPlaywright-IBC4FHWK.js → contractTestRunnerPlaywright-I36Y2NHA.js} +1 -3
- package/dist/src/utils/test/contractTestRunnerPlaywright-YNHMLHQ2.js +249 -0
- package/dist/src/utils/test/contractTestRunnerPlaywright-ZY2T4UTV.js +249 -0
- package/dist/src/utils/test/contracts/MenuContract.json +52 -8
- package/dist/src/utils/test/index.cjs +1 -3
- package/dist/src/utils/test/index.js +2 -4
- package/package.json +14 -13
- package/bin/cli.cjs +0 -475
- package/bin/cli.cjs.map +0 -1
- package/bin/cli.d.cts +0 -1
- package/bin/cli.d.ts +0 -1
- package/bin/cli.d.ts.map +0 -1
- package/bin/cli.js.map +0 -1
- package/bin/cli.ts +0 -122
- package/bin/configLoader.d.ts +0 -19
- package/bin/configLoader.d.ts.map +0 -1
- package/bin/configLoader.js +0 -155
- package/bin/configLoader.ts +0 -170
- package/bin/contractTestRunnerPlaywright-2LQHVMXT.js.map +0 -1
- package/dist/chunk-4F6O5RKZ.js.map +0 -1
- package/dist/contractTestRunnerPlaywright-FM6MK6DY.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/src/accordion/index.cjs.map +0 -1
- package/dist/src/accordion/index.js.map +0 -1
- package/dist/src/block/index.cjs.map +0 -1
- package/dist/src/block/index.js.map +0 -1
- package/dist/src/checkbox/index.cjs.map +0 -1
- package/dist/src/checkbox/index.js.map +0 -1
- package/dist/src/chunk-CGC24XEF.js +0 -127
- package/dist/src/chunk-CGC24XEF.js.map +0 -1
- package/dist/src/chunk-DF4OR64G.js.map +0 -1
- package/dist/src/chunk-MNMWQWXH.js +0 -117
- package/dist/src/chunk-MNMWQWXH.js.map +0 -1
- package/dist/src/menu/index.cjs.map +0 -1
- package/dist/src/menu/index.js.map +0 -1
- package/dist/src/radio/index.cjs.map +0 -1
- package/dist/src/radio/index.js.map +0 -1
- package/dist/src/toggle/index.cjs.map +0 -1
- package/dist/src/toggle/index.js.map +0 -1
- package/dist/src/utils/test/chunk-UAS6V5MH.js.map +0 -1
- package/dist/src/utils/test/contractTestRunnerPlaywright-IBC4FHWK.js.map +0 -1
- package/dist/src/utils/test/index.cjs.map +0 -1
- package/dist/src/utils/test/index.js.map +0 -1
package/dist/src/block/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { handleKeyPress } from '../chunk-
|
|
1
|
+
import { handleKeyPress } from '../chunk-TBJ6MIC7.js';
|
|
2
2
|
|
|
3
3
|
// src/block/src/makeBlockAccessible/makeBlockAccessible.ts
|
|
4
|
-
var eventListenersMap = /* @__PURE__ */ new Map();
|
|
5
4
|
function makeBlockAccessible(blockId, blockItemsClass) {
|
|
6
5
|
const blockDiv = document.querySelector(`#${blockId}`);
|
|
7
6
|
if (!blockDiv) {
|
|
@@ -9,12 +8,20 @@ function makeBlockAccessible(blockId, blockItemsClass) {
|
|
|
9
8
|
return { cleanup: () => {
|
|
10
9
|
} };
|
|
11
10
|
}
|
|
12
|
-
|
|
11
|
+
let cachedItems = null;
|
|
12
|
+
function getItems() {
|
|
13
|
+
if (!cachedItems) {
|
|
14
|
+
cachedItems = blockDiv.querySelectorAll(`.${blockItemsClass}`);
|
|
15
|
+
}
|
|
16
|
+
return cachedItems;
|
|
17
|
+
}
|
|
18
|
+
const blockItems = getItems();
|
|
13
19
|
if (!blockItems || blockItems.length === 0) {
|
|
14
20
|
console.error(`[aria-ease] Element with class="${blockItemsClass}" not found. Make sure the block items exist before calling makeBlockAccessible.`);
|
|
15
21
|
return { cleanup: () => {
|
|
16
22
|
} };
|
|
17
23
|
}
|
|
24
|
+
const eventListenersMap = /* @__PURE__ */ new Map();
|
|
18
25
|
blockItems.forEach((blockItem) => {
|
|
19
26
|
if (!eventListenersMap.has(blockItem)) {
|
|
20
27
|
const handler = (event) => {
|
|
@@ -35,9 +42,10 @@ function makeBlockAccessible(blockId, blockItemsClass) {
|
|
|
35
42
|
}
|
|
36
43
|
});
|
|
37
44
|
}
|
|
38
|
-
|
|
45
|
+
function refresh() {
|
|
46
|
+
cachedItems = null;
|
|
47
|
+
}
|
|
48
|
+
return { cleanup, refresh };
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
export { makeBlockAccessible };
|
|
42
|
-
//# sourceMappingURL=index.js.map
|
|
43
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CheckboxStates } from '../Types.d-
|
|
1
|
+
import { C as CheckboxStates } from '../Types.d-w1KLKLcA.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adds screen reader accessibility to multiple checkboxes. Updates the aria attributes of the checkboxes. Checkbox elements must possess the following aria attributes; aria-checked and aria-label.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CheckboxStates } from '../Types.d-
|
|
1
|
+
import { C as CheckboxStates } from '../Types.d-w1KLKLcA.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adds screen reader accessibility to multiple checkboxes. Updates the aria attributes of the checkboxes. Checkbox elements must possess the following aria attributes; aria-checked and aria-label.
|
package/dist/src/menu/index.cjs
CHANGED
|
@@ -138,7 +138,14 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
|
|
|
138
138
|
}, cleanup: () => {
|
|
139
139
|
} };
|
|
140
140
|
}
|
|
141
|
-
|
|
141
|
+
if (!/^[\w-]+$/.test(menuId)) {
|
|
142
|
+
console.error("[aria-ease] Invalid menuId: must be alphanumeric");
|
|
143
|
+
return { openMenu: () => {
|
|
144
|
+
}, closeMenu: () => {
|
|
145
|
+
}, cleanup: () => {
|
|
146
|
+
} };
|
|
147
|
+
}
|
|
148
|
+
const handlerMap = /* @__PURE__ */ new WeakMap();
|
|
142
149
|
const submenuInstances = /* @__PURE__ */ new Map();
|
|
143
150
|
let cachedItems = null;
|
|
144
151
|
let filteredItems = null;
|
|
@@ -257,7 +264,7 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
|
|
|
257
264
|
setAria(true);
|
|
258
265
|
const items = getFilteredItems();
|
|
259
266
|
addListeners();
|
|
260
|
-
if (items.length > 0) {
|
|
267
|
+
if (items && items.length > 0) {
|
|
261
268
|
items[0].focus();
|
|
262
269
|
}
|
|
263
270
|
}
|
|
@@ -282,5 +289,3 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
|
|
|
282
289
|
}
|
|
283
290
|
|
|
284
291
|
exports.makeMenuAccessible = makeMenuAccessible;
|
|
285
|
-
//# sourceMappingURL=index.cjs.map
|
|
286
|
-
//# sourceMappingURL=index.cjs.map
|
|
@@ -1,23 +1,16 @@
|
|
|
1
|
+
import { a as AccessibilityInstance } from '../Types.d-w1KLKLcA.cjs';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Adds keyboard interaction to toggle menu. The menu traps focus and can be interacted with using the keyboard. The first interactive item of the menu has focus when menu open.
|
|
3
5
|
* @param {string} menuId - The id of the menu.
|
|
4
6
|
* @param {string} menuItemsClass - The class of the items that are children of the menu.
|
|
5
7
|
* @param {string} triggerId - The id of the button that triggers the menu.
|
|
6
8
|
*/
|
|
9
|
+
|
|
7
10
|
declare function makeMenuAccessible({ menuId, menuItemsClass, triggerId }: {
|
|
8
11
|
menuId: string;
|
|
9
12
|
menuItemsClass: string;
|
|
10
13
|
triggerId: string;
|
|
11
|
-
}):
|
|
12
|
-
openMenu: () => void;
|
|
13
|
-
closeMenu: () => void;
|
|
14
|
-
cleanup: () => void;
|
|
15
|
-
refresh?: undefined;
|
|
16
|
-
} | {
|
|
17
|
-
openMenu: () => void;
|
|
18
|
-
closeMenu: () => void;
|
|
19
|
-
cleanup: () => void;
|
|
20
|
-
refresh: () => void;
|
|
21
|
-
};
|
|
14
|
+
}): AccessibilityInstance;
|
|
22
15
|
|
|
23
16
|
export { makeMenuAccessible };
|
package/dist/src/menu/index.d.ts
CHANGED
|
@@ -1,23 +1,16 @@
|
|
|
1
|
+
import { a as AccessibilityInstance } from '../Types.d-w1KLKLcA.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Adds keyboard interaction to toggle menu. The menu traps focus and can be interacted with using the keyboard. The first interactive item of the menu has focus when menu open.
|
|
3
5
|
* @param {string} menuId - The id of the menu.
|
|
4
6
|
* @param {string} menuItemsClass - The class of the items that are children of the menu.
|
|
5
7
|
* @param {string} triggerId - The id of the button that triggers the menu.
|
|
6
8
|
*/
|
|
9
|
+
|
|
7
10
|
declare function makeMenuAccessible({ menuId, menuItemsClass, triggerId }: {
|
|
8
11
|
menuId: string;
|
|
9
12
|
menuItemsClass: string;
|
|
10
13
|
triggerId: string;
|
|
11
|
-
}):
|
|
12
|
-
openMenu: () => void;
|
|
13
|
-
closeMenu: () => void;
|
|
14
|
-
cleanup: () => void;
|
|
15
|
-
refresh?: undefined;
|
|
16
|
-
} | {
|
|
17
|
-
openMenu: () => void;
|
|
18
|
-
closeMenu: () => void;
|
|
19
|
-
cleanup: () => void;
|
|
20
|
-
refresh: () => void;
|
|
21
|
-
};
|
|
14
|
+
}): AccessibilityInstance;
|
|
22
15
|
|
|
23
16
|
export { makeMenuAccessible };
|
package/dist/src/menu/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { handleKeyPress } from '../chunk-
|
|
1
|
+
import { handleKeyPress } from '../chunk-TBJ6MIC7.js';
|
|
2
2
|
|
|
3
3
|
// src/menu/src/makeMenuAccessible/makeMenuAccessible.ts
|
|
4
4
|
function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
|
|
@@ -18,7 +18,14 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
|
|
|
18
18
|
}, cleanup: () => {
|
|
19
19
|
} };
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
if (!/^[\w-]+$/.test(menuId)) {
|
|
22
|
+
console.error("[aria-ease] Invalid menuId: must be alphanumeric");
|
|
23
|
+
return { openMenu: () => {
|
|
24
|
+
}, closeMenu: () => {
|
|
25
|
+
}, cleanup: () => {
|
|
26
|
+
} };
|
|
27
|
+
}
|
|
28
|
+
const handlerMap = /* @__PURE__ */ new WeakMap();
|
|
22
29
|
const submenuInstances = /* @__PURE__ */ new Map();
|
|
23
30
|
let cachedItems = null;
|
|
24
31
|
let filteredItems = null;
|
|
@@ -137,7 +144,7 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
|
|
|
137
144
|
setAria(true);
|
|
138
145
|
const items = getFilteredItems();
|
|
139
146
|
addListeners();
|
|
140
|
-
if (items.length > 0) {
|
|
147
|
+
if (items && items.length > 0) {
|
|
141
148
|
items[0].focus();
|
|
142
149
|
}
|
|
143
150
|
}
|
|
@@ -162,5 +169,3 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
|
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
export { makeMenuAccessible };
|
|
165
|
-
//# sourceMappingURL=index.js.map
|
|
166
|
-
//# sourceMappingURL=index.js.map
|
package/dist/src/radio/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as RadioStates } from '../Types.d-
|
|
1
|
+
import { R as RadioStates } from '../Types.d-w1KLKLcA.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adds screen reader accessibility to multiple radio buttons. Updates the aria attributes of the radio buttons. Radio elements must possess the following aria attributes; aria-checked and aria-label.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as RadioStates } from '../Types.d-
|
|
1
|
+
import { R as RadioStates } from '../Types.d-w1KLKLcA.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adds screen reader accessibility to multiple radio buttons. Updates the aria attributes of the radio buttons. Radio elements must possess the following aria attributes; aria-checked and aria-label.
|
package/dist/src/radio/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as ToggleStates } from '../Types.d-
|
|
1
|
+
import { T as ToggleStates } from '../Types.d-w1KLKLcA.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adds screen reader accessibility to toggle buttons. Updates the aria attributes of the toggle buttons. Button must be a semantic button element or a non-semantic element with a role of button, and possess the aria-pressed attribute.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as ToggleStates } from '../Types.d-
|
|
1
|
+
import { T as ToggleStates } from '../Types.d-w1KLKLcA.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adds screen reader accessibility to toggle buttons. Updates the aria attributes of the toggle buttons. Button must be a semantic button element or a non-semantic element with a role of button, and possess the aria-pressed attribute.
|
package/dist/src/toggle/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContractReporter, contract_default } from './chunk-
|
|
1
|
+
import { ContractReporter, contract_default } from './chunk-SSBW5VAA.js';
|
|
2
2
|
import { chromium } from 'playwright';
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
4
|
|
|
@@ -247,5 +247,3 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
export { runContractTestsPlaywright };
|
|
250
|
-
//# sourceMappingURL=contractTestRunnerPlaywright-IBC4FHWK.js.map
|
|
251
|
-
//# sourceMappingURL=contractTestRunnerPlaywright-IBC4FHWK.js.map
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { ContractReporter, contract_default } from './chunk-SSBW5VAA.js';
|
|
2
|
+
import { chromium } from 'playwright';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
async function runContractTestsPlaywright(componentName, url) {
|
|
6
|
+
const reporter = new ContractReporter(true);
|
|
7
|
+
const contractTyped = contract_default;
|
|
8
|
+
const contractPath = contractTyped[componentName]?.path;
|
|
9
|
+
if (!contractPath) {
|
|
10
|
+
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
11
|
+
}
|
|
12
|
+
const resolvedPath = new URL(contractPath, import.meta.url).pathname;
|
|
13
|
+
const contractData = readFileSync(resolvedPath, "utf-8");
|
|
14
|
+
const componentContract = JSON.parse(contractData);
|
|
15
|
+
const totalTests = componentContract.static[0].assertions.length + componentContract.dynamic.length;
|
|
16
|
+
reporter.start(componentName, totalTests);
|
|
17
|
+
const failures = [];
|
|
18
|
+
const passes = [];
|
|
19
|
+
let browser = null;
|
|
20
|
+
try {
|
|
21
|
+
browser = await chromium.launch({ headless: true });
|
|
22
|
+
const context = await browser.newContext();
|
|
23
|
+
const page = await context.newPage();
|
|
24
|
+
await page.goto(url, { waitUntil: "networkidle" });
|
|
25
|
+
await page.waitForSelector(componentContract.selectors.trigger, { timeout: 3e4 });
|
|
26
|
+
async function resolveRelativeTarget(selector, relative) {
|
|
27
|
+
const items = await page.locator(selector).all();
|
|
28
|
+
switch (relative) {
|
|
29
|
+
case "first":
|
|
30
|
+
return items[0];
|
|
31
|
+
case "second":
|
|
32
|
+
return items[1];
|
|
33
|
+
case "last":
|
|
34
|
+
return items[items.length - 1];
|
|
35
|
+
case "next": {
|
|
36
|
+
const currentIndex = await page.evaluate(([sel]) => {
|
|
37
|
+
const items2 = Array.from(document.querySelectorAll(sel));
|
|
38
|
+
return items2.indexOf(document.activeElement);
|
|
39
|
+
}, [selector]);
|
|
40
|
+
const nextIndex = (currentIndex + 1) % items.length;
|
|
41
|
+
return items[nextIndex];
|
|
42
|
+
}
|
|
43
|
+
case "previous": {
|
|
44
|
+
const currentIndex = await page.evaluate(([sel]) => {
|
|
45
|
+
const items2 = Array.from(document.querySelectorAll(sel));
|
|
46
|
+
return items2.indexOf(document.activeElement);
|
|
47
|
+
}, [selector]);
|
|
48
|
+
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
49
|
+
return items[prevIndex];
|
|
50
|
+
}
|
|
51
|
+
default:
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const test of componentContract.static[0]?.assertions || []) {
|
|
56
|
+
if (test.target === "relative") continue;
|
|
57
|
+
const targetSelector = componentContract.selectors[test.target];
|
|
58
|
+
if (!targetSelector) {
|
|
59
|
+
failures.push(`Selector for target ${test.target} not found.`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const target = page.locator(targetSelector).first();
|
|
63
|
+
const exists = await target.count() > 0;
|
|
64
|
+
if (!exists) {
|
|
65
|
+
failures.push(`Target ${test.target} not found.`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!test.expectedValue) {
|
|
69
|
+
const attributes = test.attribute.split(" | ");
|
|
70
|
+
let hasAny = false;
|
|
71
|
+
for (const attr of attributes) {
|
|
72
|
+
const value = await target.getAttribute(attr.trim());
|
|
73
|
+
if (value !== null) {
|
|
74
|
+
hasAny = true;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!hasAny) {
|
|
79
|
+
failures.push(test.failureMessage + ` None of the attributes "${test.attribute}" are present.`);
|
|
80
|
+
} else {
|
|
81
|
+
passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
const attributeValue = await target.getAttribute(test.attribute);
|
|
85
|
+
const expectedValues = test.expectedValue.split(" | ");
|
|
86
|
+
if (!attributeValue || !expectedValues.includes(attributeValue)) {
|
|
87
|
+
failures.push(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
|
|
88
|
+
} else {
|
|
89
|
+
passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const dynamicTest of componentContract.dynamic || []) {
|
|
94
|
+
const { action, assertions } = dynamicTest;
|
|
95
|
+
const failuresBeforeTest = failures.length;
|
|
96
|
+
const containerElement = page.locator(componentContract.selectors.container).first();
|
|
97
|
+
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
98
|
+
const isContainerVisible = await containerElement.isVisible();
|
|
99
|
+
if (isContainerVisible) {
|
|
100
|
+
await triggerElement.click();
|
|
101
|
+
await page.waitForTimeout(50);
|
|
102
|
+
}
|
|
103
|
+
for (const act of action) {
|
|
104
|
+
if (act.type === "click") {
|
|
105
|
+
if (act.target === "document") {
|
|
106
|
+
await page.mouse.click(10, 10);
|
|
107
|
+
} else {
|
|
108
|
+
const actionSelector = componentContract.selectors[act.target];
|
|
109
|
+
if (!actionSelector) {
|
|
110
|
+
failures.push(`Selector for action target ${act.target} not found.`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
await page.locator(actionSelector).first().click();
|
|
114
|
+
await page.waitForTimeout(200);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (act.type === "keypress" && act.key) {
|
|
118
|
+
const keyMap = {
|
|
119
|
+
"Space": "Space",
|
|
120
|
+
"Enter": "Enter",
|
|
121
|
+
"Escape": "Escape",
|
|
122
|
+
"Arrow Up": "ArrowUp",
|
|
123
|
+
"Arrow Down": "ArrowDown",
|
|
124
|
+
"Arrow Left": "ArrowLeft",
|
|
125
|
+
"Arrow Right": "ArrowRight",
|
|
126
|
+
"Home": "Home",
|
|
127
|
+
"End": "End",
|
|
128
|
+
"Tab": "Tab"
|
|
129
|
+
};
|
|
130
|
+
let keyValue = keyMap[act.key] || act.key;
|
|
131
|
+
if (keyValue === "Space") {
|
|
132
|
+
keyValue = " ";
|
|
133
|
+
} else if (keyValue.includes(" ")) {
|
|
134
|
+
keyValue = keyValue.replace(/ /g, "");
|
|
135
|
+
}
|
|
136
|
+
if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
137
|
+
await page.waitForTimeout(100);
|
|
138
|
+
await page.keyboard.press(keyValue);
|
|
139
|
+
await page.waitForTimeout(50);
|
|
140
|
+
} else {
|
|
141
|
+
const keypressSelector = componentContract.selectors[act.target];
|
|
142
|
+
if (!keypressSelector) {
|
|
143
|
+
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const target = page.locator(keypressSelector).first();
|
|
147
|
+
await target.press(keyValue);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
await page.waitForTimeout(100);
|
|
151
|
+
}
|
|
152
|
+
for (const assertion of assertions) {
|
|
153
|
+
let target;
|
|
154
|
+
if (assertion.target === "relative") {
|
|
155
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
156
|
+
if (!relativeSelector) {
|
|
157
|
+
failures.push("Relative selector is not defined in the contract.");
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (!assertion.expectedValue) {
|
|
161
|
+
failures.push("Expected value for relative target is not defined.");
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
target = await resolveRelativeTarget(relativeSelector, assertion.expectedValue);
|
|
165
|
+
} else {
|
|
166
|
+
const assertionSelector = componentContract.selectors[assertion.target];
|
|
167
|
+
if (!assertionSelector) {
|
|
168
|
+
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
target = page.locator(assertionSelector).first();
|
|
172
|
+
}
|
|
173
|
+
if (!target) {
|
|
174
|
+
failures.push(`Target ${assertion.target} not found.`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (assertion.assertion === "toBeVisible") {
|
|
178
|
+
const isVisible = await target.isVisible();
|
|
179
|
+
if (isVisible) {
|
|
180
|
+
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
181
|
+
} else {
|
|
182
|
+
failures.push(`${assertion.failureMessage}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (assertion.assertion === "notToBeVisible") {
|
|
186
|
+
const isVisible = await target.isVisible();
|
|
187
|
+
if (!isVisible) {
|
|
188
|
+
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
189
|
+
} else {
|
|
190
|
+
failures.push(assertion.failureMessage + ` ${assertion.target} is still visible.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (assertion.assertion === "toHaveAttribute" && assertion.attribute && assertion.expectedValue) {
|
|
194
|
+
const attributeValue = await target.getAttribute(assertion.attribute);
|
|
195
|
+
if (attributeValue === assertion.expectedValue) {
|
|
196
|
+
passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
197
|
+
} else {
|
|
198
|
+
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should be "${assertion.expectedValue}", found "${attributeValue}".`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (assertion.assertion === "toHaveFocus") {
|
|
202
|
+
const hasFocus = await target.evaluate((el) => el === document.activeElement);
|
|
203
|
+
if (hasFocus) {
|
|
204
|
+
passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
|
|
205
|
+
} else {
|
|
206
|
+
failures.push(`${assertion.failureMessage}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (assertion.assertion === "toHaveRole" && assertion.expectedValue) {
|
|
210
|
+
const roleValue = await target.getAttribute("role");
|
|
211
|
+
if (roleValue === assertion.expectedValue) {
|
|
212
|
+
passes.push(`${assertion.target} has role "${assertion.expectedValue}". Test: "${dynamicTest.description}".`);
|
|
213
|
+
} else {
|
|
214
|
+
failures.push(assertion.failureMessage + ` Expected role "${assertion.expectedValue}", found "${roleValue}".`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const failuresAfterTest = failures.length;
|
|
219
|
+
const testPassed = failuresAfterTest === failuresBeforeTest;
|
|
220
|
+
const failureMessage = testPassed ? void 0 : failures[failures.length - 1];
|
|
221
|
+
reporter.reportTest(dynamicTest, testPassed ? "pass" : "fail", failureMessage);
|
|
222
|
+
}
|
|
223
|
+
const staticPassed = componentContract.static[0].assertions.length;
|
|
224
|
+
const staticFailed = 0;
|
|
225
|
+
reporter.reportStatic(staticPassed, staticFailed);
|
|
226
|
+
reporter.summary(failures);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (error instanceof Error) {
|
|
229
|
+
if (error.message.includes("Executable doesn't exist")) {
|
|
230
|
+
console.error("\n\u274C Playwright browsers not found!\n");
|
|
231
|
+
console.log("\u{1F4E6} Run: npx playwright install chromium\n");
|
|
232
|
+
failures.push("Playwright browser not installed. Run: npx playwright install chromium");
|
|
233
|
+
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED")) {
|
|
234
|
+
console.error("\n\u274C Cannot connect to dev server!\n");
|
|
235
|
+
console.log(` Make sure your dev server is running at ${url}
|
|
236
|
+
`);
|
|
237
|
+
failures.push(`Dev server not running at ${url}`);
|
|
238
|
+
} else {
|
|
239
|
+
console.error("\u274C Playwright test error:", error.message);
|
|
240
|
+
failures.push(`Test error: ${error.message}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} finally {
|
|
244
|
+
if (browser) await browser.close();
|
|
245
|
+
}
|
|
246
|
+
return { passes, failures };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export { runContractTestsPlaywright };
|