aria-ease 6.4.8 → 6.5.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 +15 -27
  2. package/bin/{chunk-TQBS54MM.js → chunk-AUJAN4RK.js} +35 -19
  3. package/bin/cli.cjs +952 -513
  4. package/bin/cli.js +1 -1
  5. package/bin/contractTestRunnerPlaywright-7F756CFB.js +984 -0
  6. package/bin/{test-WICJJ62P.js → test-C3CMRHSI.js} +39 -32
  7. package/dist/{chunk-TQBS54MM.js → chunk-AUJAN4RK.js} +35 -19
  8. package/dist/contractTestRunnerPlaywright-7F756CFB.js +984 -0
  9. package/dist/index.cjs +958 -519
  10. package/dist/index.js +46 -39
  11. package/dist/src/{Types.d-DYfYR3Vc.d.cts → Types.d-yGC2bBaB.d.cts} +1 -1
  12. package/dist/src/{Types.d-DYfYR3Vc.d.ts → Types.d-yGC2bBaB.d.ts} +1 -1
  13. package/dist/src/accordion/index.d.cts +1 -1
  14. package/dist/src/accordion/index.d.ts +1 -1
  15. package/dist/src/block/index.d.cts +1 -1
  16. package/dist/src/block/index.d.ts +1 -1
  17. package/dist/src/checkbox/index.d.cts +1 -1
  18. package/dist/src/checkbox/index.d.ts +1 -1
  19. package/dist/src/combobox/index.d.cts +1 -1
  20. package/dist/src/combobox/index.d.ts +1 -1
  21. package/dist/src/menu/index.cjs +7 -7
  22. package/dist/src/menu/index.d.cts +1 -1
  23. package/dist/src/menu/index.d.ts +1 -1
  24. package/dist/src/menu/index.js +7 -7
  25. package/dist/src/radio/index.d.cts +1 -1
  26. package/dist/src/radio/index.d.ts +1 -1
  27. package/dist/src/tabs/index.d.cts +1 -1
  28. package/dist/src/tabs/index.d.ts +1 -1
  29. package/dist/src/toggle/index.d.cts +1 -1
  30. package/dist/src/toggle/index.d.ts +1 -1
  31. package/dist/src/utils/test/{contracts/AccordionContract.json → aria-contracts/accordion/accordion.contract.json} +20 -7
  32. package/dist/src/utils/test/{contracts/ComboboxContract.json → aria-contracts/combobox/combobox.listbox.contract.json} +18 -17
  33. package/dist/src/utils/test/{contracts/MenuContract.json → aria-contracts/menu/menu.contract.json} +42 -1
  34. package/dist/src/utils/test/{contracts/TabsContract.json → aria-contracts/tabs/tabs.contract.json} +20 -7
  35. package/dist/src/utils/test/{chunk-TQBS54MM.js → chunk-AUJAN4RK.js} +34 -18
  36. package/dist/src/utils/test/contractTestRunnerPlaywright-HL73FADJ.js +955 -0
  37. package/dist/src/utils/test/index.cjs +921 -505
  38. package/dist/src/utils/test/index.d.cts +6 -1
  39. package/dist/src/utils/test/index.d.ts +6 -1
  40. package/dist/src/utils/test/index.js +38 -31
  41. package/package.json +2 -2
  42. package/bin/contractTestRunnerPlaywright-D57V4RSU.js +0 -628
  43. package/dist/contractTestRunnerPlaywright-D57V4RSU.js +0 -628
  44. package/dist/src/utils/test/contractTestRunnerPlaywright-HV4EIRDH.js +0 -610
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  ContractReporter,
3
3
  closeSharedBrowser,
4
4
  contract_default
5
- } from "./chunk-TQBS54MM.js";
5
+ } from "./chunk-AUJAN4RK.js";
6
6
  import "./chunk-I2KLQ2HA.js";
7
7
 
8
8
  // src/accordion/src/makeAccordionAccessible/makeAccordionAccessible.ts
@@ -539,6 +539,13 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
539
539
  };
540
540
  return nodeListLike;
541
541
  }
542
+ function intializeMenuItems() {
543
+ const items = getItems();
544
+ items.forEach((item) => {
545
+ item.setAttribute("role", "menuitem");
546
+ });
547
+ }
548
+ intializeMenuItems();
542
549
  function isItemInNestedSubmenu(item) {
543
550
  let parent = item.parentElement;
544
551
  while (parent && parent !== menuDiv) {
@@ -651,13 +658,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
651
658
  }
652
659
  }
