aria-ease 6.12.1 → 6.14.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.
@@ -22,7 +22,7 @@ var MenuComponentStrategy = class {
22
22
  if (!closeSelector && this.selectors.focusable) {
23
23
  closeSelector = this.selectors.focusable;
24
24
  } else if (!closeSelector) {
25
- closeSelector = this.selectors.trigger;
25
+ closeSelector = this.selectors.main;
26
26
  }
27
27
  if (closeSelector) {
28
28
  const closeElement = page.locator(closeSelector).first();
@@ -30,8 +30,8 @@ var MenuComponentStrategy = class {
30
30
  await page.keyboard.press("Escape");
31
31
  menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
32
32
  }
33
- if (!menuClosed && this.selectors.trigger) {
34
- const triggerElement = page.locator(this.selectors.trigger).first();
33
+ if (!menuClosed && this.selectors.main) {
34
+ const triggerElement = page.locator(this.selectors.main).first();
35
35
  await triggerElement.click({ timeout: this.actionTimeoutMs });
36
36
  menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
37
37
  }
@@ -51,8 +51,8 @@ This indicates a problem with the menu component's close functionality.`
51
51
  if (this.selectors.input) {
52
52
  await page.locator(this.selectors.input).first().clear();
53
53
  }
54
- if (this.selectors.trigger) {
55
- const triggerElement = page.locator(this.selectors.trigger).first();
54
+ if (this.selectors.main) {
55
+ const triggerElement = page.locator(this.selectors.main).first();
56
56
  await triggerElement.focus();
57
57
  }
58
58
  }
@@ -214,25 +214,6 @@ function validateContractSchema(contract) {
214
214
  });
215
215
  }
216
216
  }
217
- if (c.states !== void 0) {
218
- if (!Array.isArray(c.states)) {
219
- errors.push({ path: "$.states", message: "states must be an array" });
220
- } else {
221
- c.states.forEach((state, idx) => {
222
- if (typeof state !== "object" || state === null) {
223
- errors.push({ path: `$.states[${idx}]`, message: "state must be an object" });
224
- return;
225
- }
226
- const s = state;
227
- if (typeof s.name !== "string") {
228
- errors.push({ path: `$.states[${idx}].name`, message: "name is required and must be a string" });
229
- }
230
- if (!Array.isArray(s.requires)) {
231
- errors.push({ path: `$.states[${idx}].requires`, message: "requires is required and must be an array" });
232
- }
233
- });
234
- }
235
- }
236
217
  return {
237
218
  valid: errors.length === 0,
238
219
  errors
@@ -308,17 +289,8 @@ function validateTargetReferences(contract, selectorKeys) {
308
289
  });
309
290
  }
310
291
  const dynamicItems = c.dynamic;
