aria-ease 6.12.2 → 7.0.0
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/AccordionComponentStrategy-2SWMNUR6.js +1 -0
- package/dist/ComboboxComponentStrategy-YSYLR2U5.js +5 -0
- package/dist/MenuComponentStrategy-C22BZEBH.js +5 -0
- package/dist/RelativeTargetResolver-T4P25J2M.js +1 -0
- package/dist/TabsComponentStrategy-ADEEFJXM.js +1 -0
- package/dist/audit-APAPHXRO.js +9 -0
- package/dist/badgeHelper-IB5RTMAG.js +11 -0
- package/dist/badgeHelper-JSROP5ML.js +1 -0
- package/dist/buildContracts-T4XQZBDU.js +13 -0
- package/dist/chunk-52I3INNG.js +11 -0
- package/dist/chunk-APUMBDOT.js +1 -0
- package/dist/chunk-BHNO4ZI3.js +1 -0
- package/dist/chunk-CNU4N4AY.js +1 -0
- package/dist/chunk-SM6ZKEDR.js +1 -0
- package/dist/chunk-ZNQ5BXVJ.js +1 -0
- package/dist/cli.cjs +132 -3494
- package/dist/cli.js +19 -161
- package/dist/configLoader-ZEJVXLX7.js +1 -0
- package/dist/configLoader-ZXTSCIP6.js +1 -0
- package/dist/contractTestRunnerPlaywright-FOCQTM4L.js +46 -0
- package/dist/contractTestRunnerPlaywright-QPU6HZXG.js +46 -0
- package/dist/formatters-H3CPDLG5.js +87 -0
- package/dist/index.cjs +64 -4657
- package/dist/index.d.cts +23 -2
- package/dist/index.d.ts +23 -2
- package/dist/index.js +17 -2351
- package/dist/src/accordion/index.cjs +1 -183
- package/dist/src/accordion/index.js +1 -181
- package/dist/src/block/index.cjs +1 -124
- package/dist/src/block/index.js +1 -122
- package/dist/src/checkbox/index.cjs +1 -109
- package/dist/src/checkbox/index.js +1 -107
- package/dist/src/combobox/index.cjs +1 -265
- package/dist/src/combobox/index.js +1 -263
- package/dist/src/menu/index.cjs +1 -339
- package/dist/src/menu/index.js +1 -337
- package/dist/src/radio/index.cjs +1 -117
- package/dist/src/radio/index.js +1 -115
- package/dist/src/tabs/index.cjs +1 -265
- package/dist/src/tabs/index.js +1 -263
- package/dist/src/toggle/index.cjs +1 -119
- package/dist/src/toggle/index.js +1 -117
- package/dist/src/utils/test/AccordionComponentStrategy-X2GSQ5KT.js +1 -0
- package/dist/src/utils/test/ComboboxComponentStrategy-SICWLI27.js +5 -0
- package/dist/src/utils/test/MenuComponentStrategy-R4VPAHDE.js +5 -0
- package/dist/src/utils/test/RelativeTargetResolver-UQQMZHI6.js +1 -0
- package/dist/src/utils/test/TabsComponentStrategy-L2PYNEW6.js +1 -0
- package/dist/src/utils/test/badgeHelper-ER5ZOHWF.js +11 -0
- package/dist/src/utils/test/chunk-APUMBDOT.js +1 -0
- package/dist/src/utils/test/chunk-BHNO4ZI3.js +1 -0
- package/dist/src/utils/test/configLoader-NCYRL2O6.js +1 -0
- package/dist/src/utils/test/contractTestRunnerPlaywright-YZCMF64Q.js +46 -0
- package/dist/src/utils/test/dsl/index.cjs +1 -486
- package/dist/src/utils/test/dsl/index.d.cts +21 -0
- package/dist/src/utils/test/dsl/index.d.ts +21 -0
- package/dist/src/utils/test/dsl/index.js +1 -484
- package/dist/src/utils/test/index.cjs +64 -2578
- package/dist/src/utils/test/index.d.cts +2 -2
- package/dist/src/utils/test/index.d.ts +2 -2
- package/dist/src/utils/test/index.js +16 -340
- package/dist/test-VXSCSKV5.js +19 -0
- package/package.json +7 -9
- package/dist/AccordionComponentStrategy-4ZEIQ2V6.js +0 -42
- package/dist/ComboboxComponentStrategy-DU342VMB.js +0 -64
- package/dist/MenuComponentStrategy-JAMTCSNF.js +0 -81
- package/dist/RelativeTargetResolver-DJAITO6D.js +0 -7
- package/dist/TabsComponentStrategy-3SQURPMX.js +0 -29
- package/dist/audit-JYEPKLHR.js +0 -63
- package/dist/badgeHelper-JOWO6RQG.js +0 -15
- package/dist/badgeHelper-RDOMCC6E.js +0 -108
- package/dist/buildContracts-FT6KWUJN.js +0 -465
- package/dist/chunk-4DU5Z5BR.js +0 -340
- package/dist/chunk-GJGUY643.js +0 -182
- package/dist/chunk-GLT43UVH.js +0 -43
- package/dist/chunk-I2KLQ2HA.js +0 -22
- package/dist/chunk-JJEPLK7L.js +0 -107
- package/dist/chunk-PK5L2SAF.js +0 -17
- package/dist/configLoader-Q7N5XV4P.js +0 -183
- package/dist/configLoader-REHK3S3Q.js +0 -7
- package/dist/contractTestRunnerPlaywright-47DCBO4A.js +0 -1300
- package/dist/contractTestRunnerPlaywright-UJKXRXBS.js +0 -1300
- package/dist/formatters-32KQIIYS.js +0 -183
- package/dist/src/utils/test/AccordionComponentStrategy-WRHZOEN6.js +0 -38
- package/dist/src/utils/test/ComboboxComponentStrategy-XKQ72RFD.js +0 -60
- package/dist/src/utils/test/MenuComponentStrategy-VKZQYLBE.js +0 -77
- package/dist/src/utils/test/RelativeTargetResolver-G2XDN2VV.js +0 -1
- package/dist/src/utils/test/TabsComponentStrategy-BKG53SEV.js +0 -26
- package/dist/src/utils/test/badgeHelper-HZKGOPB4.js +0 -102
- package/dist/src/utils/test/chunk-4DU5Z5BR.js +0 -332
- package/dist/src/utils/test/chunk-GLT43UVH.js +0 -41
- package/dist/src/utils/test/configLoader-NA7IBCS3.js +0 -181
- package/dist/src/utils/test/contractTestRunnerPlaywright-AZ4QKLYT.js +0 -1278
- package/dist/test-6Y4CIQOM.js +0 -358
|
@@ -16,13 +16,13 @@ type StrictnessMode = 'minimal' | 'balanced' | 'strict' | 'paranoid';
|
|
|
16
16
|
* Runs static and interactions accessibility test on UI components.
|
|
17
17
|
* @param {string} componentName The name of the component contract to test against
|
|
18
18
|
* @param {HTMLElement} component The UI component to be tested
|
|
19
|
-
* @param {string} url
|
|
19
|
+
* @param {string} url URL for Playwright E2E tests
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
type TestAuditOptions = {
|
|
23
23
|
strictness?: StrictnessMode;
|
|
24
24
|
};
|
|
25
|
-
declare function testUiComponent(componentName: string,
|
|
25
|
+
declare function testUiComponent(componentName: string, url: string | null, options?: TestAuditOptions): Promise<JestAxeResult>;
|
|
26
26
|
declare let runTest: () => Promise<ContractTestResult>;
|
|
27
27
|
/**
|
|
28
28
|
* Cleanup function to close the shared Playwright browser
|
|
@@ -16,13 +16,13 @@ type StrictnessMode = 'minimal' | 'balanced' | 'strict' | 'paranoid';
|
|
|
16
16
|
* Runs static and interactions accessibility test on UI components.
|
|
17
17
|
* @param {string} componentName The name of the component contract to test against
|
|
18
18
|
* @param {HTMLElement} component The UI component to be tested
|
|
19
|
-
* @param {string} url
|
|
19
|
+
* @param {string} url URL for Playwright E2E tests
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
type TestAuditOptions = {
|
|
23
23
|
strictness?: StrictnessMode;
|
|
24
24
|
};
|
|
25
|
-
declare function testUiComponent(componentName: string,
|
|
25
|
+
declare function testUiComponent(componentName: string, url: string | null, options?: TestAuditOptions): Promise<JestAxeResult>;
|
|
26
26
|
declare let runTest: () => Promise<ContractTestResult>;
|
|
27
27
|
/**
|
|
28
28
|
* Cleanup function to close the shared Playwright browser
|
|
@@ -1,343 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
async function runContractTests(contractPath, componentName, component, strictness) {
|
|
7
|
-
const reporter = new ContractReporter(false);
|
|
8
|
-
const strictnessMode = normalizeStrictness(strictness);
|
|
9
|
-
if (!contractPath) {
|
|
10
|
-
throw new Error(`No contract path provided for component: ${componentName}`);
|
|
11
|
-
}
|
|
12
|
-
const contractData = await fs.readFile(contractPath, "utf-8");
|
|
13
|
-
const componentContract = JSON.parse(contractData);
|
|
14
|
-
const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
|
|
15
|
-
reporter.start(componentName, totalTests);
|
|
16
|
-
const failures = [];
|
|
17
|
-
const passes = [];
|
|
18
|
-
const skipped = [];
|
|
19
|
-
const warnings = [];
|
|
20
|
-
const classifyFailure = (message, levelRaw) => {
|
|
21
|
-
const level = normalizeLevel(levelRaw);
|
|
22
|
-
const enforcement = resolveEnforcement(level, strictnessMode);
|
|
23
|
-
if (enforcement === "error") {
|
|
24
|
-
failures.push(message);
|
|
25
|
-
return { status: "fail", level, detail: message };
|
|
26
|
-
}
|
|
27
|
-
if (enforcement === "warning") {
|
|
28
|
-
warnings.push(message);
|
|
29
|
-
return { status: "warn", level, detail: message };
|
|
30
|
-
}
|
|
31
|
-
const ignoredMessage = `${message} (ignored by strictness=${strictnessMode}, level=${level})`;
|
|
32
|
-
skipped.push(ignoredMessage);
|
|
33
|
-
return { status: "skip", level, detail: ignoredMessage };
|
|
34
|
-
};
|
|
35
|
-
let staticPassed = 0;
|
|
36
|
-
let staticFailed = 0;
|
|
37
|
-
let staticWarnings = 0;
|
|
38
|
-
for (const rel of componentContract.relationships || []) {
|
|
39
|
-
const relationshipLevel = normalizeLevel(rel.level);
|
|
40
|
-
if (rel.type === "aria-reference") {
|
|
41
|
-
const fromSelector = componentContract.selectors[rel.from];
|
|
42
|
-
const toSelector = componentContract.selectors[rel.to];
|
|
43
|
-
const relDescription = `${rel.from}.${rel.attribute} references ${rel.to}`;
|
|
44
|
-
if (!fromSelector || !toSelector) {
|
|
45
|
-
const outcome = classifyFailure(`Relationship selector missing: from="${rel.from}" or to="${rel.to}" not found in selectors.`, rel.level);
|
|
46
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
47
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
48
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
const fromTarget = component.querySelector(fromSelector);
|
|
52
|
-
const toTarget = component.querySelector(toSelector);
|
|
53
|
-
if (!fromTarget || !toTarget) {
|
|
54
|
-
const outcome = classifyFailure(`Relationship target not found: ${!fromTarget ? rel.from : rel.to}.`, rel.level);
|
|
55
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
56
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
57
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
const toId = toTarget.getAttribute("id");
|
|
61
|
-
const attrValue = fromTarget.getAttribute(rel.attribute) || "";
|
|
62
|
-
if (!toId) {
|
|
63
|
-
const outcome = classifyFailure(`Relationship target "${rel.to}" must have an id for ${rel.attribute} validation.`, rel.level);
|
|
64
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
65
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
66
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
const references = attrValue.split(/\s+/).filter(Boolean);
|
|
70
|
-
if (!references.includes(toId)) {
|
|
71
|
-
const outcome = classifyFailure(`Expected ${rel.from} ${rel.attribute} to reference id "${toId}", found "${attrValue}".`, rel.level);
|
|
72
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
73
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
74
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
passes.push(`Relationship valid: ${rel.from}.${rel.attribute} -> ${rel.to} (id=${toId}).`);
|
|
78
|
-
staticPassed += 1;
|
|
79
|
-
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
if (rel.type === "contains") {
|
|
83
|
-
const parentSelector = componentContract.selectors[rel.parent];
|
|
84
|
-
const childSelector = componentContract.selectors[rel.child];
|
|
85
|
-
const relDescription = `${rel.parent} contains ${rel.child}`;
|
|
86
|
-
if (!parentSelector || !childSelector) {
|
|
87
|
-
const outcome = classifyFailure(`Relationship selector missing: parent="${rel.parent}" or child="${rel.child}" not found in selectors.`, rel.level);
|
|
88
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
89
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
90
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
const parentTarget = component.querySelector(parentSelector);
|
|
94
|
-
if (!parentTarget) {
|
|
95
|
-
const outcome = classifyFailure(`Relationship parent target not found: ${rel.parent}.`, rel.level);
|
|
96
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
97
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
98
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
const nestedChild = parentTarget.querySelector(childSelector);
|
|
102
|
-
if (!nestedChild) {
|
|
103
|
-
const outcome = classifyFailure(`Expected ${rel.parent} to contain descendant matching selector for ${rel.child}.`, rel.level);
|
|
104
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
105
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
106
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
passes.push(`Relationship valid: ${rel.parent} contains ${rel.child}.`);
|
|
110
|
-
staticPassed += 1;
|
|
111
|
-
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
for (const test of componentContract.static[0].assertions) {
|
|
115
|
-
if (test.target !== "relative") {
|
|
116
|
-
const staticLevel = normalizeLevel(test.level);
|
|
117
|
-
const selector = componentContract.selectors[test.target];
|
|
118
|
-
if (!selector) {
|
|
119
|
-
const outcome = classifyFailure(`Selector for target ${test.target} not found.`, test.level);
|
|
120
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
121
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
122
|
-
reporter.reportStaticTest(`${test.target} has required ARIA attributes`, outcome.status, outcome.detail, outcome.level);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
const target = component.querySelector(selector);
|
|
126
|
-
if (!target) {
|
|
127
|
-
const outcome = classifyFailure(`Target ${test.target} not found.`, test.level);
|
|
128
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
129
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
130
|
-
reporter.reportStaticTest(`${test.target} has required ARIA attributes`, outcome.status, outcome.detail, outcome.level);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
const attributeValue = target.getAttribute(test.attribute);
|
|
134
|
-
if (!test.expectedValue) {
|
|
135
|
-
const attributes = test.attribute.split(" | ");
|
|
136
|
-
const hasAnyAttribute = attributes.some((attr) => target.hasAttribute(attr));
|
|
137
|
-
if (!hasAnyAttribute) {
|
|
138
|
-
const outcome = classifyFailure(test.failureMessage + ` None of the attributes "${test.attribute}" are present.`, test.level);
|
|
139
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
140
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
141
|
-
reporter.reportStaticTest(`${test.target} has ${test.attribute}`, outcome.status, outcome.detail, outcome.level);
|
|
142
|
-
} else {
|
|
143
|
-
passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
|
|
144
|
-
staticPassed += 1;
|
|
145
|
-
reporter.reportStaticTest(`${test.target} has ${test.attribute}`, "pass", void 0, staticLevel);
|
|
146
|
-
}
|
|
147
|
-
} else if (!attributeValue || typeof test.expectedValue === "string" && !test.expectedValue.split(" | ").includes(attributeValue)) {
|
|
148
|
-
const outcome = classifyFailure(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`, test.level);
|
|
149
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
150
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
151
|
-
reporter.reportStaticTest(`${test.target} has ${test.attribute}="${test.expectedValue}"`, outcome.status, outcome.detail, outcome.level);
|
|
152
|
-
} else {
|
|
153
|
-
passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
|
|
154
|
-
staticPassed += 1;
|
|
155
|
-
reporter.reportStaticTest(`${test.target} has ${test.attribute}="${attributeValue}"`, "pass", void 0, staticLevel);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
for (const dynamicTest of componentContract.dynamic) {
|
|
160
|
-
skipped.push(dynamicTest.description);
|
|
161
|
-
reporter.reportTest({ description: dynamicTest.description, level: dynamicTest.level }, "skip");
|
|
162
|
-
}
|
|
163
|
-
reporter.reportStatic(staticPassed, staticFailed, staticWarnings);
|
|
164
|
-
reporter.summary(failures);
|
|
165
|
-
return { passes, failures, skipped, warnings };
|
|
166
|
-
}
|
|
167
|
-
async function testUiComponent(componentName, component, url, options = {}) {
|
|
168
|
-
if (!componentName || typeof componentName !== "string") {
|
|
169
|
-
throw new Error("\u274C testUiComponent requires a valid componentName (string)");
|
|
170
|
-
}
|
|
171
|
-
if (!url && (!component || !(component instanceof HTMLElement))) {
|
|
172
|
-
throw new Error("\u274C testUiComponent requires either a valid component (HTMLElement) or a URL");
|
|
173
|
-
}
|
|
174
|
-
if (url && typeof url !== "string") {
|
|
175
|
-
throw new Error("\u274C testUiComponent url parameter must be a string");
|
|
176
|
-
}
|
|
177
|
-
let results;
|
|
178
|
-
if (component) {
|
|
179
|
-
try {
|
|
180
|
-
results = await axe(component);
|
|
181
|
-
} catch (error) {
|
|
182
|
-
throw new Error(
|
|
183
|
-
`\u274C Axe accessibility scan failed
|
|
184
|
-
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
} else {
|
|
188
|
-
results = { violations: [] };
|
|
189
|
-
}
|
|
190
|
-
async function checkDevServer(url2) {
|
|
191
|
-
try {
|
|
192
|
-
const response = await fetch(url2, {
|
|
193
|
-
method: "HEAD",
|
|
194
|
-
signal: AbortSignal.timeout(1e3)
|
|
195
|
-
});
|
|
196
|
-
if (response.ok || response.status === 304) {
|
|
197
|
-
return url2;
|
|
198
|
-
}
|
|
199
|
-
} catch {
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
let strictness = normalizeStrictness(options.strictness);
|
|
205
|
-
let config = {};
|
|
206
|
-
let configBaseDir = typeof process !== "undefined" ? process.cwd() : "";
|
|
207
|
-
if (typeof process !== "undefined" && typeof process.cwd === "function") {
|
|
208
|
-
try {
|
|
209
|
-
const { loadConfig } = await import('./configLoader-NA7IBCS3.js');
|
|
210
|
-
const result2 = await loadConfig(process.cwd());
|
|
211
|
-
config = result2.config;
|
|
212
|
-
if (result2.configPath) {
|
|
213
|
-
configBaseDir = path.dirname(result2.configPath);
|
|
214
|
-
}
|
|
215
|
-
if (options.strictness === void 0) {
|
|
216
|
-
const componentStrictness = config.test?.components?.find((comp) => comp?.name === componentName)?.strictness;
|
|
217
|
-
strictness = normalizeStrictness(componentStrictness ?? config.test?.strictness);
|
|
218
|
-
}
|
|
219
|
-
} catch {
|
|
220
|
-
if (options.strictness === void 0) {
|
|
221
|
-
strictness = "balanced";
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
let contract;
|
|
226
|
-
try {
|
|
227
|
-
if (url) {
|
|
228
|
-
const devServerUrl = await checkDevServer(url);
|
|
229
|
-
if (devServerUrl) {
|
|
230
|
-
console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
|
|
231
|
-
const { runContractTestsPlaywright } = await import('./contractTestRunnerPlaywright-AZ4QKLYT.js');
|
|
232
|
-
contract = await runContractTestsPlaywright(componentName, devServerUrl, strictness, config, configBaseDir);
|
|
233
|
-
} else {
|
|
234
|
-
throw new Error(
|
|
235
|
-
`\u274C Dev server not running at ${url}
|
|
236
|
-
Please start your dev server and try again.`
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
} else if (component) {
|
|
240
|
-
console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
|
|
241
|
-
const contractPath = config.test?.components?.find((comp) => comp?.name === componentName)?.contractPath;
|
|
242
|
-
if (!contractPath) {
|
|
243
|
-
throw new Error(`\u274C No contract path found for component: ${componentName}`);
|
|
244
|
-
}
|
|
245
|
-
contract = await runContractTests(
|
|
246
|
-
path.resolve(configBaseDir, contractPath),
|
|
247
|
-
componentName,
|
|
248
|
-
component,
|
|
249
|
-
strictness
|
|
250
|
-
);
|
|
251
|
-
} else {
|
|
252
|
-
throw new Error("\u274C Either component or URL must be provided");
|
|
253
|
-
}
|
|
254
|
-
} catch (error) {
|
|
255
|
-
if (error instanceof Error) {
|
|
256
|
-
throw error;
|
|
257
|
-
}
|
|
258
|
-
throw new Error(`\u274C Contract test execution failed: ${String(error)}`);
|
|
259
|
-
}
|
|
260
|
-
const result = {
|
|
261
|
-
violations: results.violations,
|
|
262
|
-
raw: results,
|
|
263
|
-
contract
|
|
264
|
-
};
|
|
265
|
-
if (contract.failures.length > 0 && url === "Playwright") {
|
|
266
|
-
throw new Error(
|
|
267
|
-
`
|
|
268
|
-
\u274C ${contract.failures.length} accessibility contract test${contract.failures.length > 1 ? "s" : ""} failed (Playwright mode)
|
|
269
|
-
\u2705 ${contract.passes.length} test${contract.passes.length > 1 ? "s" : ""} passed
|
|
1
|
+
import {d,b}from'./chunk-APUMBDOT.js';import m from'path';async function y(a,e,c={}){if(!a||typeof a!="string")throw new Error("\u274C testUiComponent requires a valid componentName (string)");if(!e)throw new Error("\u274C testUiComponent requires a URL");if(e&&typeof e!="string")throw new Error("\u274C testUiComponent url parameter must be a string");let i={violations:[]};async function l(t){try{let n=await fetch(t,{method:"HEAD",signal:AbortSignal.timeout(1e3)});if(n.ok||n.status===304)return t}catch{return null}return null}let p=d(c.strictness),o={},d$1=typeof process<"u"?process.cwd():"";if(typeof process<"u"&&typeof process.cwd=="function")try{let{loadConfig:t}=await import('./configLoader-NCYRL2O6.js'),n=await t(process.cwd());if(o=n.config,n.configPath&&(d$1=m.dirname(n.configPath)),c.strictness===void 0){let r=o.test?.components?.find(h=>h?.name===a)?.strictness;p=d(r??o.test?.strictness);}}catch{c.strictness===void 0&&(p="balanced");}let s;try{if(e){let t=await l(e);if(t){console.log(`\u{1F3AD} Running Playwright tests on ${t}`);let{runContractTestsPlaywright:n}=await import('./contractTestRunnerPlaywright-YZCMF64Q.js');s=await n(a,t,p,o,d$1);}else throw new Error(`\u274C Dev server not running at ${e}
|
|
2
|
+
Please start your dev server and try again.`)}else throw new Error("\u274C URL is required for component testing. Please provide a URL to run full accessibility tests with testUiComponent.")}catch(t){throw t instanceof Error?t:new Error(`\u274C Contract test execution failed: ${String(t)}`)}let u={violations:i.violations,raw:i,contract:s};if(s.failures.length>0&&e==="Playwright")throw new Error(`
|
|
3
|
+
\u274C ${s.failures.length} accessibility contract test${s.failures.length>1?"s":""} failed (Playwright mode)
|
|
4
|
+
\u2705 ${s.passes.length} test${s.passes.length>1?"s":""} passed
|
|
270
5
|
|
|
271
6
|
\u{1F4CB} Review the detailed test report above for specific failures.
|
|
272
|
-
\u{1F4A1} Contract tests validate ARIA attributes and keyboard interactions per W3C APG guidelines.`
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
Impact: ${v.impact}
|
|
281
|
-
Affected elements: ${v.nodes.length}
|
|
282
|
-
Help: ${v.helpUrl}`
|
|
283
|
-
).join("\n");
|
|
284
|
-
throw new Error(
|
|
285
|
-
`
|
|
286
|
-
\u274C ${violationCount} axe accessibility violation${violationCount > 1 ? "s" : ""} detected
|
|
287
|
-
${violationDetails}
|
|
288
|
-
|
|
289
|
-
\u{1F4CB} Full details available in result.violations`
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
return result;
|
|
293
|
-
}
|
|
294
|
-
var runTest = async () => {
|
|
295
|
-
return {
|
|
296
|
-
passes: [],
|
|
297
|
-
failures: [],
|
|
298
|
-
skipped: []
|
|
299
|
-
};
|
|
300
|
-
};
|
|
301
|
-
if (typeof window === "undefined") {
|
|
302
|
-
runTest = async () => {
|
|
303
|
-
console.log(`\u{1F680} Running component accessibility tests...
|
|
304
|
-
`);
|
|
305
|
-
const { exec } = await import('child_process');
|
|
306
|
-
const chalk = (await import('chalk')).default;
|
|
307
|
-
return new Promise((resolve, reject) => {
|
|
308
|
-
exec(
|
|
309
|
-
`npx vitest --run --reporter verbose`,
|
|
310
|
-
async (error, stdout, stderr) => {
|
|
311
|
-
console.log(stdout);
|
|
312
|
-
if (stderr) console.error(stderr);
|
|
313
|
-
const testsPassed = !error || error.code === 0;
|
|
314
|
-
if (testsPassed) {
|
|
315
|
-
try {
|
|
316
|
-
const { displayBadgeInfo, promptAddBadge } = await import('./badgeHelper-HZKGOPB4.js');
|
|
317
|
-
displayBadgeInfo("component");
|
|
318
|
-
await promptAddBadge("component", process.cwd());
|
|
319
|
-
console.log(chalk.dim("\n" + "\u2500".repeat(60)));
|
|
320
|
-
console.log(chalk.cyan("\u{1F499} Found aria-ease helpful?"));
|
|
321
|
-
console.log(chalk.white(" \u2022 Star us on GitHub: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease"));
|
|
322
|
-
console.log(chalk.white(" \u2022 Share feedback: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease/discussions"));
|
|
323
|
-
console.log(chalk.dim("\u2500".repeat(60) + "\n"));
|
|
324
|
-
} catch (badgeError) {
|
|
325
|
-
console.error("Warning: Could not display badge prompt:", badgeError);
|
|
326
|
-
}
|
|
327
|
-
resolve({ passes: [], failures: [], skipped: [] });
|
|
328
|
-
process.exit(0);
|
|
329
|
-
} else {
|
|
330
|
-
const exitCode = error?.code || 1;
|
|
331
|
-
reject(new Error(`Tests failed with code ${exitCode}`));
|
|
332
|
-
process.exit(exitCode);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
);
|
|
336
|
-
});
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
async function cleanupTests() {
|
|
340
|
-
await closeSharedBrowser();
|
|
341
|
-
}
|
|
7
|
+
\u{1F4A1} Contract tests validate ARIA attributes and keyboard interactions per W3C APG guidelines.`);if(i.violations.length>0){let t=i.violations.length,n=i.violations.map(r=>`
|
|
8
|
+
- ${r.id}: ${r.description}
|
|
9
|
+
Impact: ${r.impact}
|
|
10
|
+
Affected elements: ${r.nodes.length}
|
|
11
|
+
Help: ${r.helpUrl}`).join(`
|
|
12
|
+
`);throw new Error(`
|
|
13
|
+
\u274C ${t} axe accessibility violation${t>1?"s":""} detected
|
|
14
|
+
${n}
|
|
342
15
|
|
|
343
|
-
|
|
16
|
+
\u{1F4CB} Full details available in result.violations`)}return u}var w=async()=>({passes:[],failures:[],skipped:[]});typeof window>"u"&&(w=async()=>{console.log(`\u{1F680} Running component accessibility tests...
|
|
17
|
+
`);let{exec:a}=await import('child_process'),e=(await import('chalk')).default;return new Promise((c,i)=>{a("npx vitest --run --reporter verbose",async(l,p,o)=>{if(console.log(p),o&&console.error(o),!l||l.code===0){try{let{displayBadgeInfo:s,promptAddBadge:u}=await import('./badgeHelper-ER5ZOHWF.js');s("component"),await u("component",process.cwd()),console.log(e.dim(`
|
|
18
|
+
`+"\u2500".repeat(60))),console.log(e.cyan("\u{1F499} Found aria-ease helpful?")),console.log(e.white(" \u2022 Star us on GitHub: ")+e.blue.underline("https://github.com/aria-ease/aria-ease")),console.log(e.white(" \u2022 Share feedback: ")+e.blue.underline("https://github.com/aria-ease/aria-ease/discussions")),console.log(e.dim("\u2500".repeat(60)+`
|
|
19
|
+
`));}catch(s){console.error("Warning: Could not display badge prompt:",s);}c({passes:[],failures:[],skipped:[]}),process.exit(0);}else {let s=l?.code||1;i(new Error(`Tests failed with code ${s}`)),process.exit(s);}});})});async function v(){await b();}export{v as cleanupTests,w as runTest,y as testUiComponent};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import{b as g,d as f}from"./chunk-APUMBDOT.js";import"./chunk-CNU4N4AY.js";import m from"path";async function y(a,e,c={}){if(!a||typeof a!="string")throw new Error("\u274C testUiComponent requires a valid componentName (string)");if(!e)throw new Error("\u274C testUiComponent requires a URL");if(e&&typeof e!="string")throw new Error("\u274C testUiComponent url parameter must be a string");let i={violations:[]};async function l(t){try{let n=await fetch(t,{method:"HEAD",signal:AbortSignal.timeout(1e3)});if(n.ok||n.status===304)return t}catch{return null}return null}let p=f(c.strictness),o={},d=typeof process<"u"?process.cwd():"";if(typeof process<"u"&&typeof process.cwd=="function")try{let{loadConfig:t}=await import("./configLoader-ZXTSCIP6.js"),n=await t(process.cwd());if(o=n.config,n.configPath&&(d=m.dirname(n.configPath)),c.strictness===void 0){let r=o.test?.components?.find(h=>h?.name===a)?.strictness;p=f(r??o.test?.strictness)}}catch{c.strictness===void 0&&(p="balanced")}let s;try{if(e){let t=await l(e);if(t){console.log(`\u{1F3AD} Running Playwright tests on ${t}`);let{runContractTestsPlaywright:n}=await import("./contractTestRunnerPlaywright-FOCQTM4L.js");s=await n(a,t,p,o,d)}else throw new Error(`\u274C Dev server not running at ${e}
|
|
2
|
+
Please start your dev server and try again.`)}else throw new Error("\u274C URL is required for component testing. Please provide a URL to run full accessibility tests with testUiComponent.")}catch(t){throw t instanceof Error?t:new Error(`\u274C Contract test execution failed: ${String(t)}`)}let u={violations:i.violations,raw:i,contract:s};if(s.failures.length>0&&e==="Playwright")throw new Error(`
|
|
3
|
+
\u274C ${s.failures.length} accessibility contract test${s.failures.length>1?"s":""} failed (Playwright mode)
|
|
4
|
+
\u2705 ${s.passes.length} test${s.passes.length>1?"s":""} passed
|
|
5
|
+
|
|
6
|
+
\u{1F4CB} Review the detailed test report above for specific failures.
|
|
7
|
+
\u{1F4A1} Contract tests validate ARIA attributes and keyboard interactions per W3C APG guidelines.`);if(i.violations.length>0){let t=i.violations.length,n=i.violations.map(r=>`
|
|
8
|
+
- ${r.id}: ${r.description}
|
|
9
|
+
Impact: ${r.impact}
|
|
10
|
+
Affected elements: ${r.nodes.length}
|
|
11
|
+
Help: ${r.helpUrl}`).join(`
|
|
12
|
+
`);throw new Error(`
|
|
13
|
+
\u274C ${t} axe accessibility violation${t>1?"s":""} detected
|
|
14
|
+
${n}
|
|
15
|
+
|
|
16
|
+
\u{1F4CB} Full details available in result.violations`)}return u}var w=async()=>({passes:[],failures:[],skipped:[]});typeof window>"u"&&(w=async()=>{console.log(`\u{1F680} Running component accessibility tests...
|
|
17
|
+
`);let{exec:a}=await import("child_process"),e=(await import("chalk")).default;return new Promise((c,i)=>{a("npx vitest --run --reporter verbose",async(l,p,o)=>{if(console.log(p),o&&console.error(o),!l||l.code===0){try{let{displayBadgeInfo:s,promptAddBadge:u}=await import("./badgeHelper-JSROP5ML.js");s("component"),await u("component",process.cwd()),console.log(e.dim(`
|
|
18
|
+
`+"\u2500".repeat(60))),console.log(e.cyan("\u{1F499} Found aria-ease helpful?")),console.log(e.white(" \u2022 Star us on GitHub: ")+e.blue.underline("https://github.com/aria-ease/aria-ease")),console.log(e.white(" \u2022 Share feedback: ")+e.blue.underline("https://github.com/aria-ease/aria-ease/discussions")),console.log(e.dim("\u2500".repeat(60)+`
|
|
19
|
+
`))}catch(s){console.error("Warning: Could not display badge prompt:",s)}c({passes:[],failures:[],skipped:[]}),process.exit(0)}else{let s=l?.code||1;i(new Error(`Tests failed with code ${s}`)),process.exit(s)}})})});async function v(){await g()}export{v as cleanupTests,w as runTest,y as testUiComponent};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aria-ease",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "Accessibility infrastructure for the entire frontend engineering lifecycle. Build accessible patterns, run automated audits, verify component interactions, and gate deployments — all in one system.",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"release:minor": "standard-version --release-as minor",
|
|
18
18
|
"release:major": "standard-version --release-as major",
|
|
19
19
|
"clean": "rm -rf dist",
|
|
20
|
-
"build:core": "tsup ./index.ts --format esm,cjs --dts --outDir dist --external
|
|
21
|
-
"build:modules": "tsup ./src/combobox/index.ts ./src/accordion/index.ts ./src/block/index.ts ./src/checkbox/index.ts ./src/menu/index.ts ./src/tabs/index.ts ./src/radio/index.ts ./src/toggle/index.ts ./src/contracts/index.ts --format esm,cjs --dts --treeshake --outDir dist/src",
|
|
22
|
-
"build:test": "tsup ./src/utils/test/index.ts --format esm,cjs --dts --treeshake --external
|
|
23
|
-
"build:dsl": "tsup ./src/utils/test/dsl/index.ts --format esm,cjs --dts --treeshake --outDir dist/src/utils/test/dsl",
|
|
24
|
-
"build:cli": "tsup ./src/utils/cli/cli.ts --format esm,cjs --dts --outDir dist --external commander --external chalk --external
|
|
20
|
+
"build:core": "tsup ./index.ts --format esm,cjs --dts --outDir dist --external @testing-library/react --external @axe-core/playwright --external playwright --minify",
|
|
21
|
+
"build:modules": "tsup ./src/combobox/index.ts ./src/accordion/index.ts ./src/block/index.ts ./src/checkbox/index.ts ./src/menu/index.ts ./src/tabs/index.ts ./src/radio/index.ts ./src/toggle/index.ts ./src/contracts/index.ts --format esm,cjs --dts --treeshake --outDir dist/src --minify",
|
|
22
|
+
"build:test": "tsup ./src/utils/test/index.ts --format esm,cjs --dts --treeshake --external @testing-library/react --external playwright --external @playwright/test --outDir dist/src/utils/test --minify",
|
|
23
|
+
"build:dsl": "tsup ./src/utils/test/dsl/index.ts --format esm,cjs --dts --treeshake --outDir dist/src/utils/test/dsl --minify",
|
|
24
|
+
"build:cli": "tsup ./src/utils/cli/cli.ts --format esm,cjs --dts --outDir dist --external commander --external chalk --external @testing-library/react --external @axe-core/playwright --external playwright --minify",
|
|
25
25
|
"build": "npm run clean && npm run build:core && npm run build:modules && npm run build:test && npm run build:dsl && npm run build:cli"
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
@@ -54,11 +54,9 @@
|
|
|
54
54
|
"@testing-library/dom": "^10.4.1",
|
|
55
55
|
"@testing-library/react": "^16.3.0",
|
|
56
56
|
"@types/fs-extra": "^11.0.4",
|
|
57
|
-
"@types/jest-axe": "^3.5.9",
|
|
58
57
|
"benchmark": "^2.1.4",
|
|
59
58
|
"eslint": "^9.34.0",
|
|
60
|
-
"
|
|
61
|
-
"jsdom": "^26.1.0",
|
|
59
|
+
"jsdom": "^29.0.1",
|
|
62
60
|
"prettier": "^3.6.2",
|
|
63
61
|
"standard-version": "^9.5.0",
|
|
64
62
|
"tsup": "^8.5.0",
|
|
@@ -1,42 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,64 +0,0 @@
|
|
|
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 popupClosed = 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
|
-
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
32
|
-
}
|
|
33
|
-
if (!popupClosed && this.selectors.button) {
|
|
34
|
-
const buttonElement = page.locator(this.selectors.button).first();
|
|
35
|
-
await buttonElement.click({ timeout: this.actionTimeoutMs });
|
|
36
|
-
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
37
|
-
}
|
|
38
|
-
if (!popupClosed) {
|
|
39
|
-
await page.mouse.click(10, 10);
|
|
40
|
-
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
41
|
-
}
|
|
42
|
-
if (!popupClosed) {
|
|
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
|
-
};
|
|
@@ -1,81 +0,0 @@
|
|
|
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
|
-
};
|