aria-ease 6.9.1 → 6.11.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 (38) hide show
  1. package/README.md +3 -3
  2. package/{bin/buildContracts-GBOY7UXG.js → dist/buildContracts-FT6KWUJN.js} +31 -3
  3. package/{bin/chunk-LMSKLN5O.js → dist/chunk-NI3MQCAS.js} +34 -0
  4. package/{bin → dist}/cli.cjs +239 -24
  5. package/{bin → dist}/cli.js +4 -4
  6. package/dist/{configLoader-WTGJAP4Z.js → configLoader-DWHOHXHL.js} +34 -0
  7. package/{bin/configLoader-Q6A4JLKW.js → dist/configLoader-UJZHQBYS.js} +1 -1
  8. package/{bin/contractTestRunnerPlaywright-ZZNWDUYP.js → dist/contractTestRunnerPlaywright-QDXSK3FE.js} +173 -20
  9. package/dist/{contractTestRunnerPlaywright-XBWJZMR3.js → contractTestRunnerPlaywright-WNWQYSXZ.js} +173 -20
  10. package/dist/index.cjs +568 -298
  11. package/dist/index.d.cts +53 -53
  12. package/dist/index.d.ts +53 -53
  13. package/dist/index.js +364 -281
  14. package/dist/src/combobox/index.cjs +21 -7
  15. package/dist/src/combobox/index.js +21 -7
  16. package/dist/src/utils/test/{configLoader-YE2CYGDG.js → configLoader-SHJSRG2A.js} +34 -0
  17. package/dist/src/utils/test/{contractTestRunnerPlaywright-LC5OAVXB.js → contractTestRunnerPlaywright-Z2AHXSNM.js} +173 -20
  18. package/dist/src/utils/test/dsl/index.cjs +338 -269
  19. package/dist/src/utils/test/dsl/index.d.cts +53 -53
  20. package/dist/src/utils/test/dsl/index.d.ts +53 -53
  21. package/dist/src/utils/test/dsl/index.js +338 -269
  22. package/dist/src/utils/test/index.cjs +207 -20
  23. package/dist/src/utils/test/index.js +2 -2
  24. package/{bin/test-OND56UUL.js → dist/test-O3J4ZPQR.js} +2 -2
  25. package/package.json +4 -5
  26. package/bin/AccordionComponentStrategy-4ZEIQ2V6.js +0 -42
  27. package/bin/ComboboxComponentStrategy-OGRVZXAF.js +0 -64
  28. package/bin/MenuComponentStrategy-JAMTCSNF.js +0 -81
  29. package/bin/TabsComponentStrategy-3SQURPMX.js +0 -29
  30. package/bin/chunk-I2KLQ2HA.js +0 -22
  31. package/bin/chunk-PK5L2SAF.js +0 -17
  32. package/bin/chunk-XERMSYEH.js +0 -363
  33. /package/{bin → dist}/audit-RM6TCZ5C.js +0 -0
  34. /package/{bin → dist}/badgeHelper-JOWO6RQG.js +0 -0
  35. /package/{bin → dist}/chunk-JJEPLK7L.js +0 -0
  36. /package/{bin → dist}/cli.d.cts +0 -0
  37. /package/{bin → dist}/cli.d.ts +0 -0
  38. /package/{bin → dist}/formatters-32KQIIYS.js +0 -0
package/dist/index.js CHANGED
@@ -1022,16 +1022,12 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
1022
1022
  }
