aria-ease 3.0.2 → 4.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.
Files changed (44) hide show
  1. package/README.md +6 -6
  2. package/bin/cli.cjs +90 -22
  3. package/bin/cli.js +1 -1
  4. package/bin/{contractTestRunnerPlaywright-TLQZGKW7.js → contractTestRunnerPlaywright-EZLNNJV5.js} +43 -10
  5. package/bin/{test-Q7W3WQEA.js → test-45KMD4F4.js} +47 -12
  6. package/dist/{contractTestRunnerPlaywright-7U2O33SR.js → contractTestRunnerPlaywright-UQQI5MYS.js} +43 -10
  7. package/dist/index.cjs +663 -38
  8. package/dist/index.d.cts +88 -6
  9. package/dist/index.d.ts +88 -6
  10. package/dist/index.js +616 -28
  11. package/dist/src/{Types.d-uG0Hm1yK.d.ts → Types.d-BrHSyS03.d.cts} +17 -0
  12. package/dist/src/{Types.d-uG0Hm1yK.d.cts → Types.d-BrHSyS03.d.ts} +17 -0
  13. package/dist/src/accordion/index.cjs +159 -0
  14. package/dist/src/accordion/index.d.cts +19 -2
  15. package/dist/src/accordion/index.d.ts +19 -2
  16. package/dist/src/accordion/index.js +159 -1
  17. package/dist/src/block/index.cjs +1 -1
  18. package/dist/src/block/index.d.cts +6 -2
  19. package/dist/src/block/index.d.ts +6 -2
  20. package/dist/src/block/index.js +1 -1
  21. package/dist/src/checkbox/index.cjs +129 -0
  22. package/dist/src/checkbox/index.d.cts +15 -2
  23. package/dist/src/checkbox/index.d.ts +15 -2
  24. package/dist/src/checkbox/index.js +129 -1
  25. package/dist/src/combobox/index.d.cts +1 -1
  26. package/dist/src/combobox/index.d.ts +1 -1
  27. package/dist/src/menu/index.cjs +13 -15
  28. package/dist/src/menu/index.d.cts +1 -1
  29. package/dist/src/menu/index.d.ts +1 -1
  30. package/dist/src/menu/index.js +13 -15
  31. package/dist/src/radio/index.cjs +122 -0
  32. package/dist/src/radio/index.d.cts +17 -2
  33. package/dist/src/radio/index.d.ts +17 -2
  34. package/dist/src/radio/index.js +122 -1
  35. package/dist/src/toggle/index.cjs +145 -0
  36. package/dist/src/toggle/index.d.cts +17 -2
  37. package/dist/src/toggle/index.d.ts +17 -2
  38. package/dist/src/toggle/index.js +145 -1
  39. package/dist/src/utils/test/{contractTestRunnerPlaywright-7U2O33SR.js → contractTestRunnerPlaywright-UQQI5MYS.js} +43 -10
  40. package/dist/src/utils/test/index.cjs +90 -22
  41. package/dist/src/utils/test/index.d.cts +5 -4
  42. package/dist/src/utils/test/index.d.ts +5 -4
  43. package/dist/src/utils/test/index.js +47 -12
  44. package/package.json +1 -1
package/README.md CHANGED
@@ -172,7 +172,7 @@ updateAccordionTriggerAriaAttributes(
172
172
  "accordion-container", // Container ID
173
173
  "accordion-trigger", // Shared class for triggers
174
174
  accordionStates, // State array
175
- 0 // Index of trigger that changed
175
+ 0, // Index of trigger that changed
176
176
  );
177
177
  ```
178
178
 
@@ -223,7 +223,7 @@ function handleCheckboxClick(index) {
223
223
  "checkbox-group",
224
224
  "custom-checkbox",
225
225
  checkboxStates,
226
- index
226
+ index,
227
227
  );
228
228
  }
229
229
  ```