653
660
  }
654
- function intializeMenuItems() {
655
- const items = getItems();
656
- items.forEach((item) => {
657
- item.setAttribute("role", "menuitem");
658
- });
659
- }
660
- intializeMenuItems();
661
661
  function handleTriggerClick() {
662
662
  const isOpen = triggerButton.getAttribute("aria-expanded") === "true";
663
663
  if (isOpen) {
@@ -1433,7 +1433,7 @@ function makeTabsAccessible({ tabListId, tabsClass, tabPanelsClass, orientation
1433
1433
  // src/utils/test/src/test.ts
1434
1434
  import { axe } from "jest-axe";
1435
1435
 
1436
- // src/utils/test/contract/contractTestRunner.ts
1436
+ // src/utils/test/src/contractTestRunner.ts
1437
1437
  import fs from "fs/promises";
1438
1438
  async function runContractTests(componentName, component) {
1439
1439
  const reporter = new ContractReporter(false);
@@ -1450,6 +1450,7 @@ async function runContractTests(componentName, component) {
1450
1450
  const failures = [];
1451
1451
  const passes = [];
1452
1452
  const skipped = [];
1453
+ const failuresBeforeStatic = failures.length;
1453
1454
  for (const test of componentContract.static[0].assertions) {
1454
1455
  if (test.target !== "relative") {
1455
1456
  const selector = componentContract.selectors[test.target];
@@ -1488,8 +1489,9 @@ async function runContractTests(componentName, component) {
1488
1489
  skipped.push(dynamicTest.description);
1489
1490
  reporter.reportTest(dynamicTest, "skip");
1490
1491
  }
1491
- const staticPassed = componentContract.static[0].assertions.length;
1492
- const staticFailed = 0;
1492
+ const staticTotal = componentContract.static[0].assertions.length;
1493
+ const staticFailed = failures.length - failuresBeforeStatic;
1494
+ const staticPassed = Math.max(0, staticTotal - staticFailed);
1493
1495
  reporter.reportStatic(staticPassed, staticFailed);
1494
1496
  reporter.summary(failures);
1495
1497
  return { passes, failures, skipped };
@@ -1539,7 +1541,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
1539
1541
  const devServerUrl = await checkDevServer(url);
1540
1542
  if (devServerUrl) {
1541
1543
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
1542
- const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-D57V4RSU.js");
1544
+ const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-7F756CFB.js");
1543
1545
  contract = await runContractTestsPlaywright(componentName, devServerUrl);
1544
1546
  } else {
1545
1547
  throw new Error(
@@ -1594,6 +1596,11 @@ ${violationDetails}
1594
1596
  return result;
1595
1597
  }
1596
1598
  var runTest = async () => {
1599
+ return {
1600
+ passes: [],
1601
+ failures: [],
1602
+ skipped: []
1603
+ };
1597
1604
  };
1598
1605
  if (typeof window === "undefined") {
1599
1606
  runTest = async () => {
@@ -1601,36 +1608,36 @@ if (typeof window === "undefined") {
1601
1608
  `);
1602
1609
  const { exec } = await import("child_process");
1603
1610
  const chalk = (await import("chalk")).default;
1604
- exec(
1605
- `npx vitest --run --reporter verbose`,
1606
- { cwd: process.cwd() },
1607
- async (error, stdout, stderr) => {
1608
- if (stdout) {
1611
+ return new Promise((resolve, reject) => {
1612
+ exec(
1613
+ `npx vitest --run --reporter verbose`,
1614
+ async (error, stdout, stderr) => {
1609
1615
  console.log(stdout);
1610
- }
1611
- if (stderr) {
1612
- console.error(stderr);
1613
- }
1614
- if (!error || error.code === 0) {
1615
- try {
1616
- const { displayBadgeInfo, promptAddBadge } = await import("./badgeHelper-RDOMCC6E.js");
1617
- displayBadgeInfo("component");
1618
- await promptAddBadge("component", process.cwd());
1619
- console.log(chalk.dim("\n" + "\u2500".repeat(60)));
1620
- console.log(chalk.cyan("\u{1F499} Found aria-ease helpful?"));
1621
- console.log(chalk.white(" \u2022 Star us on GitHub: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease"));
1622
- console.log(chalk.white(" \u2022 Share feedback: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease/discussions"));
1623
- console.log(chalk.dim("\u2500".repeat(60) + "\n"));
1624
- } catch (badgeError) {
1625
- console.error("Warning: Could not display badge prompt:", badgeError);
1616
+ if (stderr) console.error(stderr);
1617
+ const testsPassed = !error || error.code === 0;
1618
+ if (testsPassed) {
1619
+ try {
1620
+ const { displayBadgeInfo, promptAddBadge } = await import("./badgeHelper-RDOMCC6E.js");
1621
+ displayBadgeInfo("component");
1622
+ await promptAddBadge("component", process.cwd());
1623
+ console.log(chalk.dim("\n" + "\u2500".repeat(60)));
1624
+ console.log(chalk.cyan("\u{1F499} Found aria-ease helpful?"));
1625
+ console.log(chalk.white(" \u2022 Star us on GitHub: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease"));
1626
+ console.log(chalk.white(" \u2022 Share feedback: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease/discussions"));
1627
+ console.log(chalk.dim("\u2500".repeat(60) + "\n"));
1628
+ } catch (badgeError) {
1629
+ console.error("Warning: Could not display badge prompt:", badgeError);
1630
+ }
1631
+ resolve({ passes: [], failures: [], skipped: [] });
1632
+ process.exit(0);
1633
+ } else {
1634
+ const exitCode = error?.code || 1;
1635
+ reject(new Error(`Tests failed with code ${exitCode}`));
1636
+ process.exit(exitCode);
1626
1637
  }
1627
- process.exit(0);
1628
- } else {
1629
- const exitCode = error?.code || 1;
1630
- process.exit(exitCode);
1631
1638
  }
1632
- }
1633
- );
1639
+ );
1640
+ });
1634
1641
  };
1635
1642
  }
1636
1643
  async function cleanupTests() {
@@ -88,4 +88,4 @@ interface MenuCallback {
88
88
  onOpenChange?: (isOpen: boolean) => void;
89
89
  }
90
90
 
91
- export type { AccordionConfig as A, ComboboxConfig as C, MenuConfig as M, TabsConfig as T, AccessibilityInstance as a };
91
+ export type { AccessibilityInstance as A, ComboboxConfig as C, MenuConfig as M, TabsConfig as T, AccordionConfig as a };
@@ -88,4 +88,4 @@ interface MenuCallback {
88
88
  onOpenChange?: (isOpen: boolean) => void;
89
89
  }
90
90
 
91
- export type { AccordionConfig as A, ComboboxConfig as C, MenuConfig as M, TabsConfig as T, AccessibilityInstance as a };
91
+ export type { AccessibilityInstance as A, ComboboxConfig as C, MenuConfig as M, TabsConfig as T, AccordionConfig as a };
@@ -1,4 +1,4 @@
1
- import { A as AccordionConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
1
+ import { a as AccordionConfig, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
2
2
 
3
3
  /**
4
4
  * Makes an accordion accessible by managing ARIA attributes, keyboard interaction, and state.
@@ -1,4 +1,4 @@
1
- import { A as AccordionConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
1
+ import { a as AccordionConfig, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
2
2
 
3
3
  /**
4
4
  * Makes an accordion accessible by managing ARIA attributes, keyboard interaction, and state.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
1
+ import { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
2
2
 
3
3
  /**
4
4
  * Adds keyboard interaction to block. The block traps focus and can be interacted with using the keyboard.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
1
+ import { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
2
2
 
3
3
  /**
4
4
  * Adds keyboard interaction to block. The block traps focus and can be interacted with using the keyboard.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
1
+ import { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
2
2
 
3
3
  /**
4
4
  * Makes a checkbox group accessible by managing ARIA attributes and keyboard interaction.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
1
+ import { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
2
2
 
3
3
  /**
4
4
  * Makes a checkbox group accessible by managing ARIA attributes and keyboard interaction.
@@ -1,4 +1,4 @@
1
- import { C as ComboboxConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
1
+ import { C as ComboboxConfig, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
2
2
 
3
3
  /**
4
4
  * Makes a Combobox accessible by adding appropriate ARIA attributes, keyboard interactions and focus management.
@@ -1,4 +1,4 @@
1
- import { C as ComboboxConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
1
+ import { C as ComboboxConfig, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
2
2
 
3
3
  /**
4
4
  * Makes a Combobox accessible by adding appropriate ARIA attributes, keyboard interactions and focus management.
@@ -198,6 +198,13 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
198
198
  };
199
199
  return nodeListLike;
200
200
  }
201
+ function intializeMenuItems() {
202
+ const items = getItems();
203
+ items.forEach((item) => {
204
+ item.setAttribute("role", "menuitem");
205
+ });
206
+ }
207
+ intializeMenuItems();
201
208
  function isItemInNestedSubmenu(item) {
202
209
  let parent = item.parentElement;
203
210
  while (parent && parent !== menuDiv) {
@@ -310,13 +317,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
310
317
  }
311
318
  }
312
319
  }
313
- function intializeMenuItems() {
314
- const items = getItems();
315
- items.forEach((item) => {
316
- item.setAttribute("role", "menuitem");
317
- });
318
- }
319
- intializeMenuItems();
320
320
  function handleTriggerClick() {
321
321
  const isOpen = triggerButton.getAttribute("aria-expanded") === "true";
322
322
  if (isOpen) {
@@ -1,4 +1,4 @@
1
- import { M as MenuConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
1
+ import { M as MenuConfig, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
2
2
 
3
3
  /**
4
4
  * Adds keyboard interaction to toggle menu. The menu traps focus and can be interacted with using the keyboard. The first interactive item of the menu has focus when menu open.
@@ -1,4 +1,4 @@
1
- import { M as MenuConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
1
+ import { M as MenuConfig, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
2
2
 
3
3
  /**
4
4
  * Adds keyboard interaction to toggle menu. The menu traps focus and can be interacted with using the keyboard. The first interactive item of the menu has focus when menu open.
@@ -72,6 +72,13 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
72
72
  };
73
73
  return nodeListLike;
74
74
  }
75
+ function intializeMenuItems() {
76
+ const items = getItems();
77
+ items.forEach((item) => {
78
+ item.setAttribute("role", "menuitem");
79
+ });
80
+ }
81
+ intializeMenuItems();
75
82
  function isItemInNestedSubmenu(item) {
76
83
  let parent = item.parentElement;
77
84
  while (parent && parent !== menuDiv) {
@@ -184,13 +191,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
184
191
  }
185
192
  }
186
193
  }
187
- function intializeMenuItems() {
188
- const items = getItems();
189
- items.forEach((item) => {
190
- item.setAttribute("role", "menuitem");
191
- });
192
- }
193
- intializeMenuItems();
194
194
  function handleTriggerClick() {
195
195
  const isOpen = triggerButton.getAttribute("aria-expanded") === "true";
196
196
  if (isOpen) {
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
1
+ import { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
2
2
 
3
3
  /**
4
4
  * Makes a radio group accessible by managing ARIA attributes, keyboard interaction, and state.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
1
+ import { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
2
2
 
3
3
  /**
4
4
  * Makes a radio group accessible by managing ARIA attributes, keyboard interaction, and state.
@@ -1,4 +1,4 @@
1
- import { T as TabsConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
1
+ import { T as TabsConfig, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
2
2
 
3
3
  /**
4
4
  * Makes tabs accessible by managing ARIA attributes, keyboard interaction, and state.
@@ -1,4 +1,4 @@
1
- import { T as TabsConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
1
+ import { T as TabsConfig, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
2
2
 
3
3
  /**
4
4
  * Makes tabs accessible by managing ARIA attributes, keyboard interaction, and state.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
1
+ import { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
2
2
 
3
3
  /**
4
4
  * Makes a toggle button accessible by managing ARIA attributes and keyboard interactions.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
1
+ import { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
2
2
 
3
3
  /**
4
4
  * Makes a toggle button accessible by managing ARIA attributes and keyboard interactions.
@@ -26,6 +26,26 @@
26
26
  "relative": "first | last | next | previous"
27
27
  },
28
28
 
29
+ "relationships": [
30
+ {
31
+ "type": "aria-reference",
32
+ "from": "trigger",
33
+ "attribute": "aria-controls",
34
+ "to": "panel"
35
+ },
36
+ {
37
+ "type": "aria-reference",
38
+ "from": "panel",
39
+ "attribute": "aria-labelledby",
40
+ "to": "trigger"
41
+ },
42
+ {
43
+ "type": "contains",
44
+ "parent": "container",
45
+ "child": "trigger"
46
+ }
47
+ ],
48
+
29
49
  "static": [
30
50
  {
31
51
  "assertions": [
@@ -42,13 +62,6 @@
42
62
  "attribute": "aria-controls",
43
63
  "failureMessage": "Accordion trigger button doesn't conform to the ARIA Accordion Button pattern as specified in APG 1.2. Accordion trigger button should have 'aria-controls' attribute that points to the id of the accordion panel div it controls with 'role=region'. This helps assistive technology to identify the accordion panel div that the button controls."
44
64
  },
45
- {
46
- "target": "panel",
47
- "assertion": "toHaveAttribute",
48
- "attribute": "role",
49
- "expectedValue": "region",
50
- "failureMessage": "Accordion panel doesn't conform to the ARIA Accordion pattern as specified in APG 1.2. Each accordion panel should have 'role=region' attribute. This helps assistive technology identify the panel as a significant region of the page."
51
- },
52
65
  {
53
66
  "target": "panel",
54
67
  "assertion": "toHaveAttribute",
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "meta": {
3
- "id": "aria-ease.combobox",
3
+ "id": "aria-ease.combobox.listbox",
4
4
  "version": "1.0.0",
5
5
  "created": "11-02-2026",
6
6
  "lastUpdated": "28-02-2026",
7
- "description": "ARIA Combobox interaction contract. Validates the ARIA and interaction contract for a custom combobox component following the ARIA Authoring Practices Guide combobox with listbox popup pattern.",
7
+ "description": "ARIA Combobox with Listbox popup interaction contract. Validates the ARIA and interaction contract for a custom combobox with listbox component following the ARIA Authoring Practices Guide combobox with listbox popup pattern.",
8
8
  "source": {
9
9
  "apg": "https://www.w3.org/WAI/ARIA/apg/patterns/combobox/",
10
10
  "wcag": ["2.2 AA"]
11
11
  },
12
- "W3CName": "Combobox"
12
+ "W3CName": "Combobox Listbox Popup"
13
13
  },
14
14
 
15
15
  "selectors": {
@@ -28,6 +28,20 @@
28
28
  "relative": "first | last | next | previous"
29
29
  },
30
30
 
31
+ "relationships": [
32
+ {
33
+ "type": "aria-reference",
34
+ "from": "input",
35
+ "attribute": "aria-controls",
36
+ "to": "listbox"
37
+ },
38
+ {
39
+ "type": "contains",
40
+ "parent": "listbox",
41
+ "child": "options"
42
+ }
43
+ ],
44
+
31
45
  "static": [
32
46
  {
33
47
  "assertions": [
@@ -49,6 +63,7 @@
49
63
  "target": "input",
50
64
  "assertion": "toHaveAttribute",
51
65
  "attribute": "aria-controls",
66
+ "expectedValue": "!empty",
52
67
  "failureMessage": "Combobox input doesn't conform to the ARIA Combobox pattern. Input should have 'aria-controls' attribute that points to the id of the listbox it controls."
53
68
  },
54
69
  {
@@ -65,20 +80,6 @@
65
80
  "expectedValue": "listbox",
66
81
  "failureMessage": "Combobox input should have 'aria-haspopup=listbox' to indicate that it can trigger a listbox popup."
67
82
  },
68
- {
69
- "target": "listbox",
70
- "assertion": "toHaveAttribute",
71
- "attribute": "role",
72
- "expectedValue": "listbox",
73
- "failureMessage": "Listbox element should have 'role=listbox' attribute."
74
- },
75
- {
76
- "target": "options",
77
- "assertion": "toHaveAttribute",
78
- "attribute": "role",
79
- "expectedValue": "option",
80
- "failureMessage": "Each option in the listbox should have 'role=option' attribute."
81
- },
82
83
  {
83
84
  "target": "options",
84
85
  "assertion": "toHaveAttribute",
@@ -16,7 +16,7 @@
16
16
  "trigger": "[data-test-id=menu-trigger]",
17
17
  "container": "[role=menu]",
18
18
  "items": "[role=menuitem], [role=menuitemradio], [role=menuitemcheckbox]",
19
- "submenuTrigger": "[role=menuitem][aria-haspopup=true], [role=menuitemradio][aria-haspopup=true], [role=menuitemcheckbox][aria-haspopup=true]",
19
+ "submenuTrigger": "[role=menu] [role=menuitem][aria-haspopup=true], [role=menu] [role=menuitemradio][aria-haspopup=true], [role=menu] [role=menuitemcheckbox][aria-haspopup=true], [role=menu] [role=menuitem][aria-haspopup=menu], [role=menu] [role=menuitemradio][aria-haspopup=menu], [role=menu] [role=menuitemcheckbox][aria-haspopup=menu]",
20
20
  "submenu": "[role=menu][aria-labelledby]",
21
21
  "focusable": "[role=menuitem], [role=menuitemradio], [role=menuitemcheckbox]",
22
22
  "relative": "[role=menuitem], [role=menuitemradio], [role=menuitemcheckbox]",
@@ -29,6 +29,26 @@
29
29
  "relative": "first | last | next | previous"
30
30
  },
31
31
 
32
+ "relationships": [
33
+ {
34
+ "type": "aria-reference",
35
+ "from": "trigger",
36
+ "attribute": "aria-controls",
37
+ "to": "container"
38
+ },
39
+ {
40
+ "type": "aria-reference",
41
+ "from": "container",
42
+ "attribute": "aria-labelledby",
43
+ "to": "trigger"
44
+ },
45
+ {
46
+ "type": "contains",
47
+ "parent": "container",
48
+ "child": "items"
49
+ }
50
+ ],
51
+
32
52
  "static": [
33
53
  {
34
54
  "assertions": [
@@ -57,6 +77,27 @@
57
77
  "assertion": "toHaveAttribute",
58
78
  "attribute": "aria-labelledby | aria-label",
59
79
  "failureMessage": "Menu container doesn't conform to the ARIA Menu pattern as specified in APG 1.2. Menu container should have 'aria-labelledby' attribute that points to the id of the trigger button that controls it, or 'aria-label' that describes the purpose of the container."
80
+ },
81
+ {
82
+ "target": "container",
83
+ "assertion": "toHaveAttribute",
84
+ "attribute": "role",
85
+ "expectedValue": "menu",
86
+ "failureMessage": "Menu container doesn't conform to the ARIA Menu pattern as specified in APG 1.2. Menu container should have role of 'menu'"
87
+ },
88
+ {
89
+ "target": "items",
90
+ "assertion": "toHaveAttribute",
91
+ "attribute": "role",
92
+ "expectedValue": "menuitem | menuitemcheckbox | menuitemradio",
93
+ "failureMessage": "Menu items do not conform to the ARIA Menu pattern as specified in APG 1.2. Menu items should have role of 'menuitem', 'menuitemcheckbox', or 'menuitemradio'"
94
+ },
95
+ {
96
+ "target": "items",
97
+ "assertion": "toHaveAttribute",
98
+ "attribute": "tabindex",
99
+ "expectedValue": "-1",
100
+ "failureMessage": "Menu items do not conform to the ARIA Menu pattern as specified in APG 1.2. Menu items' tabindex should have value of '-1'. This ensures the menu items are excluded from the page's Tab sequence"
60
101
  }
61
102
  ]
62
103
  }
@@ -26,6 +26,26 @@
26
26
  "relative": "first | last | next | previous"
27
27
  },
28
28
 
29
+ "relationships": [
30
+ {
31
+ "type": "aria-reference",
32
+ "from": "tab",
33
+ "attribute": "aria-controls",
34
+ "to": "panel"
35
+ },
36
+ {
37
+ "type": "aria-reference",
38
+ "from": "panel",
39
+ "attribute": "aria-labelledby",
40
+ "to": "tab"
41
+ },
42
+ {
43
+ "type": "contains",
44
+ "parent": "tablist",
45
+ "child": "tab"
46
+ }
47
+ ],
48
+
29
49
  "static": [
30
50
  {
31
51
  "assertions": [
@@ -48,13 +68,6 @@
48
68
  "attribute": "aria-controls",
49
69
  "failureMessage": "Tab element doesn't conform to the ARIA Tab pattern as specified in APG 1.2. Tab element should have 'aria-controls' attribute that points to the id of the tab panel it controls. This helps assistive technology to associate the tab with its corresponding tab panel."
50
70
  },
51
- {
52
- "target": "panel",
53
- "assertion": "toHaveAttribute",
54
- "attribute": "role",
55
- "expectedValue": "tabpanel",
56
- "failureMessage": "Tab panel doesn't conform to the ARIA Tab pattern as specified in APG 1.2. Each tab panel should have 'role=tabpanel' attribute. This helps assistive technology identify the panel as a significant region of the page."
57
- },
58
71
  {
59
72
  "target": "panel",
60
73
  "assertion": "toHaveAttribute",