1023
1023
  function setActiveDescendant(index) {
1024
1024
  const visibleItems = getVisibleItems();
1025
- visibleItems.forEach((item) => {
1026
- item.setAttribute("aria-selected", "false");
1027
- });
1028
1025
  if (index >= 0 && index < visibleItems.length) {
1029
1026
  const activeItem = visibleItems[index];
1030
1027
  const itemId = activeItem.id || `${listBoxId}-option-${index}`;
1031
1028
  if (!activeItem.id) {
1032
1029
  activeItem.id = itemId;
1033
1030
  }
1034
- activeItem.setAttribute("aria-selected", "true");
1035
1031
  comboboxInput.setAttribute("aria-activedescendant", itemId);
1036
1032
  if (typeof activeItem.scrollIntoView === "function") {
1037
1033
  activeItem.scrollIntoView({ block: "nearest", behavior: "smooth" });
@@ -1064,8 +1060,6 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
1064
1060
  comboboxInput.setAttribute("aria-activedescendant", "");
1065
1061
  listBox.style.display = "none";
1066
1062
  activeIndex = -1;
1067
- const visibleItems = getVisibleItems();
1068
- visibleItems.forEach((item) => item.setAttribute("aria-selected", "false"));
1069
1063
  if (callback?.onOpenChange) {
1070
1064
  try {
1071
1065
  callback.onOpenChange(false);
@@ -1077,6 +1071,7 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
1077
1071
  function selectOption(item) {
1078
1072
  const value = item.textContent?.trim() || "";
1079
1073
  comboboxInput.value = value;
1074
+ item.setAttribute("aria-selected", "true");
1080
1075
  closeListbox();
1081
1076
  if (callback?.onSelect) {
1082
1077
  try {
@@ -1123,6 +1118,10 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
1123
1118
  } else if (comboboxInput.value) {
1124
1119
  event.preventDefault();
1125
1120
  comboboxInput.value = "";
1121
+ const visibleItems2 = getVisibleItems();
1122
+ visibleItems2.forEach((item) => {
1123
+ if (item.getAttribute("aria-selected") === "true") item.setAttribute("aria-selected", "false");
1124
+ });
1126
1125
  if (callback?.onClear) {
1127
1126
  try {
1128
1127
  callback.onClear();
@@ -1201,9 +1200,24 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
1201
1200
  function initializeOptions() {
1202
1201
  const items = listBox.querySelectorAll(`.${listBoxItemsClass}`);
1203
1202
  if (items.length === 0) return;
1203
+ let selectedValue = null;
1204
+ for (const item of items) {
1205
+ if (item.getAttribute("aria-selected") === "true") {
1206
+ selectedValue = item.textContent?.trim() || null;
1207
+ break;
1208
+ }
1209
+ }
1210
+ if (!selectedValue && comboboxInput.value) {
1211
+ selectedValue = comboboxInput.value.trim();
1212
+ }
1204
1213
  items.forEach((item, index) => {
1205
1214
  item.setAttribute("role", "option");
1206
- item.setAttribute("aria-selected", "false");
1215
+ const itemValue = item.textContent?.trim() || "";
1216
+ if (selectedValue && itemValue === selectedValue) {
1217
+ item.setAttribute("aria-selected", "true");
1218
+ } else {
1219
+ item.setAttribute("aria-selected", "false");
1220
+ }
1207
1221
  const currentId = item.getAttribute("id");
1208
1222
  if (!currentId || currentId === "") {
1209
1223
  const itemId = `${listBoxId}-option-${index}`;
@@ -1494,315 +1508,384 @@ function makeTabsAccessible({ tabListId, tabsClass, tabPanelsClass, orientation
1494
1508
  return { activateTab, cleanup, refresh };
1495
1509
  }
1496
1510
 
1497
- // src/utils/test/dsl/index.ts
1498
- var FluentContract = class {
1499
- constructor(jsonContract) {
1500
- this.jsonContract = jsonContract;
1501
- }
1502
- toJSON() {
1503
- return this.jsonContract;
1504
- }
1505
- };
1506
- var StaticTargetBuilder = class {
1507
- constructor(targetName, sink) {
1508
- this.targetName = targetName;
1509
- this.sink = sink;
1510
- }
1511
- has(attribute, expectedValue) {
1512
- const create = (level) => {
1513
- this.sink.push({
1514
- target: this.targetName,
1515
- attribute,
1516
- expectedValue,
1517
- failureMessage: `Expected ${this.targetName} to have ${attribute}${expectedValue !== void 0 ? `=${expectedValue}` : ""}.`,
1518
- level
1519
- });
1520
- };
1521
- return {
1522
- required: () => create("required"),
1523
- recommended: () => create("recommended"),
1524
- optional: () => create("optional")
1525
- };
1511
+ // src/utils/test/dsl/src/state-packs/comboboxStatePack.ts
1512
+ function hasCapabilities(ctx, requiredCaps) {
1513
+ return requiredCaps.some((cap) => ctx.capabilities.includes(cap));
1514
+ }
1515
+ function resolveSetup(setup, ctx) {
1516
+ if (Array.isArray(setup) && setup.length && !setup[0].when) {
1517
+ setup = [{ when: ["keyboard"], steps: () => setup }];
1526
1518
  }
1527
- };
1528
- var StaticBuilder = class {
1529
- constructor(sink) {
1530
- this.sink = sink;
1519
+ for (const strat of setup) {
1520
+ if (hasCapabilities(ctx, strat.when)) {
1521
+ return strat.steps(ctx);
1522
+ }
1531
1523
  }
1532
- target(targetName) {
1533
- return new StaticTargetBuilder(targetName, this.sink);
1524
+ throw new Error(
1525
+ `No setup strategy matches capabilities: ${ctx.capabilities.join(", ")}`
1526
+ );
1527
+ }
1528
+ var COMBOBOX_STATES = {
1529
+ "listbox.open": {
1530
+ setup: [
1531
+ {
1532
+ when: ["keyboard", "textInput"],
1533
+ steps: () => [
1534
+ { type: "keypress", target: "input", key: "ArrowDown" }
1535
+ ]
1536
+ },
1537
+ {
1538
+ when: ["pointer"],
1539
+ steps: () => [
1540
+ { type: "click", target: "button" }
1541
+ ]
1542
+ }
1543
+ ],
1544
+ assertion: isComboboxOpen
1545
+ },
1546
+ "listbox.closed": {
1547
+ setup: [
1548
+ {
1549
+ when: ["keyboard"],
1550
+ steps: () => [
1551
+ /* { type: "keypress", target: "input", key: "Escape" } */
1552
+ ]
1553
+ },
1554
+ {
1555
+ when: ["pointer"],
1556
+ steps: () => [
1557
+ /* { type: "click", target: "button" } */
1558
+ ]
1559
+ }
1560
+ ],
1561
+ assertion: isComboboxClosed
1562
+ },
1563
+ "input.focused": {
1564
+ setup: [
1565
+ {
1566
+ when: ["keyboard"],
1567
+ steps: () => [
1568
+ { type: "focus", target: "input" }
1569
+ ]
1570
+ }
1571
+ ],
1572
+ assertion: isInputFocused
1573
+ },
1574
+ "input.filled": {
1575
+ setup: [
1576
+ {
1577
+ when: ["keyboard", "textInput"],
1578
+ steps: () => [
1579
+ { type: "type", target: "input", value: "test" }
1580
+ ]
1581
+ }
1582
+ ],
1583
+ assertion: isInputFilled
1584
+ },
1585
+ "activeOption.first": {
1586
+ requires: ["listbox.open"],
1587
+ setup: [
1588
+ {
1589
+ when: ["keyboard"],
1590
+ steps: () => [
1591
+ { type: "keypress", target: "input", key: "ArrowDown" }
1592
+ ]
1593
+ }
1594
+ ],
1595
+ assertion: isActiveDescendantNotEmpty
1596
+ },
1597
+ "activeOption.last": {
1598
+ requires: ["activeOption.first"],
1599
+ setup: [
1600
+ {
1601
+ when: ["keyboard"],
1602
+ steps: () => [
1603
+ { type: "keypress", target: "input", key: "ArrowUp" }
1604
+ ]
1605
+ }
1606
+ ],
1607
+ assertion: isActiveDescendantNotEmpty
1608
+ },
1609
+ "selectedOption.first": {
1610
+ requires: ["listbox.open"],
1611
+ setup: [
1612
+ {
1613
+ when: ["pointer"],
1614
+ steps: () => [
1615
+ { type: "click", target: "relative", relativeTarget: "first" }
1616
+ ]
1617
+ }
1618
+ ],
1619
+ assertion: () => isAriaSelected("first")
1620
+ },
1621
+ "selectedOption.last": {
1622
+ requires: ["listbox.open"],
1623
+ setup: [
1624
+ {
1625
+ when: ["pointer"],
1626
+ steps: () => [
1627
+ { type: "click", target: "relative", relativeTarget: "last" }
1628
+ ]
1629
+ }
1630
+ ],
1631
+ assertion: () => isAriaSelected("last")
1534
1632
  }
1535
1633
  };
1536
- var DynamicChain = class {
1537
- constructor(key, testsSink, selectors) {
1538
- this.key = key;
1539
- this.testsSink = testsSink;
1540
- this.selectors = selectors;
1541
- }
1542
- selectorTarget = "";
1543
- actions = [];
1544
- assertions = [];
1545
- explicitDescription = "";
1546
- on(target) {
1547
- this.selectorTarget = target;
1548
- this.actions.push({ type: "keypress", target, key: this.key });
1549
- return this;
1550
- }
1551
- describe(description) {
1552
- this.explicitDescription = description;
1553
- return this;
1554
- }
1555
- focus(targetExpression) {
1556
- const parsed = this.parseRelativeExpression(targetExpression);
1557
- if (parsed) {
1558
- if (!this.selectors[parsed.selectorKey]) {
1559
- const availableSelectors = Object.keys(this.selectors).sort().join(", ") || "(none)";
1560
- throw new Error(
1561
- `Invalid focus target expression "${targetExpression}": selector "${parsed.selectorKey}" is not defined. Available selectors: ${availableSelectors}`
1562
- );
1563
- }
1564
- if (!this.selectors.relative && this.selectors[parsed.selectorKey]) {
1565
- this.selectors.relative = this.selectors[parsed.selectorKey];
1566
- }
1567
- this.assertions.push({
1568
- target: "relative",
1569
- assertion: "toHaveFocus",
1570
- relativeTarget: parsed.relativeTarget
1571
- });
1572
- } else {
1573
- this.assertions.push({
1574
- target: targetExpression,
1575
- assertion: "toHaveFocus"
1576
- });
1634
+ function isComboboxOpen() {
1635
+ return [
1636
+ {
1637
+ target: "listbox",
1638
+ assertion: "toBeVisible",
1639
+ failureMessage: "Expected listbox to be visible"
1640
+ },
1641
+ {
1642
+ target: "input",
1643
+ assertion: "toHaveAttribute",
1644
+ attribute: "aria-expanded",
1645
+ expectedValue: "true",
1646
+ failureMessage: "Expect combobox input to have aria-expanded='true'"
1577
1647
  }
1578
- return this;
1579
- }
1580
- visible(target) {
1581
- this.assertions.push({ target, assertion: "toBeVisible" });
1582
- return this;
1583
- }
1584
- hidden(target) {
1585
- this.assertions.push({ target, assertion: "notToBeVisible" });
1586
- return this;
1587
- }
1588
- has(target, attribute, expectedValue) {
1589
- this.assertions.push({
1590
- target,
1648
+ ];
1649
+ }
1650
+ function isComboboxClosed() {
1651
+ return [
1652
+ {
1653
+ target: "listbox",
1654
+ assertion: "notToBeVisible",
1655
+ failureMessage: "Expected listbox to be closed"
1656
+ },
1657
+ {
1658
+ target: "input",
1591
1659
  assertion: "toHaveAttribute",
1592
- attribute,
1593
- expectedValue
1594
- });
1595
- return this;
1596
- }
1597
- required() {
1598
- this.finalize("required");
1599
- }
1600
- recommended() {
1601
- this.finalize("recommended");
1602
- }
1603
- optional() {
1604
- this.finalize("optional");
1605
- }
1606
- finalize(level) {
1607
- if (!this.selectorTarget) {
1608
- throw new Error("Dynamic contract chain requires .on(<selectorKey>) before level terminator.");
1609
- }
1610
- const description = this.explicitDescription || `Pressing ${this.key} on ${this.selectorTarget} satisfies expected behavior.`;
1611
- this.testsSink.push({
1612
- description,
1613
- level,
1614
- action: this.actions,
1615
- assertions: this.assertions.map((a) => ({ ...a, level }))
1616
- });
1660
+ attribute: "aria-expanded",
1661
+ expectedValue: "false",
1662
+ failureMessage: "Expect combobox input to have aria-expanded='false'"
1663
+ }
1664
+ ];
1665
+ }
1666
+ function isActiveDescendantNotEmpty() {
1667
+ return [
1668
+ {
1669
+ target: "input",
1670
+ assertion: "toHaveAttribute",
1671
+ attribute: "aria-activedescendant",
1672
+ expectedValue: "!empty",
1673
+ failureMessage: "Expected aria-activedescendant to not be empty"
1674
+ }
1675
+ ];
1676
+ }
1677
+ function isAriaSelected(index) {
1678
+ return [
1679
+ {
1680
+ target: "relative",
1681
+ relativeTarget: index,
1682
+ assertion: "toHaveAttribute",
1683
+ attribute: "aria-selected",
1684
+ expectedValue: "true",
1685
+ failureMessage: `Expected ${index} option to have aria-selected='true'`
1686
+ }
1687
+ ];
1688
+ }
1689
+ function isInputFocused() {
1690
+ return [
1691
+ {
1692
+ target: "input",
1693
+ assertion: "toHaveFocus",
1694
+ failureMessage: "Expected input to be focused"
1695
+ }
1696
+ ];
1697
+ }
1698
+ function isInputFilled() {
1699
+ return [
1700
+ {
1701
+ target: "input",
1702
+ assertion: "toHaveValue",
1703
+ expectedValue: "test",
1704
+ failureMessage: "Expected input to have the value 'test'"
1705
+ }
1706
+ ];
1707
+ }
1708
+
1709
+ // src/utils/test/dsl/src/contractBuilder.ts
1710
+ var STATE_PACKS = {
1711
+ "combobox.listbox": COMBOBOX_STATES
1712
+ // Add more mappings as needed
1713
+ };
1714
+ var FluentContract = class {
1715
+ constructor(jsonContract) {
1716
+ this.jsonContract = jsonContract;
1617
1717
  }
1618
- parseRelativeExpression(input) {
1619
- const match = input.match(/^(next|previous|first|last)\(([^)]+)\)$/);
1620
- if (!match) return null;
1621
- const relativeTarget = match[1];
1622
- const selectorKey = match[2].trim();
1623
- return { relativeTarget, selectorKey };
1718
+ toJSON() {
1719
+ return this.jsonContract;
1624
1720
  }
1625
1721
  };
1626
1722
  var ContractBuilder = class {
1627
1723
  constructor(componentName) {
1628
1724
  this.componentName = componentName;
1725
+ this.statePack = STATE_PACKS[componentName] || {};
1629
1726
  }
1630
1727
  metaValue = {};
1631
1728
  selectorsValue = {};
1632
1729
  relationshipInvariants = [];
1633
1730
  staticAssertions = [];
1634
1731
  dynamicTests = [];
1732
+ statePack;
1635
1733
  meta(meta) {
1636
- this.metaValue = { ...this.metaValue, ...meta };
1734
+ this.metaValue = meta;
1637
1735
  return this;
1638
1736
  }
1639
1737
  selectors(selectors) {
1640
- this.selectorsValue = { ...this.selectorsValue, ...selectors };
1738
+ this.selectorsValue = selectors;
1641
1739
  return this;
1642
1740
  }
1643
- relationship(invariant) {
1644
- this.relationshipInvariants.push(invariant);
1741
+ relationships(fn) {
1742
+ const api = {
1743
+ ariaReference: (from, attribute, to) => ({
1744
+ required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required" }),
1745
+ optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" })
1746
+ }),
1747
+ contains: (parent, child) => ({
1748
+ required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required" }),
1749
+ optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" })
1750
+ })
1751
+ };
1752
+ fn(api);
1645
1753
  return this;
1646
1754
  }
1647
- relationships(builderFn) {
1648
- builderFn({
1649
- ariaReference: (from, attribute, to) => {
1650
- const create = (level) => {
1651
- this.relationshipInvariants.push({
1652
- type: "aria-reference",
1653
- from,
1654
- attribute,
1655
- to,
1656
- level
1657
- });
1658
- };
1659
- return {
1660
- required: () => create("required"),
1661
- recommended: () => create("recommended"),
1662
- optional: () => create("optional")
1663
- };
1664
- },
1665
- contains: (parent, child) => {
1666
- const create = (level) => {
1667
- this.relationshipInvariants.push({
1668
- type: "contains",
1669
- parent,
1670
- child,
1671
- level
1672
- });
1673
- };
1674
- return {
1675
- required: () => create("required"),
1676
- recommended: () => create("recommended"),
1677
- optional: () => create("optional")
1678
- };
1679
- }
1680
- });
1755
+ static(fn) {
1756
+ const api = {
1757
+ target: (target) => ({
1758
+ has: (attribute, expectedValue) => ({
1759
+ required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required" }),
1760
+ optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" })
1761
+ })
1762
+ })
1763
+ };
1764
+ fn(api);
1681
1765
  return this;
1682
1766
  }
1683
- static(builderFn) {
1684
- builderFn(new StaticBuilder(this.staticAssertions));
1767
+ when(event) {
1768
+ return new DynamicTestBuilder(this, this.statePack, event);
1769
+ }
1770
+ addDynamicTest(test) {
1771
+ this.dynamicTests.push(test);
1772
+ }
1773
+ build() {
1774
+ return {
1775
+ meta: this.metaValue,
1776
+ selectors: this.selectorsValue,
1777
+ relationships: this.relationshipInvariants.length ? this.relationshipInvariants : void 0,
1778
+ static: this.staticAssertions.length ? [{ assertions: this.staticAssertions }] : [],
1779
+ dynamic: this.dynamicTests
1780
+ };
1781
+ }
1782
+ };
1783
+ var DynamicTestBuilder = class {
1784
+ constructor(parent, statePack, event) {
1785
+ this.parent = parent;
1786
+ this.statePack = statePack;
1787
+ this.event = event;
1788
+ }
1789
+ _as;
1790
+ _on;
1791
+ _given = [];
1792
+ _then = [];
1793
+ _desc = "";
1794
+ _level = "required";
1795
+ as(actionType) {
1796
+ this._as = actionType;
1685
1797
  return this;
1686
1798
  }
1687
- when(key) {
1688
- return new DynamicChain(key, this.dynamicTests, this.selectorsValue);
1799
+ on(target) {
1800
+ this._on = target;
1801
+ return this;
1689
1802
  }
1690
- validateRelationshipInvariants() {
1691
- if (this.relationshipInvariants.length === 0) {
1692
- return;
1693
- }
1694
- const selectorKeys = new Set(Object.keys(this.selectorsValue));
1695
- const available = Object.keys(this.selectorsValue).sort().join(", ");
1696
- const errors = [];
1697
- this.relationshipInvariants.forEach((invariant, index) => {
1698
- const prefix = `relationships[${index}] (${invariant.type})`;
1699
- if (invariant.type === "aria-reference") {
1700
- if (!selectorKeys.has(invariant.from)) {
1701
- errors.push(`${prefix}: "from" references unknown selector "${invariant.from}"`);
1702
- }
1703
- if (!selectorKeys.has(invariant.to)) {
1704
- errors.push(`${prefix}: "to" references unknown selector "${invariant.to}"`);
1705
- }
1706
- }
1707
- if (invariant.type === "contains") {
1708
- if (!selectorKeys.has(invariant.parent)) {
1709
- errors.push(`${prefix}: "parent" references unknown selector "${invariant.parent}"`);
1710
- }
1711
- if (!selectorKeys.has(invariant.child)) {
1712
- errors.push(`${prefix}: "child" references unknown selector "${invariant.child}"`);
1713
- }
1714
- }
1715
- });
1716
- if (errors.length > 0) {
1717
- const availableSelectorsMessage = available.length > 0 ? available : "(none)";
1718
- throw new Error(
1719
- [
1720
- `Contract invariant validation failed for component "${this.componentName}".`,
1721
- ...errors.map((error) => `- ${error}`),
1722
- `Available selectors: ${availableSelectorsMessage}`
1723
- ].join("\n")
1724
- );
1725
- }
1803
+ given(states) {
1804
+ this._given = Array.isArray(states) ? states : [states];
1805
+ return this;
1726
1806
  }
1727
- validateStaticTargets() {
1728
- const selectorKeys = new Set(Object.keys(this.selectorsValue));
1729
- const available = Object.keys(this.selectorsValue).sort().join(", ") || "(none)";
1730
- const errors = [];
1731
- this.staticAssertions.forEach((assertion, index) => {
1732
- if (!selectorKeys.has(assertion.target)) {
1733
- errors.push(`static.assertions[${index}]: target "${assertion.target}" is not defined in selectors`);
1734
- }
1735
- });
1736
- if (errors.length > 0) {
1737
- throw new Error(
1738
- [
1739
- `Contract static target validation failed for component "${this.componentName}".`,
1740
- ...errors.map((error) => `- ${error}`),
1741
- `Available selectors: ${available}`
1742
- ].join("\n")
1743
- );
1744
- }
1807
+ then(states) {
1808
+ this._then = Array.isArray(states) ? states : [states];
1809
+ return this;
1745
1810
  }
1746
- validateDynamicTargets() {
1747
- const selectorKeys = new Set(Object.keys(this.selectorsValue));
1748
- const available = Object.keys(this.selectorsValue).sort().join(", ") || "(none)";
1749
- const errors = [];
1750
- const isValidActionTarget = (target) => {
1751
- return selectorKeys.has(target) || target === "document" || target === "relative";
1752
- };
1753
- const isValidAssertionTarget = (target) => {
1754
- return selectorKeys.has(target) || target === "relative";
1811
+ describe(desc) {
1812
+ this._desc = desc;
1813
+ return this;
1814
+ }
1815
+ required() {
1816
+ this._level = "required";
1817
+ this._finalize();
1818
+ return this.parent;
1819
+ }
1820
+ optional() {
1821
+ this._level = "optional";
1822
+ this._finalize();
1823
+ return this.parent;
1824
+ }
1825
+ recommended() {
1826
+ this._level = "recommended";
1827
+ this._finalize();
1828
+ return this.parent;
1829
+ }
1830
+ _finalize() {
1831
+ const capabilityMap = {
1832
+ keypress: "keyboard",
1833
+ click: "pointer",
1834
+ type: "textInput",
1835
+ focus: "keyboard",
1836
+ hover: "pointer"
1837
+ // add more mappings as needed
1755
1838
  };
1756
- this.dynamicTests.forEach((test, testIndex) => {
1757
- test.action.forEach((action, actionIndex) => {
1758
- if (!isValidActionTarget(action.target)) {
1759
- errors.push(
1760
- `dynamic[${testIndex}].action[${actionIndex}]: target "${action.target}" is not defined in selectors`
1761
- );
1839
+ const capability = capabilityMap[this._as || "keyboard"] || (this._as || "keyboard");
1840
+ const ctx = { capabilities: [capability] };
1841
+ const resolveAllSetups = (stateName, visited = /* @__PURE__ */ new Set()) => {
1842
+ if (visited.has(stateName)) return [];
1843
+ visited.add(stateName);
1844
+ const s = this.statePack[stateName];
1845
+ if (!s) return [];
1846
+ let actions = [];
1847
+ if (Array.isArray(s.requires)) {
1848
+ for (const req of s.requires) {
1849
+ actions = actions.concat(resolveAllSetups(req, visited));
1762
1850
  }
1763
- });
1764
- test.assertions.forEach((assertion, assertionIndex) => {
1765
- if (!isValidAssertionTarget(assertion.target)) {
1766
- errors.push(
1767
- `dynamic[${testIndex}].assertions[${assertionIndex}]: target "${assertion.target}" is not defined in selectors`
1768
- );
1769
- }
1770
- if (assertion.target === "relative" && !this.selectorsValue.relative) {
1771
- errors.push(
1772
- `dynamic[${testIndex}].assertions[${assertionIndex}]: target "relative" requires selectors.relative to be defined`
1773
- );
1851
+ }
1852
+ if (s.setup) actions = actions.concat(resolveSetup(s.setup, ctx));
1853
+ return actions;
1854
+ };
1855
+ const setup = [];
1856
+ for (const state of this._given) {
1857
+ setup.push(...resolveAllSetups(state));
1858
+ }
1859
+ const assertions = [];
1860
+ for (const state of this._then) {
1861
+ const s = this.statePack[state];
1862
+ if (s && s.assertion !== void 0) {
1863
+ let value = s.assertion;
1864
+ if (typeof value === "function") {
1865
+ try {
1866
+ value = value();
1867
+ } catch (e) {
1868
+ throw new Error(`Error calling assertion function for state '${state}': ${e.message}`);
1869
+ }
1774
1870
  }
1775
- });
1871
+ if (Array.isArray(value)) assertions.push(...value);
1872
+ else assertions.push(value);
1873
+ }
1874
+ }
1875
+ const action = [
1876
+ {
1877
+ type: this._as,
1878
+ target: this._on,
1879
+ key: this._as === "keypress" ? this.event : void 0
1880
+ }
1881
+ ];
1882
+ this.parent.addDynamicTest({
1883
+ description: this._desc || "",
1884
+ level: this._level,
1885
+ action,
1886
+ assertions,
1887
+ ...setup.length ? { setup } : {}
1776
1888
  });
1777
- if (errors.length > 0) {
1778
- throw new Error(
1779
- [
1780
- `Contract dynamic target validation failed for component "${this.componentName}".`,
1781
- ...errors.map((error) => `- ${error}`),
1782
- `Available selectors: ${available}`,
1783
- `Allowed special targets: document, relative`
1784
- ].join("\n")
1785
- );
1786
- }
1787
- }
1788
- build() {
1789
- this.validateRelationshipInvariants();
1790
- this.validateStaticTargets();
1791
- this.validateDynamicTargets();
1792
- const fallbackId = this.metaValue.id || `aria-ease.contract.${this.componentName}`;
1793
- return {
1794
- meta: {
1795
- id: fallbackId,
1796
- version: this.metaValue.version || "1.0.0",
1797
- description: this.metaValue.description || `Fluent contract for ${this.componentName}`,
1798
- source: this.metaValue.source,
1799
- W3CName: this.metaValue.W3CName
1800
- },
1801
- selectors: this.selectorsValue,
1802
- relationships: this.relationshipInvariants,
1803
- static: [{ assertions: this.staticAssertions }],
1804
- dynamic: this.dynamicTests
1805
- };
1806
1889
  }
1807
1890
  };
1808
1891
  function createContract(componentName, define) {
@@ -2025,7 +2108,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
2025
2108
  let configBaseDir = typeof process !== "undefined" ? process.cwd() : "";
2026
2109
  if (typeof process !== "undefined" && typeof process.cwd === "function") {
2027
2110
  try {
2028
- const { loadConfig } = await import("./configLoader-WTGJAP4Z.js");
2111
+ const { loadConfig } = await import("./configLoader-DWHOHXHL.js");
2029
2112
  const result2 = await loadConfig(process.cwd());
2030
2113
  config = result2.config;
2031
2114
  if (result2.configPath) {
@@ -2047,7 +2130,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
2047
2130
  const devServerUrl = await checkDevServer(url);
2048
2131
  if (devServerUrl) {
2049
2132
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
2050
- const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-XBWJZMR3.js");
2133
+ const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-WNWQYSXZ.js");
2051
2134
  contract = await runContractTestsPlaywright(componentName, devServerUrl, strictness, config, configBaseDir);
2052
2135
  } else {
2053
2136
  throw new Error(