@@ -314,14 +314,14 @@ Aria-Ease includes a built-in testing framework for automated accessibility vali
314
314
  import { testUiComponent } from "aria-ease/test";
315
315
 
316
316
  // In your test file (Vitest, Jest, etc.)
317
- test("menu is accessible", async () => {
318
- const { container } = render(<MyMenu />);
317
+ test("combobox is accessible", async () => {
318
+ const { container } = render(<Combobox />);
319
319
 
320
320
  // Runs axe-core + contract tests
321
321
  const result = await testUiComponent(
322
- "menu",
322
+ "combobox",
323
323
  container,
324
- "http://localhost:3000" // Optional: full E2E with Playwright
324
+ "http://localhost:3000", // Optional: full E2E with Playwright
325
325
  );
326
326
 
327
327
  expect(result.violations).toHaveLength(0);
package/bin/cli.cjs CHANGED
@@ -13261,7 +13261,7 @@ async function runContractTests(componentName, component) {
13261
13261
  const staticFailed = 0;
13262
13262
  reporter.reportStatic(staticPassed, staticFailed);
13263
13263
  reporter.summary(failures);
13264
- return { passes, failures };
13264
+ return { passes, failures, skipped };
13265
13265
  }
13266
13266
  var import_promises, import_meta;
13267
13267
  var init_contractTestRunner = __esm({
@@ -13308,6 +13308,7 @@ async function runContractTestsPlaywright(componentName, url) {
13308
13308
  reporter.start(componentName, totalTests);
13309
13309
  const failures = [];
13310
13310
  const passes = [];
13311
+ const skipped = [];
13311
13312
  let browser = null;
13312
13313
  try {
13313
13314
  browser = await import_playwright3.chromium.launch({ headless: true });
@@ -13315,13 +13316,13 @@ async function runContractTestsPlaywright(componentName, url) {
13315
13316
  const page = await context.newPage();
13316
13317
  await page.goto(url, {
13317
13318
  waitUntil: "domcontentloaded",
13318
- timeout: 6e4
13319
+ timeout: 9e4
13319
13320
  });
13320
13321
  const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
13321
13322
  if (!mainSelector) {
13322
13323
  throw new Error(`No main selector (trigger, input, or container) found in contract for ${componentName}`);
13323
13324
  }
13324
- await page.waitForSelector(mainSelector, { timeout: 6e4 });
13325
+ await page.waitForSelector(mainSelector, { timeout: 9e4 });
13325
13326
  if (componentName === "menu" && componentContract.selectors.trigger) {
13326
13327
  await page.waitForFunction(
13327
13328
  (selector) => {
@@ -13329,7 +13330,7 @@ async function runContractTestsPlaywright(componentName, url) {
13329
13330
  return trigger && trigger.getAttribute("data-menu-initialized") === "true";
13330
13331
  },
13331
13332
  componentContract.selectors.trigger,
13332
- { timeout: 5e3 }
13333
+ { timeout: 6e3 }
13333
13334
  ).catch(() => {
13334
13335
  console.warn("Menu initialization signal not detected, continuing with tests...");
13335
13336
  });
@@ -13419,11 +13420,43 @@ async function runContractTestsPlaywright(componentName, url) {
13419
13420
  if (componentContract.selectors.input) {
13420
13421
  const inputElement = page.locator(componentContract.selectors.input).first();
13421
13422
  await inputElement.clear();
13422
- await page.waitForTimeout(50);
13423
+ await page.waitForTimeout(100);
13424
+ }
13425
+ }
13426
+ }
13427
+ }
13428
+ let shouldSkipTest = false;
13429
+ for (const act of action) {
13430
+ if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
13431
+ const submenuSelector = componentContract.selectors[act.target];
13432
+ if (submenuSelector) {
13433
+ const submenuCount = await page.locator(submenuSelector).count();
13434
+ if (submenuCount === 0) {
13435
+ reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
13436
+ shouldSkipTest = true;
13437
+ break;
13438
+ }
13439
+ }
13440
+ }
13441
+ }
13442
+ if (!shouldSkipTest) {
13443
+ for (const assertion of assertions) {
13444
+ if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
13445
+ const submenuSelector = componentContract.selectors[assertion.target];
13446
+ if (submenuSelector) {
13447
+ const submenuCount = await page.locator(submenuSelector).count();
13448
+ if (submenuCount === 0) {
13449
+ reporter.reportTest(dynamicTest, "skip", `Skipping test - ${assertion.target} element not found (optional submenu test)`);
13450
+ shouldSkipTest = true;
13451
+ break;
13452
+ }
13423
13453
  }
13424
13454
  }
13425
13455
  }
13426
13456
  }
13457
+ if (shouldSkipTest) {
13458
+ continue;
13459
+ }
13427
13460
  for (const act of action) {
13428
13461
  if (act.type === "focus") {
13429
13462
  const focusSelector = componentContract.selectors[act.target];
@@ -13432,7 +13465,7 @@ async function runContractTestsPlaywright(componentName, url) {
13432
13465
  continue;
13433
13466
  }
13434
13467
  await page.locator(focusSelector).first().focus();
13435
- await page.waitForTimeout(50);
13468
+ await page.waitForTimeout(100);
13436
13469
  }
13437
13470
  if (act.type === "type" && act.value) {
13438
13471
  const typeSelector = componentContract.selectors[act.target];
@@ -13441,7 +13474,7 @@ async function runContractTestsPlaywright(componentName, url) {
13441
13474
  continue;
13442
13475
  }
13443
13476
  await page.locator(typeSelector).first().fill(act.value);
13444
- await page.waitForTimeout(50);
13477
+ await page.waitForTimeout(100);
13445
13478
  }
13446
13479
  if (act.type === "click") {
13447
13480
  if (act.target === "document") {
@@ -13491,7 +13524,7 @@ async function runContractTestsPlaywright(componentName, url) {
13491
13524
  if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
13492
13525
  await page.waitForTimeout(100);
13493
13526
  await page.keyboard.press(keyValue);
13494
- await page.waitForTimeout(50);
13527
+ await page.waitForTimeout(100);
13495
13528
  } else {
13496
13529
  const keypressSelector = componentContract.selectors[act.target];
13497
13530
  if (!keypressSelector) {
@@ -13520,7 +13553,7 @@ async function runContractTestsPlaywright(componentName, url) {
13520
13553
  continue;
13521
13554
  }
13522
13555
  await relativeElement.hover();
13523
- await page.waitForTimeout(50);
13556
+ await page.waitForTimeout(100);
13524
13557
  } else {
13525
13558
  const hoverSelector = componentContract.selectors[act.target];
13526
13559
  if (!hoverSelector) {
@@ -13528,7 +13561,7 @@ async function runContractTestsPlaywright(componentName, url) {
13528
13561
  continue;
13529
13562
  }
13530
13563
  await page.locator(hoverSelector).first().hover();
13531
- await page.waitForTimeout(50);
13564
+ await page.waitForTimeout(100);
13532
13565
  }
13533
13566
  }
13534
13567
  await page.waitForTimeout(100);
@@ -13670,7 +13703,7 @@ async function runContractTestsPlaywright(componentName, url) {
13670
13703
  } finally {
13671
13704
  if (browser) await browser.close();
13672
13705
  }
13673
- return { passes, failures };
13706
+ return { passes, failures, skipped };
13674
13707
  }
13675
13708
  var import_playwright3, import_fs, import_meta2;
13676
13709
  var init_contractTestRunnerPlaywright = __esm({
@@ -13687,15 +13720,49 @@ var init_contractTestRunnerPlaywright = __esm({
13687
13720
 
13688
13721
  // src/utils/test/src/test.ts
13689
13722
  async function testUiComponent(componentName, component, url) {
13690
- const results = await (0, import_jest_axe.axe)(component);
13723
+ if (!componentName || typeof componentName !== "string") {
13724
+ throw new Error("\u274C testUiComponent requires a valid componentName (string)");
13725
+ }
13726
+ if (!component || !(component instanceof HTMLElement)) {
13727
+ throw new Error("\u274C testUiComponent requires a valid component (HTMLElement)");
13728
+ }
13729
+ if (url && typeof url !== "string") {
13730
+ throw new Error("\u274C testUiComponent url parameter must be a string");
13731
+ }
13732
+ let results;
13733
+ try {
13734
+ results = await (0, import_jest_axe.axe)(component);
13735
+ } catch (error) {
13736
+ throw new Error(
13737
+ `\u274C Axe accessibility scan failed
13738
+ Error: ${error instanceof Error ? error.message : String(error)}`
13739
+ );
13740
+ }
13691
13741
  let contract;
13692
- if (url) {
13693
- console.log(`\u{1F3AD} Running Playwright E2E tests on ${url}`);
13694
- const { runContractTestsPlaywright: runContractTestsPlaywright2 } = await Promise.resolve().then(() => (init_contractTestRunnerPlaywright(), contractTestRunnerPlaywright_exports));
13695
- contract = await runContractTestsPlaywright2(componentName, url);
13696
- } else {
13697
- console.log(`\u{1F9EA} Running jsdom tests (limited event handling)`);
13698
- contract = await runContractTests(componentName, component);
13742
+ try {
13743
+ if (url) {
13744
+ console.log(`\u{1F3AD} Running Playwright E2E tests on ${url}`);
13745
+ try {
13746
+ new URL(url);
13747
+ } catch {
13748
+ throw new Error(
13749
+ `\u274C Invalid URL format: "${url}"
13750
+ URL must include protocol (e.g., "http://localhost:5173/test")`
13751
+ );
13752
+ }
13753
+ const { runContractTestsPlaywright: runContractTestsPlaywright2 } = await Promise.resolve().then(() => (init_contractTestRunnerPlaywright(), contractTestRunnerPlaywright_exports));
13754
+ contract = await runContractTestsPlaywright2(componentName, url);
13755
+ } else {
13756
+ console.log(`\u{1F9EA} Running jsdom tests (limited event handling)`);
13757
+ console.log(`Some tests may be skipped or yield false positives/negatives.
13758
+ For full coverage, run with a URL to enable Playwright E2E tests.`);
13759
+ contract = await runContractTests(componentName, component);
13760
+ }
13761
+ } catch (error) {
13762
+ if (error instanceof Error) {
13763
+ throw error;
13764
+ }
13765
+ throw new Error(`\u274C Contract test execution failed: ${String(error)}`);
13699
13766
  }
13700
13767
  const result = {
13701
13768
  violations: results.violations,
@@ -13706,10 +13773,11 @@ async function testUiComponent(componentName, component, url) {
13706
13773
  const mode = url ? "Playwright" : "jsdom";
13707
13774
  throw new Error(
13708
13775
  `
13709
- \u274C ${contract.failures.length} assertion${contract.failures.length > 1 ? "s" : ""} failed (${mode} mode)
13710
- \u2705 ${contract.passes.length} assertion${contract.passes.length > 1 ? "s" : ""} passed
13776
+ \u274C ${contract.failures.length} accessibility contract test${contract.failures.length > 1 ? "s" : ""} failed (${mode} mode)
13777
+ \u2705 ${contract.passes.length} test${contract.passes.length > 1 ? "s" : ""} passed
13711
13778
 
13712
- \u{1F4CB} Review the detailed test report above for specific failures.`
13779
+ \u{1F4CB} Review the detailed test report above for specific failures.
13780
+ \u{1F4A1} Contract tests validate ARIA attributes and keyboard interactions per W3C APG guidelines.`
13713
13781
  );
13714
13782
  }
13715
13783
  if (results.violations.length > 0) {
package/bin/cli.js CHANGED
@@ -204,7 +204,7 @@ program.command("audit").description("Run axe-core powered accessibility audit o
204
204
  console.log(chalk.green("\n\u{1F389} All audits completed."));
205
205
  });
206
206
  program.command("test").description("Run core a11y accessibility standard tests on UI components").action(async () => {
207
- const { runTest } = await import("./test-Q7W3WQEA.js");
207
+ const { runTest } = await import("./test-45KMD4F4.js");
208
208
  runTest();
209
209
  });
210
210
  program.command("help").description("Display help information").action(() => {
@@ -35,6 +35,7 @@ async function runContractTestsPlaywright(componentName, url) {
35
35
  reporter.start(componentName, totalTests);
36
36
  const failures = [];
37
37
  const passes = [];
38
+ const skipped = [];
38
39
  let browser = null;
39
40
  try {
40
41
  browser = await chromium.launch({ headless: true });
@@ -42,13 +43,13 @@ async function runContractTestsPlaywright(componentName, url) {
42
43
  const page = await context.newPage();
43
44
  await page.goto(url, {
44
45
  waitUntil: "domcontentloaded",
45
- timeout: 6e4
46
+ timeout: 9e4
46
47
  });
47
48
  const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
48
49
  if (!mainSelector) {
49
50
  throw new Error(`No main selector (trigger, input, or container) found in contract for ${componentName}`);
50
51
  }
51
- await page.waitForSelector(mainSelector, { timeout: 6e4 });
52
+ await page.waitForSelector(mainSelector, { timeout: 9e4 });
52
53
  if (componentName === "menu" && componentContract.selectors.trigger) {
53
54
  await page.waitForFunction(
54
55
  (selector) => {
@@ -56,7 +57,7 @@ async function runContractTestsPlaywright(componentName, url) {
56
57
  return trigger && trigger.getAttribute("data-menu-initialized") === "true";
57
58
  },
58
59
  componentContract.selectors.trigger,
59
- { timeout: 5e3 }
60
+ { timeout: 6e3 }
60
61
  ).catch(() => {
61
62
  console.warn("Menu initialization signal not detected, continuing with tests...");
62
63
  });
@@ -146,11 +147,43 @@ async function runContractTestsPlaywright(componentName, url) {
146
147
  if (componentContract.selectors.input) {
147
148
  const inputElement = page.locator(componentContract.selectors.input).first();
148
149
  await inputElement.clear();
149
- await page.waitForTimeout(50);
150
+ await page.waitForTimeout(100);
150
151
  }
151
152
  }
152
153
  }
153
154
  }
155
+ let shouldSkipTest = false;
156
+ for (const act of action) {
157
+ if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
158
+ const submenuSelector = componentContract.selectors[act.target];
159
+ if (submenuSelector) {
160
+ const submenuCount = await page.locator(submenuSelector).count();
161
+ if (submenuCount === 0) {
162
+ reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
163
+ shouldSkipTest = true;
164
+ break;
165
+ }
166
+ }
167
+ }
168
+ }
169
+ if (!shouldSkipTest) {
170
+ for (const assertion of assertions) {
171
+ if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
172
+ const submenuSelector = componentContract.selectors[assertion.target];
173
+ if (submenuSelector) {
174
+ const submenuCount = await page.locator(submenuSelector).count();
175
+ if (submenuCount === 0) {
176
+ reporter.reportTest(dynamicTest, "skip", `Skipping test - ${assertion.target} element not found (optional submenu test)`);
177
+ shouldSkipTest = true;
178
+ break;
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ if (shouldSkipTest) {
185
+ continue;
186
+ }
154
187
  for (const act of action) {
155
188
  if (act.type === "focus") {
156
189
  const focusSelector = componentContract.selectors[act.target];
@@ -159,7 +192,7 @@ async function runContractTestsPlaywright(componentName, url) {
159
192
  continue;
160
193
  }
161
194
  await page.locator(focusSelector).first().focus();
162
- await page.waitForTimeout(50);
195
+ await page.waitForTimeout(100);
163
196
  }
164
197
  if (act.type === "type" && act.value) {
165
198
  const typeSelector = componentContract.selectors[act.target];
@@ -168,7 +201,7 @@ async function runContractTestsPlaywright(componentName, url) {
168
201
  continue;
169
202
  }
170
203
  await page.locator(typeSelector).first().fill(act.value);
171
- await page.waitForTimeout(50);
204
+ await page.waitForTimeout(100);
172
205
  }
173
206
  if (act.type === "click") {
174
207
  if (act.target === "document") {
@@ -218,7 +251,7 @@ async function runContractTestsPlaywright(componentName, url) {
218
251
  if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
219
252
  await page.waitForTimeout(100);
220
253
  await page.keyboard.press(keyValue);
221
- await page.waitForTimeout(50);
254
+ await page.waitForTimeout(100);
222
255
  } else {
223
256
  const keypressSelector = componentContract.selectors[act.target];
224
257
  if (!keypressSelector) {
@@ -247,7 +280,7 @@ async function runContractTestsPlaywright(componentName, url) {
247
280
  continue;
248
281
  }
249
282
  await relativeElement.hover();
250
- await page.waitForTimeout(50);
283
+ await page.waitForTimeout(100);
251
284
  } else {
252
285
  const hoverSelector = componentContract.selectors[act.target];
253
286
  if (!hoverSelector) {
@@ -255,7 +288,7 @@ async function runContractTestsPlaywright(componentName, url) {
255
288
  continue;
256
289
  }
257
290
  await page.locator(hoverSelector).first().hover();
258
- await page.waitForTimeout(50);
291
+ await page.waitForTimeout(100);
259
292
  }
260
293
  }
261
294
  await page.waitForTimeout(100);
@@ -397,7 +430,7 @@ async function runContractTestsPlaywright(componentName, url) {
397
430
  } finally {
398
431
  if (browser) await browser.close();
399
432
  }
400
- return { passes, failures };
433
+ return { passes, failures, skipped };
401
434
  }
402
435
  export {
403
436
  runContractTestsPlaywright
@@ -12741,20 +12741,54 @@ async function runContractTests(componentName, component) {
12741
12741
  const staticFailed = 0;
12742
12742
  reporter.reportStatic(staticPassed, staticFailed);
12743
12743
  reporter.summary(failures);
12744
- return { passes, failures };
12744
+ return { passes, failures, skipped };
12745
12745
  }
12746
12746
 
12747
12747
  // src/utils/test/src/test.ts
12748
12748
  async function testUiComponent(componentName, component, url) {
12749
- const results = await axe(component);
12749
+ if (!componentName || typeof componentName !== "string") {
12750
+ throw new Error("\u274C testUiComponent requires a valid componentName (string)");
12751
+ }
12752
+ if (!component || !(component instanceof HTMLElement)) {
12753
+ throw new Error("\u274C testUiComponent requires a valid component (HTMLElement)");
12754
+ }
12755
+ if (url && typeof url !== "string") {
12756
+ throw new Error("\u274C testUiComponent url parameter must be a string");
12757
+ }
12758
+ let results;
12759
+ try {
12760
+ results = await axe(component);
12761
+ } catch (error) {
12762
+ throw new Error(
12763
+ `\u274C Axe accessibility scan failed
12764
+ Error: ${error instanceof Error ? error.message : String(error)}`
12765
+ );
12766
+ }
12750
12767
  let contract;
12751
- if (url) {
12752
- console.log(`\u{1F3AD} Running Playwright E2E tests on ${url}`);
12753
- const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-TLQZGKW7.js");
12754
- contract = await runContractTestsPlaywright(componentName, url);
12755
- } else {
12756
- console.log(`\u{1F9EA} Running jsdom tests (limited event handling)`);
12757
- contract = await runContractTests(componentName, component);
12768
+ try {
12769
+ if (url) {
12770
+ console.log(`\u{1F3AD} Running Playwright E2E tests on ${url}`);
12771
+ try {
12772
+ new URL(url);
12773
+ } catch {
12774
+ throw new Error(
12775
+ `\u274C Invalid URL format: "${url}"
12776
+ URL must include protocol (e.g., "http://localhost:5173/test")`
12777
+ );
12778
+ }
12779
+ const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-EZLNNJV5.js");
12780
+ contract = await runContractTestsPlaywright(componentName, url);
12781
+ } else {
12782
+ console.log(`\u{1F9EA} Running jsdom tests (limited event handling)`);
12783
+ console.log(`Some tests may be skipped or yield false positives/negatives.
12784
+ For full coverage, run with a URL to enable Playwright E2E tests.`);
12785
+ contract = await runContractTests(componentName, component);
12786
+ }
12787
+ } catch (error) {
12788
+ if (error instanceof Error) {
12789
+ throw error;
12790
+ }
12791
+ throw new Error(`\u274C Contract test execution failed: ${String(error)}`);
12758
12792
  }
12759
12793
  const result = {
12760
12794
  violations: results.violations,
@@ -12765,10 +12799,11 @@ async function testUiComponent(componentName, component, url) {
12765
12799
  const mode = url ? "Playwright" : "jsdom";
12766
12800
  throw new Error(
12767
12801
  `
12768
- \u274C ${contract.failures.length} assertion${contract.failures.length > 1 ? "s" : ""} failed (${mode} mode)
12769
- \u2705 ${contract.passes.length} assertion${contract.passes.length > 1 ? "s" : ""} passed
12802
+ \u274C ${contract.failures.length} accessibility contract test${contract.failures.length > 1 ? "s" : ""} failed (${mode} mode)
12803
+ \u2705 ${contract.passes.length} test${contract.passes.length > 1 ? "s" : ""} passed
12770
12804
 
12771
- \u{1F4CB} Review the detailed test report above for specific failures.`
12805
+ \u{1F4CB} Review the detailed test report above for specific failures.
12806
+ \u{1F4A1} Contract tests validate ARIA attributes and keyboard interactions per W3C APG guidelines.`
12772
12807
  );
12773
12808
  }
12774
12809
  if (results.violations.length > 0) {
@@ -33,6 +33,7 @@ async function runContractTestsPlaywright(componentName, url) {
33
33
  reporter.start(componentName, totalTests);
34
34
  const failures = [];
35
35
  const passes = [];
36
+ const skipped = [];
36
37
  let browser = null;
37
38
  try {
38
39
  browser = await chromium.launch({ headless: true });
@@ -40,13 +41,13 @@ async function runContractTestsPlaywright(componentName, url) {
40
41
  const page = await context.newPage();
41
42
  await page.goto(url, {
42
43
  waitUntil: "domcontentloaded",
43
- timeout: 6e4
44
+ timeout: 9e4
44
45
  });
45
46
  const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
46
47
  if (!mainSelector) {
47
48
  throw new Error(`No main selector (trigger, input, or container) found in contract for ${componentName}`);
48
49
  }
49
- await page.waitForSelector(mainSelector, { timeout: 6e4 });
50
+ await page.waitForSelector(mainSelector, { timeout: 9e4 });
50
51
  if (componentName === "menu" && componentContract.selectors.trigger) {
51
52
  await page.waitForFunction(
52
53
  (selector) => {
@@ -54,7 +55,7 @@ async function runContractTestsPlaywright(componentName, url) {
54
55
  return trigger && trigger.getAttribute("data-menu-initialized") === "true";
55
56
  },
56
57
  componentContract.selectors.trigger,
57
- { timeout: 5e3 }
58
+ { timeout: 6e3 }
58
59
  ).catch(() => {
59
60
  console.warn("Menu initialization signal not detected, continuing with tests...");
60
61
  });
@@ -144,11 +145,43 @@ async function runContractTestsPlaywright(componentName, url) {
144
145
  if (componentContract.selectors.input) {
145
146
  const inputElement = page.locator(componentContract.selectors.input).first();
146
147
  await inputElement.clear();
147
- await page.waitForTimeout(50);
148
+ await page.waitForTimeout(100);
148
149
  }
149
150
  }
150
151
  }
151
152
  }
153
+ let shouldSkipTest = false;
154
+ for (const act of action) {
155
+ if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
156
+ const submenuSelector = componentContract.selectors[act.target];
157
+ if (submenuSelector) {
158
+ const submenuCount = await page.locator(submenuSelector).count();
159
+ if (submenuCount === 0) {
160
+ reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
161
+ shouldSkipTest = true;
162
+ break;
163
+ }
164
+ }
165
+ }
166
+ }
167
+ if (!shouldSkipTest) {
168
+ for (const assertion of assertions) {
169
+ if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
170
+ const submenuSelector = componentContract.selectors[assertion.target];
171
+ if (submenuSelector) {
172
+ const submenuCount = await page.locator(submenuSelector).count();
173
+ if (submenuCount === 0) {
174
+ reporter.reportTest(dynamicTest, "skip", `Skipping test - ${assertion.target} element not found (optional submenu test)`);
175
+ shouldSkipTest = true;
176
+ break;
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ if (shouldSkipTest) {
183
+ continue;
184
+ }
152
185
  for (const act of action) {
153
186
  if (act.type === "focus") {
154
187
  const focusSelector = componentContract.selectors[act.target];
@@ -157,7 +190,7 @@ async function runContractTestsPlaywright(componentName, url) {
157
190
  continue;
158
191
  }
159
192
  await page.locator(focusSelector).first().focus();
160
- await page.waitForTimeout(50);
193
+ await page.waitForTimeout(100);
161
194
  }
162
195
  if (act.type === "type" && act.value) {
163
196
  const typeSelector = componentContract.selectors[act.target];
@@ -166,7 +199,7 @@ async function runContractTestsPlaywright(componentName, url) {
166
199
  continue;
167
200
  }
168
201
  await page.locator(typeSelector).first().fill(act.value);
169
- await page.waitForTimeout(50);
202
+ await page.waitForTimeout(100);
170
203
  }
171
204
  if (act.type === "click") {
172
205
  if (act.target === "document") {
@@ -216,7 +249,7 @@ async function runContractTestsPlaywright(componentName, url) {
216
249
  if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
217
250
  await page.waitForTimeout(100);
218
251
  await page.keyboard.press(keyValue);
219
- await page.waitForTimeout(50);
252
+ await page.waitForTimeout(100);
220
253
  } else {
221
254
  const keypressSelector = componentContract.selectors[act.target];
222
255
  if (!keypressSelector) {
@@ -245,7 +278,7 @@ async function runContractTestsPlaywright(componentName, url) {
245
278
  continue;
246
279
  }
247
280
  await relativeElement.hover();
248
- await page.waitForTimeout(50);
281
+ await page.waitForTimeout(100);
249
282
  } else {
250
283
  const hoverSelector = componentContract.selectors[act.target];
251
284
  if (!hoverSelector) {
@@ -253,7 +286,7 @@ async function runContractTestsPlaywright(componentName, url) {
253
286
  continue;
254
287
  }
255
288
  await page.locator(hoverSelector).first().hover();
256
- await page.waitForTimeout(50);
289
+ await page.waitForTimeout(100);
257
290
  }
258
291
  }
259
292
  await page.waitForTimeout(100);
@@ -395,7 +428,7 @@ async function runContractTestsPlaywright(componentName, url) {
395
428
  } finally {
396
429
  if (browser) await browser.close();
397
430
  }
398
- return { passes, failures };
431
+ return { passes, failures, skipped };
399
432
  }
400
433
  export {
401
434
  runContractTestsPlaywright