311
- const states = c.states;
312
- const stateNames = new Set((states || []).map((s) => String(s.name)));
313
292
  if (Array.isArray(dynamicItems)) {
314
293
  dynamicItems.forEach((item, itemIdx) => {
315
- const given = item.given;
316
- if (given && !stateNames.has(given)) {
317
- errors.push({
318
- path: `$.dynamic[${itemIdx}].given`,
319
- message: `State '${given}' not found in states`
320
- });
321
- }
322
294
  const actions = item.action;
323
295
  if (Array.isArray(actions)) {
324
296
  actions.forEach((action, actIdx) => {
package/dist/cli.cjs CHANGED
@@ -1155,7 +1155,7 @@ var init_MenuComponentStrategy = __esm({
1155
1155
  if (!closeSelector && this.selectors.focusable) {
1156
1156
  closeSelector = this.selectors.focusable;
1157
1157
  } else if (!closeSelector) {
1158
- closeSelector = this.selectors.trigger;
1158
+ closeSelector = this.selectors.main;
1159
1159
  }
1160
1160
  if (closeSelector) {
1161
1161
  const closeElement = page.locator(closeSelector).first();
@@ -1163,8 +1163,8 @@ var init_MenuComponentStrategy = __esm({
1163
1163
  await page.keyboard.press("Escape");
1164
1164
  menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
1165
1165
  }
1166
- if (!menuClosed && this.selectors.trigger) {
1167
- const triggerElement = page.locator(this.selectors.trigger).first();
1166
+ if (!menuClosed && this.selectors.main) {
1167
+ const triggerElement = page.locator(this.selectors.main).first();
1168
1168
  await triggerElement.click({ timeout: this.actionTimeoutMs });
1169
1169
  menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
1170
1170
  }
@@ -1184,8 +1184,8 @@ This indicates a problem with the menu component's close functionality.`
1184
1184
  if (this.selectors.input) {
1185
1185
  await page.locator(this.selectors.input).first().clear();
1186
1186
  }
1187
- if (this.selectors.trigger) {
1188
- const triggerElement = page.locator(this.selectors.trigger).first();
1187
+ if (this.selectors.main) {
1188
+ const triggerElement = page.locator(this.selectors.main).first();
1189
1189
  await triggerElement.focus();
1190
1190
  }
1191
1191
  }
@@ -1977,13 +1977,21 @@ var init_AssertionRunner = __esm({
1977
1977
  /**
1978
1978
  * Validate focus assertion
1979
1979
  */
1980
- async validateFocus(target, targetName, failureMessage, testDescription) {
1980
+ async validateFocus(target, targetName, expectedFocus, failureMessage, testDescription) {
1981
1981
  try {
1982
- await (0, test_exports.expect)(target).toBeFocused({ timeout: this.timeoutMs });
1983
- return {
1984
- success: true,
1985
- passMessage: `${targetName} has focus as expected. Test: "${testDescription}".`
1986
- };
1982
+ if (expectedFocus) {
1983
+ await (0, test_exports.expect)(target).toBeFocused({ timeout: this.timeoutMs });
1984
+ return {
1985
+ success: true,
1986
+ passMessage: `${targetName} has focus as expected. Test: "${testDescription}".`
1987
+ };
1988
+ } else {
1989
+ await (0, test_exports.expect)(target).not.toBeFocused({ timeout: this.timeoutMs });
1990
+ return {
1991
+ success: true,
1992
+ passMessage: `${targetName} does not have focus as expected. Test: "${testDescription}".`
1993
+ };
1994
+ }
1987
1995
  } catch {
1988
1996
  const actualFocus = await this.page.evaluate(() => {
1989
1997
  const focused = document.activeElement;
@@ -2069,7 +2077,9 @@ var init_AssertionRunner = __esm({
2069
2077
  }
2070
2078
  return { success: false, failMessage: "Missing expectedValue for toHaveValue assertion" };
2071
2079
  case "toHaveFocus":
2072
- return this.validateFocus(target, assertion.target, assertion.failureMessage || "", testDescription);
2080
+ return this.validateFocus(target, assertion.target, true, assertion.failureMessage || "", testDescription);
2081
+ case "notToHaveFocus":
2082
+ return this.validateFocus(target, assertion.target, false, assertion.failureMessage || "", testDescription);
2073
2083
  case "toHaveRole":
2074
2084
  if (assertion.expectedValue !== void 0) {
2075
2085
  return this.validateRole(target, assertion.target, assertion.expectedValue, assertion.failureMessage || "", testDescription);
@@ -2092,12 +2102,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
2092
2102
  const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
2093
2103
  const isCustomContract = !!componentConfig?.contractPath;
2094
2104
  const reporter = new ContractReporter(true, isCustomContract);
2095
- const defaultTimeouts = {
2096
- actionTimeoutMs: 400,
2097
- assertionTimeoutMs: 400,
2098
- navigationTimeoutMs: 3e4,
2099
- componentReadyTimeoutMs: 5e3
2100
- };
2105
+ const defaultTimeouts = { actionTimeoutMs: 400, assertionTimeoutMs: 400, navigationTimeoutMs: 3e4, componentReadyTimeoutMs: 5e3 };
2101
2106
  const globalDisableTimeouts = config?.test?.disableTimeouts === true;
2102
2107
  const componentDisableTimeouts = componentConfig?.disableTimeouts === true;
2103
2108
  const disableTimeouts = componentDisableTimeouts || globalDisableTimeouts;
@@ -2109,11 +2114,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
2109
2114
  }
2110
2115
  return value;
2111
2116
  };
2112
- const actionTimeoutMs = resolveTimeout(
2113
- componentConfig?.actionTimeoutMs,
2114
- config?.test?.actionTimeoutMs,
2115
- defaultTimeouts.actionTimeoutMs
2116
- );
2117
+ const actionTimeoutMs = resolveTimeout(componentConfig?.actionTimeoutMs, config?.test?.actionTimeoutMs, defaultTimeouts.actionTimeoutMs);
2117
2118
  const assertionTimeoutMs = resolveTimeout(
2118
2119
  componentConfig?.assertionTimeoutMs,
2119
2120
  config?.test?.assertionTimeoutMs,
@@ -2213,8 +2214,8 @@ This usually means:
2213
2214
  );
2214
2215
  }
2215
2216
  reporter.start(componentName, totalTests, apgUrl);
2216
- if (componentName === "menu" && componentContract.selectors.trigger) {
2217
- await page.locator(componentContract.selectors.trigger).first().waitFor({ state: "attached", timeout: componentReadyTimeoutMs }).catch(() => {
2217
+ if (componentName === "menu" && componentContract.selectors.main) {
2218
+ await page.locator(componentContract.selectors.main).first().waitFor({ state: "visible", timeout: componentReadyTimeoutMs }).catch(() => {
2218
2219
  });
2219
2220
  }
2220
2221
  const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
@@ -2223,7 +2224,42 @@ This usually means:
2223
2224
  let staticFailed = 0;
2224
2225
  let staticWarnings = 0;
2225
2226
  for (const rel of componentContract.relationships || []) {
2227
+ if (strategy && typeof strategy.resetState === "function") {
2228
+ try {
2229
+ await strategy.resetState(page);
2230
+ } catch (err) {
2231
+ warnings.push(`Warning: resetState failed before relationship test: ${err instanceof Error ? err.message : String(err)}`);
2232
+ }
2233
+ }
2226
2234
  const relationshipLevel = normalizeLevel(rel.level);
2235
+ if (Array.isArray(rel.setup) && rel.setup.length > 0) {
2236
+ let isAllowedType2 = function(t) {
2237
+ return allowedTypes.includes(t);
2238
+ };
2239
+ var isAllowedType = isAllowedType2;
2240
+ const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
2241
+ const relDescription = rel.type === "aria-reference" ? `${rel.from}.${rel.attribute} references ${rel.to}` : `${rel.parent} contains ${rel.child}`;
2242
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
2243
+ const toSetupAction = (a) => ({
2244
+ ...a,
2245
+ type: isAllowedType2(a.type) ? a.type : "click"
2246
+ });
2247
+ const setupActions = rel.setup.map(toSetupAction);
2248
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, relDescription, ["submenu", "submenuTrigger", "submenuItems"]);
2249
+ if (setupResult.skip) {
2250
+ skipped.push(setupResult.message || "Setup action skipped");
2251
+ reporter.reportStaticTest(relDescription, "skip", setupResult.message, relationshipLevel);
2252
+ continue;
2253
+ }
2254
+ if (!setupResult.success) {
2255
+ const failure = `Relationship setup failed: ${setupResult.error}`;
2256
+ const outcome = classifyFailure(failure, rel.level);
2257
+ if (outcome.status === "fail") staticFailed += 1;
2258
+ if (outcome.status === "warn") staticWarnings += 1;
2259
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
2260
+ continue;
2261
+ }
2262
+ }
2227
2263
  if (componentName === "menu" && !hasSubmenuCapability) {
2228
2264
  const involvesSubmenu = isSubmenuRelation(rel);
2229
2265
  if (involvesSubmenu) {
@@ -2396,7 +2432,53 @@ This usually means:
2396
2432
  }
2397
2433
  }
2398
2434
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
2435
+ async function runSetupActions(setup, actionExecutor, strategy2, page2, description, skipKeywords = []) {
2436
+ if (!Array.isArray(setup) || setup.length === 0) return { success: true };
2437
+ if (strategy2 && typeof strategy2.resetState === "function") {
2438
+ await strategy2.resetState(page2);
2439
+ }
2440
+ for (const setupAct of setup) {
2441
+ let setupResult = { success: true };
2442
+ try {
2443
+ if (setupAct.type === "focus") {
2444
+ if (setupAct.target === "relative" && setupAct.relativeTarget) {
2445
+ setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
2446
+ } else {
2447
+ setupResult = await actionExecutor.focus(setupAct.target);
2448
+ }
2449
+ } else if (setupAct.type === "type" && setupAct.value) {
2450
+ setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
2451
+ } else if (setupAct.type === "click") {
2452
+ setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
2453
+ } else if (setupAct.type === "keypress" && setupAct.key) {
2454
+ setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
2455
+ } else if (setupAct.type === "hover") {
2456
+ setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
2457
+ } else {
2458
+ continue;
2459
+ }
2460
+ } catch (err) {
2461
+ setupResult = { success: false, error: err instanceof Error ? err.message : String(err) };
2462
+ }
2463
+ if (!setupResult.success) {
2464
+ const setupMsg = setupResult.error || "Setup action failed";
2465
+ const isSkip = skipKeywords.some((kw) => description.includes(kw) || setupMsg.includes(kw));
2466
+ if (isSkip) {
2467
+ return { success: false, skip: true, message: `Skipping test - capability not present: ${setupMsg}` };
2468
+ }
2469
+ return { success: false, error: setupMsg };
2470
+ }
2471
+ }
2472
+ return { success: true };
2473
+ }
2399
2474
  for (const test of componentContract.static[0]?.assertions || []) {
2475
+ if (strategy && typeof strategy.resetState === "function") {
2476
+ try {
2477
+ await strategy.resetState(page);
2478
+ } catch (err) {
2479
+ warnings.push(`Warning: resetState failed before static test: ${err instanceof Error ? err.message : String(err)}`);
2480
+ }
2481
+ }
2400
2482
  if (test.target === "relative") continue;
2401
2483
  const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
2402
2484
  const staticLevel = normalizeLevel(test.level);
@@ -2406,6 +2488,33 @@ This usually means:
2406
2488
  reporter.reportStaticTest(staticDescription, "skip", skipMessage, staticLevel);
2407
2489
  continue;
2408
2490
  }
2491
+ if (Array.isArray(test.setup) && test.setup.length > 0) {
2492
+ let isAllowedType2 = function(t) {
2493
+ return allowedTypes.includes(t);
2494
+ };
2495
+ var isAllowedType = isAllowedType2;
2496
+ const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
2497
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
2498
+ const toSetupAction = (a) => ({
2499
+ ...a,
2500
+ type: isAllowedType2(a.type) ? a.type : "click"
2501
+ });
2502
+ const setupActions = test.setup.map(toSetupAction);
2503
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, staticDescription, ["submenu", "submenuTrigger", "submenuItems"]);
2504
+ if (setupResult.skip) {
2505
+ skipped.push(setupResult.message || "Setup action skipped");
2506
+ reporter.reportStaticTest(staticDescription, "skip", setupResult.message, staticLevel);
2507
+ continue;
2508
+ }
2509
+ if (!setupResult.success) {
2510
+ const failure = `Static setup failed: ${setupResult.error}`;
2511
+ const outcome = classifyFailure(failure, test.level);
2512
+ if (outcome.status === "fail") staticFailed += 1;
2513
+ if (outcome.status === "warn") staticWarnings += 1;
2514
+ reporter.reportStaticTest(staticDescription, outcome.status, outcome.detail, outcome.level);
2515
+ continue;
2516
+ }
2517
+ }
2409
2518
  const targetSelector = componentContract.selectors[test.target];
2410
2519
  if (!targetSelector) {
2411
2520
  const failure = `Selector for target ${test.target} not found.`;
@@ -2532,31 +2641,26 @@ This usually means:
2532
2641
  const dynamicLevel = normalizeLevel(dynamicTest.level);
2533
2642
  const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
2534
2643
  if (Array.isArray(setup) && setup.length > 0) {
2535
- for (const setupAct of setup) {
2536
- let setupResult;
2537
- if (setupAct.type === "focus") {
2538
- if (setupAct.target === "relative" && setupAct.relativeTarget) {
2539
- setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
2540
- } else {
2541
- setupResult = await actionExecutor.focus(setupAct.target);
2542
- }
2543
- } else if (setupAct.type === "type" && setupAct.value) {
2544
- setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
2545
- } else if (setupAct.type === "click") {
2546
- setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
2547
- } else if (setupAct.type === "keypress" && setupAct.key) {
2548
- setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
2549
- } else if (setupAct.type === "hover") {
2550
- setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
2551
- } else {
2552
- continue;
2553
- }
2554
- if (!setupResult.success) {
2555
- const setupMsg = setupResult.error || "Setup action failed";
2556
- const outcome = classifyFailure(`Setup failed: ${setupMsg}`, dynamicTest.level);
2557
- reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
2558
- continue;
2559
- }
2644
+ let isAllowedType2 = function(t) {
2645
+ return allowedTypes.includes(t);
2646
+ };
2647
+ var isAllowedType = isAllowedType2;
2648
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
2649
+ const toSetupAction = (a) => ({
2650
+ ...a,
2651
+ type: isAllowedType2(a.type) ? a.type : "click"
2652
+ });
2653
+ const setupActions = setup.map(toSetupAction);
2654
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, dynamicTest.description, ["submenu", "submenuTrigger", "submenuItems"]);
2655
+ if (setupResult.skip) {
2656
+ skipped.push(setupResult.message || "Setup action skipped");
2657
+ reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, "skip", setupResult.message);
2658
+ continue;
2659
+ }
2660
+ if (!setupResult.success) {
2661
+ const outcome = classifyFailure(`Setup failed: ${setupResult.error}`, dynamicTest.level);
2662
+ reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
2663
+ continue;
2560
2664
  }
2561
2665
  }
2562
2666
  const failuresBeforeTest = failures.length;
@@ -3136,25 +3240,6 @@ function validateContractSchema(contract) {
3136
3240
  });
3137
3241
  }
3138
3242
  }
3139
- if (c.states !== void 0) {
3140
- if (!Array.isArray(c.states)) {
3141
- errors.push({ path: "$.states", message: "states must be an array" });
3142
- } else {
3143
- c.states.forEach((state, idx) => {
3144
- if (typeof state !== "object" || state === null) {
3145
- errors.push({ path: `$.states[${idx}]`, message: "state must be an object" });
3146
- return;
3147
- }
3148
- const s = state;
3149
- if (typeof s.name !== "string") {
3150
- errors.push({ path: `$.states[${idx}].name`, message: "name is required and must be a string" });
3151
- }
3152
- if (!Array.isArray(s.requires)) {
3153
- errors.push({ path: `$.states[${idx}].requires`, message: "requires is required and must be an array" });
3154
- }
3155
- });
3156
- }
3157
- }
3158
3243
  return {
3159
3244
  valid: errors.length === 0,
3160
3245
  errors
@@ -3230,17 +3315,8 @@ function validateTargetReferences(contract, selectorKeys) {
3230
3315
  });
3231
3316
  }
3232
3317
  const dynamicItems = c.dynamic;
3233
- const states = c.states;
3234
- const stateNames = new Set((states || []).map((s) => String(s.name)));
3235
3318
  if (Array.isArray(dynamicItems)) {
3236
3319
  dynamicItems.forEach((item, itemIdx) => {
3237
- const given = item.given;
3238
- if (given && !stateNames.has(given)) {
3239
- errors.push({
3240
- path: `$.dynamic[${itemIdx}].given`,
3241
- message: `State '${given}' not found in states`
3242
- });
3243
- }
3244
3320
  const actions = item.action;
3245
3321
  if (Array.isArray(actions)) {
3246
3322
  actions.forEach((action, actIdx) => {
package/dist/cli.js CHANGED
@@ -122,12 +122,12 @@ program.command("audit").description("Run axe-core powered accessibility audit o
122
122
  process.exit(1);
123
123
  });
124
124
  program.command("test").description("Run core a11y accessibility standard tests on UI components").action(async () => {
125
- const { runTest } = await import("./test-FYSJXQWO.js");
125
+ const { runTest } = await import("./test-WDBS5JWO.js");
126
126
  runTest();
127
127
  });
128
128
  program.command("build").description("Build accessibility artifacts").addCommand(
129
129
  new Command("contracts").description("Build DSL contracts to JSON").action(async () => {
130
- const { buildContracts } = await import("./buildContracts-FT6KWUJN.js");
130
+ const { buildContracts } = await import("./buildContracts-VIV6GM56.js");
131
131
  const { loadConfig: loadConfig2 } = await import("./configLoader-REHK3S3Q.js");
132
132
  const cwd = process.cwd();
133
133
  const { config, configPath, errors } = await loadConfig2(cwd);