aria-ease 6.8.0 → 6.9.1
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/README.md +68 -6
- package/bin/AccordionComponentStrategy-4ZEIQ2V6.js +42 -0
- package/bin/ComboboxComponentStrategy-OGRVZXAF.js +64 -0
- package/bin/MenuComponentStrategy-JAMTCSNF.js +81 -0
- package/bin/TabsComponentStrategy-3SQURPMX.js +29 -0
- package/bin/buildContracts-GBOY7UXG.js +437 -0
- package/bin/{chunk-VPBHLMAS.js → chunk-LMSKLN5O.js} +21 -0
- package/bin/chunk-PK5L2SAF.js +17 -0
- package/bin/{chunk-2TOYEY5L.js → chunk-XERMSYEH.js} +12 -3
- package/bin/cli.cjs +991 -128
- package/bin/cli.js +33 -2
- package/bin/{configLoader-XRF6VM4J.js → configLoader-Q6A4JLKW.js} +1 -1
- package/{dist/contractTestRunnerPlaywright-UAOFNS7Z.js → bin/contractTestRunnerPlaywright-ZZNWDUYP.js} +270 -219
- package/bin/{test-WRIJHN6H.js → test-OND56UUL.js} +97 -10
- package/dist/AccordionComponentStrategy-4ZEIQ2V6.js +42 -0
- package/dist/ComboboxComponentStrategy-OGRVZXAF.js +64 -0
- package/dist/MenuComponentStrategy-JAMTCSNF.js +81 -0
- package/dist/TabsComponentStrategy-3SQURPMX.js +29 -0
- package/dist/chunk-PK5L2SAF.js +17 -0
- package/dist/{chunk-2TOYEY5L.js → chunk-XERMSYEH.js} +12 -3
- package/dist/{configLoader-IT4PWCJB.js → configLoader-WTGJAP4Z.js} +21 -0
- package/{bin/contractTestRunnerPlaywright-UAOFNS7Z.js → dist/contractTestRunnerPlaywright-XBWJZMR3.js} +270 -219
- package/dist/index.cjs +794 -90
- package/dist/index.d.cts +136 -1
- package/dist/index.d.ts +136 -1
- package/dist/index.js +415 -10
- package/dist/src/utils/test/AccordionComponentStrategy-WRHZOEN6.js +38 -0
- package/dist/src/utils/test/ComboboxComponentStrategy-5AECQSRN.js +60 -0
- package/dist/src/utils/test/MenuComponentStrategy-VKZQYLBE.js +77 -0
- package/dist/src/utils/test/TabsComponentStrategy-BKG53SEV.js +26 -0
- package/dist/src/utils/test/{chunk-2TOYEY5L.js → chunk-XERMSYEH.js} +12 -3
- package/dist/src/utils/test/{configLoader-LD4RV2WQ.js → configLoader-YE2CYGDG.js} +21 -0
- package/dist/src/utils/test/{contractTestRunnerPlaywright-IRJOAEMT.js → contractTestRunnerPlaywright-LC5OAVXB.js} +262 -200
- package/dist/src/utils/test/dsl/index.cjs +320 -0
- package/dist/src/utils/test/dsl/index.d.cts +136 -0
- package/dist/src/utils/test/dsl/index.d.ts +136 -0
- package/dist/src/utils/test/dsl/index.js +318 -0
- package/dist/src/utils/test/index.cjs +472 -88
- package/dist/src/utils/test/index.js +97 -12
- package/package.json +9 -3
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
normalizeLevel,
|
|
6
6
|
normalizeStrictness,
|
|
7
7
|
resolveEnforcement
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-XERMSYEH.js";
|
|
9
9
|
import "./chunk-I2KLQ2HA.js";
|
|
10
10
|
|
|
11
11
|
// src/utils/test/src/test.ts
|
|
@@ -24,7 +24,7 @@ async function runContractTests(componentName, component, strictness) {
|
|
|
24
24
|
const resolvedPath = new URL(contractPath, import.meta.url).pathname;
|
|
25
25
|
const contractData = await fs.readFile(resolvedPath, "utf-8");
|
|
26
26
|
const componentContract = JSON.parse(contractData);
|
|
27
|
-
const totalTests = componentContract.static[0]
|
|
27
|
+
const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
|
|
28
28
|
reporter.start(componentName, totalTests);
|
|
29
29
|
const failures = [];
|
|
30
30
|
const passes = [];
|
|
@@ -48,6 +48,82 @@ async function runContractTests(componentName, component, strictness) {
|
|
|
48
48
|
let staticPassed = 0;
|
|
49
49
|
let staticFailed = 0;
|
|
50
50
|
let staticWarnings = 0;
|
|
51
|
+
for (const rel of componentContract.relationships || []) {
|
|
52
|
+
const relationshipLevel = normalizeLevel(rel.level);
|
|
53
|
+
if (rel.type === "aria-reference") {
|
|
54
|
+
const fromSelector = componentContract.selectors[rel.from];
|
|
55
|
+
const toSelector = componentContract.selectors[rel.to];
|
|
56
|
+
const relDescription = `${rel.from}.${rel.attribute} references ${rel.to}`;
|
|
57
|
+
if (!fromSelector || !toSelector) {
|
|
58
|
+
const outcome = classifyFailure(`Relationship selector missing: from="${rel.from}" or to="${rel.to}" not found in selectors.`, rel.level);
|
|
59
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
60
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
61
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const fromTarget = component.querySelector(fromSelector);
|
|
65
|
+
const toTarget = component.querySelector(toSelector);
|
|
66
|
+
if (!fromTarget || !toTarget) {
|
|
67
|
+
const outcome = classifyFailure(`Relationship target not found: ${!fromTarget ? rel.from : rel.to}.`, rel.level);
|
|
68
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
69
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
70
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const toId = toTarget.getAttribute("id");
|
|
74
|
+
const attrValue = fromTarget.getAttribute(rel.attribute) || "";
|
|
75
|
+
if (!toId) {
|
|
76
|
+
const outcome = classifyFailure(`Relationship target "${rel.to}" must have an id for ${rel.attribute} validation.`, rel.level);
|
|
77
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
78
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
79
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const references = attrValue.split(/\s+/).filter(Boolean);
|
|
83
|
+
if (!references.includes(toId)) {
|
|
84
|
+
const outcome = classifyFailure(`Expected ${rel.from} ${rel.attribute} to reference id "${toId}", found "${attrValue}".`, rel.level);
|
|
85
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
86
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
87
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
passes.push(`Relationship valid: ${rel.from}.${rel.attribute} -> ${rel.to} (id=${toId}).`);
|
|
91
|
+
staticPassed += 1;
|
|
92
|
+
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (rel.type === "contains") {
|
|
96
|
+
const parentSelector = componentContract.selectors[rel.parent];
|
|
97
|
+
const childSelector = componentContract.selectors[rel.child];
|
|
98
|
+
const relDescription = `${rel.parent} contains ${rel.child}`;
|
|
99
|
+
if (!parentSelector || !childSelector) {
|
|
100
|
+
const outcome = classifyFailure(`Relationship selector missing: parent="${rel.parent}" or child="${rel.child}" not found in selectors.`, rel.level);
|
|
101
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
102
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
103
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const parentTarget = component.querySelector(parentSelector);
|
|
107
|
+
if (!parentTarget) {
|
|
108
|
+
const outcome = classifyFailure(`Relationship parent target not found: ${rel.parent}.`, rel.level);
|
|
109
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
110
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
111
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const nestedChild = parentTarget.querySelector(childSelector);
|
|
115
|
+
if (!nestedChild) {
|
|
116
|
+
const outcome = classifyFailure(`Expected ${rel.parent} to contain descendant matching selector for ${rel.child}.`, rel.level);
|
|
117
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
118
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
119
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
passes.push(`Relationship valid: ${rel.parent} contains ${rel.child}.`);
|
|
123
|
+
staticPassed += 1;
|
|
124
|
+
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
51
127
|
for (const test of componentContract.static[0].assertions) {
|
|
52
128
|
if (test.target !== "relative") {
|
|
53
129
|
const staticLevel = normalizeLevel(test.level);
|
|
@@ -103,6 +179,7 @@ async function runContractTests(componentName, component, strictness) {
|
|
|
103
179
|
}
|
|
104
180
|
|
|
105
181
|
// src/utils/test/src/test.ts
|
|
182
|
+
import path from "path";
|
|
106
183
|
async function testUiComponent(componentName, component, url, options = {}) {
|
|
107
184
|
if (!componentName || typeof componentName !== "string") {
|
|
108
185
|
throw new Error("\u274C testUiComponent requires a valid componentName (string)");
|
|
@@ -141,14 +218,24 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
141
218
|
return null;
|
|
142
219
|
}
|
|
143
220
|
let strictness = normalizeStrictness(options.strictness);
|
|
144
|
-
|
|
221
|
+
let config = {};
|
|
222
|
+
let configBaseDir = typeof process !== "undefined" ? process.cwd() : "";
|
|
223
|
+
if (typeof process !== "undefined" && typeof process.cwd === "function") {
|
|
145
224
|
try {
|
|
146
|
-
const { loadConfig } = await import("./configLoader-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
225
|
+
const { loadConfig } = await import("./configLoader-Q6A4JLKW.js");
|
|
226
|
+
const result2 = await loadConfig(process.cwd());
|
|
227
|
+
config = result2.config;
|
|
228
|
+
if (result2.configPath) {
|
|
229
|
+
configBaseDir = path.dirname(result2.configPath);
|
|
230
|
+
}
|
|
231
|
+
if (options.strictness === void 0) {
|
|
232
|
+
const componentStrictness = config.test?.components?.find((comp) => comp?.name === componentName)?.strictness;
|
|
233
|
+
strictness = normalizeStrictness(componentStrictness ?? config.test?.strictness);
|
|
234
|
+
}
|
|
150
235
|
} catch {
|
|
151
|
-
strictness
|
|
236
|
+
if (options.strictness === void 0) {
|
|
237
|
+
strictness = "balanced";
|
|
238
|
+
}
|
|
152
239
|
}
|
|
153
240
|
}
|
|
154
241
|
let contract;
|
|
@@ -157,8 +244,8 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
157
244
|
const devServerUrl = await checkDevServer(url);
|
|
158
245
|
if (devServerUrl) {
|
|
159
246
|
console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
|
|
160
|
-
const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-
|
|
161
|
-
contract = await runContractTestsPlaywright(componentName, devServerUrl, strictness);
|
|
247
|
+
const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-ZZNWDUYP.js");
|
|
248
|
+
contract = await runContractTestsPlaywright(componentName, devServerUrl, strictness, config, configBaseDir);
|
|
162
249
|
} else {
|
|
163
250
|
throw new Error(
|
|
164
251
|
`\u274C Dev server not running at ${url}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
test_exports
|
|
3
|
+
} from "./chunk-PK5L2SAF.js";
|
|
4
|
+
import "./chunk-I2KLQ2HA.js";
|
|
5
|
+
|
|
6
|
+
// src/utils/test/src/component-strategies/AccordionComponentStrategy.ts
|
|
7
|
+
var AccordionComponentStrategy = class {
|
|
8
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
9
|
+
this.mainSelector = mainSelector;
|
|
10
|
+
this.selectors = selectors;
|
|
11
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
12
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
13
|
+
}
|
|
14
|
+
async resetState(page) {
|
|
15
|
+
if (!this.selectors.panel || !this.selectors.trigger || this.selectors.popup) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const triggerSelector = this.selectors.trigger;
|
|
19
|
+
const panelSelector = this.selectors.panel;
|
|
20
|
+
if (!triggerSelector || !panelSelector) return;
|
|
21
|
+
const allTriggers = await page.locator(triggerSelector).all();
|
|
22
|
+
for (const trigger of allTriggers) {
|
|
23
|
+
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
24
|
+
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
25
|
+
if (isExpanded && triggerPanel) {
|
|
26
|
+
await trigger.click({ timeout: this.actionTimeoutMs });
|
|
27
|
+
const panel = page.locator(`#${triggerPanel}`);
|
|
28
|
+
await (0, test_exports.expect)(panel).toBeHidden({ timeout: this.assertionTimeoutMs }).catch(() => {
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async shouldSkipTest() {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
getMainSelector() {
|
|
37
|
+
return this.mainSelector;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
export {
|
|
41
|
+
AccordionComponentStrategy
|
|
42
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
test_exports
|
|
3
|
+
} from "./chunk-PK5L2SAF.js";
|
|
4
|
+
import "./chunk-I2KLQ2HA.js";
|
|
5
|
+
|
|
6
|
+
// src/utils/test/src/component-strategies/ComboboxComponentStrategy.ts
|
|
7
|
+
var ComboboxComponentStrategy = class {
|
|
8
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
9
|
+
this.mainSelector = mainSelector;
|
|
10
|
+
this.selectors = selectors;
|
|
11
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
12
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
13
|
+
}
|
|
14
|
+
async resetState(page) {
|
|
15
|
+
if (!this.selectors.popup) return;
|
|
16
|
+
const popupSelector = this.selectors.popup;
|
|
17
|
+
const popupElement = page.locator(popupSelector).first();
|
|
18
|
+
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
19
|
+
if (!isPopupVisible) return;
|
|
20
|
+
let listBoxClosed = false;
|
|
21
|
+
let closeSelector = this.selectors.input;
|
|
22
|
+
if (!closeSelector && this.selectors.focusable) {
|
|
23
|
+
closeSelector = this.selectors.focusable;
|
|
24
|
+
} else if (!closeSelector) {
|
|
25
|
+
closeSelector = this.selectors.button;
|
|
26
|
+
}
|
|
27
|
+
if (closeSelector) {
|
|
28
|
+
const closeElement = page.locator(closeSelector).first();
|
|
29
|
+
await closeElement.focus();
|
|
30
|
+
await page.keyboard.press("Escape");
|
|
31
|
+
listBoxClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
32
|
+
}
|
|
33
|
+
if (!listBoxClosed && this.selectors.button) {
|
|
34
|
+
const buttonElement = page.locator(this.selectors.button).first();
|
|
35
|
+
await buttonElement.click({ timeout: this.actionTimeoutMs });
|
|
36
|
+
listBoxClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
37
|
+
}
|
|
38
|
+
if (!listBoxClosed) {
|
|
39
|
+
await page.mouse.click(10, 10);
|
|
40
|
+
listBoxClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
41
|
+
}
|
|
42
|
+
if (!listBoxClosed) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`\u274C FATAL: Cannot close combobox popup between tests. Popup remains visible after trying:
|
|
45
|
+
1. Escape key
|
|
46
|
+
2. Clicking button
|
|
47
|
+
3. Clicking outside
|
|
48
|
+
This indicates a problem with the combobox component's close functionality.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (this.selectors.input) {
|
|
52
|
+
await page.locator(this.selectors.input).first().clear();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async shouldSkipTest() {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
getMainSelector() {
|
|
59
|
+
return this.mainSelector;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
export {
|
|
63
|
+
ComboboxComponentStrategy
|
|
64
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
test_exports
|
|
3
|
+
} from "./chunk-PK5L2SAF.js";
|
|
4
|
+
import "./chunk-I2KLQ2HA.js";
|
|
5
|
+
|
|
6
|
+
// src/utils/test/src/component-strategies/MenuComponentStrategy.ts
|
|
7
|
+
var MenuComponentStrategy = class {
|
|
8
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
9
|
+
this.mainSelector = mainSelector;
|
|
10
|
+
this.selectors = selectors;
|
|
11
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
12
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
13
|
+
}
|
|
14
|
+
async resetState(page) {
|
|
15
|
+
if (!this.selectors.popup) return;
|
|
16
|
+
const popupSelector = this.selectors.popup;
|
|
17
|
+
const popupElement = page.locator(popupSelector).first();
|
|
18
|
+
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
19
|
+
if (!isPopupVisible) return;
|
|
20
|
+
let menuClosed = false;
|
|
21
|
+
let closeSelector = this.selectors.input;
|
|
22
|
+
if (!closeSelector && this.selectors.focusable) {
|
|
23
|
+
closeSelector = this.selectors.focusable;
|
|
24
|
+
} else if (!closeSelector) {
|
|
25
|
+
closeSelector = this.selectors.trigger;
|
|
26
|
+
}
|
|
27
|
+
if (closeSelector) {
|
|
28
|
+
const closeElement = page.locator(closeSelector).first();
|
|
29
|
+
await closeElement.focus();
|
|
30
|
+
await page.keyboard.press("Escape");
|
|
31
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
32
|
+
}
|
|
33
|
+
if (!menuClosed && this.selectors.trigger) {
|
|
34
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
35
|
+
await triggerElement.click({ timeout: this.actionTimeoutMs });
|
|
36
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
37
|
+
}
|
|
38
|
+
if (!menuClosed) {
|
|
39
|
+
await page.mouse.click(10, 10);
|
|
40
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
41
|
+
}
|
|
42
|
+
if (!menuClosed) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
|
|
45
|
+
1. Escape key
|
|
46
|
+
2. Clicking trigger
|
|
47
|
+
3. Clicking outside
|
|
48
|
+
This indicates a problem with the menu component's close functionality.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (this.selectors.input) {
|
|
52
|
+
await page.locator(this.selectors.input).first().clear();
|
|
53
|
+
}
|
|
54
|
+
if (this.selectors.trigger) {
|
|
55
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
56
|
+
await triggerElement.focus();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async shouldSkipTest(test, page) {
|
|
60
|
+
const requiresSubmenu = test.action.some(
|
|
61
|
+
(act) => act.target === "submenu" || act.target === "submenuTrigger" || act.target === "submenuItems"
|
|
62
|
+
) || test.assertions.some(
|
|
63
|
+
(assertion) => assertion.target === "submenu" || assertion.target === "submenuTrigger" || assertion.target === "submenuItems"
|
|
64
|
+
);
|
|
65
|
+
if (!requiresSubmenu) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const submenuTriggerSelector = this.selectors.submenuTrigger;
|
|
69
|
+
if (!submenuTriggerSelector) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
const submenuTriggerCount = await page.locator(submenuTriggerSelector).count();
|
|
73
|
+
return submenuTriggerCount === 0;
|
|
74
|
+
}
|
|
75
|
+
getMainSelector() {
|
|
76
|
+
return this.mainSelector;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
export {
|
|
80
|
+
MenuComponentStrategy
|
|
81
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "./chunk-I2KLQ2HA.js";
|
|
2
|
+
|
|
3
|
+
// src/utils/test/src/component-strategies/TabsComponentStrategy.ts
|
|
4
|
+
var TabsComponentStrategy = class {
|
|
5
|
+
constructor(mainSelector, selectors) {
|
|
6
|
+
this.mainSelector = mainSelector;
|
|
7
|
+
this.selectors = selectors;
|
|
8
|
+
}
|
|
9
|
+
async resetState() {
|
|
10
|
+
}
|
|
11
|
+
async shouldSkipTest(test, page) {
|
|
12
|
+
if (test.isVertical !== void 0 && this.selectors.tablist) {
|
|
13
|
+
const tablistSelector = this.selectors.tablist;
|
|
14
|
+
const tablist = page.locator(tablistSelector).first();
|
|
15
|
+
const orientation = await tablist.getAttribute("aria-orientation");
|
|
16
|
+
const isVertical = orientation === "vertical";
|
|
17
|
+
if (test.isVertical !== isVertical) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
getMainSelector() {
|
|
24
|
+
return this.mainSelector;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
export {
|
|
28
|
+
TabsComponentStrategy
|
|
29
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__export,
|
|
3
|
+
__reExport
|
|
4
|
+
} from "./chunk-I2KLQ2HA.js";
|
|
5
|
+
|
|
6
|
+
// node_modules/@playwright/test/index.mjs
|
|
7
|
+
var test_exports = {};
|
|
8
|
+
__export(test_exports, {
|
|
9
|
+
default: () => default2
|
|
10
|
+
});
|
|
11
|
+
__reExport(test_exports, test_star);
|
|
12
|
+
import * as test_star from "playwright/test";
|
|
13
|
+
import { default as default2 } from "playwright/test";
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
test_exports
|
|
17
|
+
};
|
|
@@ -30,11 +30,13 @@ var ContractReporter = class {
|
|
|
30
30
|
skipped = 0;
|
|
31
31
|
warnings = 0;
|
|
32
32
|
isPlaywright = false;
|
|
33
|
+
isCustomContract = false;
|
|
33
34
|
apgUrl = "https://www.w3.org/WAI/ARIA/apg/";
|
|
34
35
|
hasPrintedStaticSection = false;
|
|
35
36
|
hasPrintedDynamicSection = false;
|
|
36
|
-
constructor(isPlaywright = false) {
|
|
37
|
+
constructor(isPlaywright = false, isCustomContract = false) {
|
|
37
38
|
this.isPlaywright = isPlaywright;
|
|
39
|
+
this.isCustomContract = isCustomContract;
|
|
38
40
|
}
|
|
39
41
|
log(message) {
|
|
40
42
|
process.stderr.write(message + "\n");
|
|
@@ -195,6 +197,13 @@ ${"\u2500".repeat(60)}`);
|
|
|
195
197
|
const totalPasses = this.staticPasses + dynamicPasses;
|
|
196
198
|
const totalFailures = this.staticFailures + dynamicFailures;
|
|
197
199
|
const totalRun = totalPasses + totalFailures + this.warnings;
|
|
200
|
+
const getComponentMessage = () => {
|
|
201
|
+
const componentDisplayName = `${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)}`;
|
|
202
|
+
if (this.isCustomContract) {
|
|
203
|
+
return `${componentDisplayName} component validates against your custom accessibility policy \u2713`;
|
|
204
|
+
}
|
|
205
|
+
return `${componentDisplayName} component meets Aria-Ease baseline WAI-ARIA expectations \u2713`;
|
|
206
|
+
};
|
|
198
207
|
if (failures.length > 0) {
|
|
199
208
|
this.reportFailures(failures);
|
|
200
209
|
}
|
|
@@ -206,7 +215,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
206
215
|
`);
|
|
207
216
|
if (totalFailures === 0 && this.skipped === 0 && this.warnings === 0) {
|
|
208
217
|
this.log(`\u2705 All ${totalRun} tests passed!`);
|
|
209
|
-
this.log(` ${
|
|
218
|
+
this.log(` ${getComponentMessage()}`);
|
|
210
219
|
} else if (totalFailures === 0) {
|
|
211
220
|
this.log(`\u2705 ${totalPasses}/${totalRun} tests passed`);
|
|
212
221
|
if (this.skipped > 0) {
|
|
@@ -215,7 +224,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
215
224
|
if (this.warnings > 0) {
|
|
216
225
|
this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings > 1 ? "s" : ""}`);
|
|
217
226
|
}
|
|
218
|
-
this.log(` ${
|
|
227
|
+
this.log(` ${getComponentMessage()}`);
|
|
219
228
|
} else {
|
|
220
229
|
this.log(`\u274C ${totalFailures} test${totalFailures > 1 ? "s" : ""} failed`);
|
|
221
230
|
this.log(`\u2705 ${totalPasses} test${totalPasses > 1 ? "s" : ""} passed`);
|
|
@@ -56,6 +56,9 @@ function validateConfig(config) {
|
|
|
56
56
|
if (comp.path !== void 0 && typeof comp.path !== "string") {
|
|
57
57
|
errors.push(`test.components[${idx}].path must be a string when provided`);
|
|
58
58
|
}
|
|
59
|
+
if (comp.strategyPath !== void 0 && typeof comp.strategyPath !== "string") {
|
|
60
|
+
errors.push(`test.components[${idx}].strategyPath must be a string when provided`);
|
|
61
|
+
}
|
|
59
62
|
if (comp.strictness !== void 0 && !["minimal", "balanced", "strict", "paranoid"].includes(comp.strictness)) {
|
|
60
63
|
errors.push(`test.components[${idx}].strictness must be one of: minimal, balanced, strict, paranoid`);
|
|
61
64
|
}
|
|
@@ -70,6 +73,24 @@ function validateConfig(config) {
|
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
}
|
|
76
|
+
if (cfg.contracts !== void 0) {
|
|
77
|
+
if (!Array.isArray(cfg.contracts)) {
|
|
78
|
+
errors.push("contracts must be an array");
|
|
79
|
+
} else {
|
|
80
|
+
cfg.contracts.forEach((contract, idx) => {
|
|
81
|
+
if (typeof contract !== "object" || contract === null) {
|
|
82
|
+
errors.push(`contracts[${idx}] must be an object`);
|
|
83
|
+
} else {
|
|
84
|
+
if (typeof contract.src !== "string") {
|
|
85
|
+
errors.push(`contracts[${idx}].src is required and must be a string`);
|
|
86
|
+
}
|
|
87
|
+
if (contract.out !== void 0 && typeof contract.out !== "string") {
|
|
88
|
+
errors.push(`contracts[${idx}].out must be a string`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
73
94
|
return { valid: errors.length === 0, errors };
|
|
74
95
|
}
|
|
75
96
|
async function loadConfigFile(filePath) {
|