aria-ease 6.8.0 → 6.9.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 (36) hide show
  1. package/README.md +68 -6
  2. package/bin/AccordionComponentStrategy-4ZEIQ2V6.js +42 -0
  3. package/bin/ComboboxComponentStrategy-OGRVZXAF.js +64 -0
  4. package/bin/MenuComponentStrategy-JAMTCSNF.js +81 -0
  5. package/bin/TabsComponentStrategy-3SQURPMX.js +29 -0
  6. package/bin/buildContracts-GBOY7UXG.js +437 -0
  7. package/bin/{chunk-VPBHLMAS.js → chunk-LMSKLN5O.js} +21 -0
  8. package/bin/chunk-PK5L2SAF.js +17 -0
  9. package/bin/{chunk-2TOYEY5L.js → chunk-XERMSYEH.js} +12 -3
  10. package/bin/cli.cjs +991 -128
  11. package/bin/cli.js +33 -2
  12. package/bin/{configLoader-XRF6VM4J.js → configLoader-Q6A4JLKW.js} +1 -1
  13. package/{dist/contractTestRunnerPlaywright-UAOFNS7Z.js → bin/contractTestRunnerPlaywright-ZZNWDUYP.js} +270 -219
  14. package/bin/{test-WRIJHN6H.js → test-OND56UUL.js} +97 -10
  15. package/dist/AccordionComponentStrategy-4ZEIQ2V6.js +42 -0
  16. package/dist/ComboboxComponentStrategy-OGRVZXAF.js +64 -0
  17. package/dist/MenuComponentStrategy-JAMTCSNF.js +81 -0
  18. package/dist/TabsComponentStrategy-3SQURPMX.js +29 -0
  19. package/dist/chunk-PK5L2SAF.js +17 -0
  20. package/dist/{chunk-2TOYEY5L.js → chunk-XERMSYEH.js} +12 -3
  21. package/dist/{configLoader-IT4PWCJB.js → configLoader-WTGJAP4Z.js} +21 -0
  22. package/{bin/contractTestRunnerPlaywright-UAOFNS7Z.js → dist/contractTestRunnerPlaywright-XBWJZMR3.js} +270 -219
  23. package/dist/index.cjs +800 -96
  24. package/dist/index.d.cts +136 -1
  25. package/dist/index.d.ts +136 -1
  26. package/dist/index.js +421 -16
  27. package/dist/src/utils/test/AccordionComponentStrategy-WRHZOEN6.js +38 -0
  28. package/dist/src/utils/test/ComboboxComponentStrategy-5AECQSRN.js +60 -0
  29. package/dist/src/utils/test/MenuComponentStrategy-VKZQYLBE.js +77 -0
  30. package/dist/src/utils/test/TabsComponentStrategy-BKG53SEV.js +26 -0
  31. package/dist/src/utils/test/{chunk-2TOYEY5L.js → chunk-XERMSYEH.js} +12 -3
  32. package/dist/src/utils/test/{configLoader-LD4RV2WQ.js → configLoader-YE2CYGDG.js} +21 -0
  33. package/dist/src/utils/test/{contractTestRunnerPlaywright-IRJOAEMT.js → contractTestRunnerPlaywright-LC5OAVXB.js} +262 -200
  34. package/dist/src/utils/test/index.cjs +472 -88
  35. package/dist/src/utils/test/index.js +97 -12
  36. package/package.json +7 -2
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  normalizeLevel,
6
6
  normalizeStrictness,
7
7
  resolveEnforcement
8
- } from "./chunk-2TOYEY5L.js";
8
+ } from "./chunk-XERMSYEH.js";
9
9
  import "./chunk-I2KLQ2HA.js";
10
10
 
11
11
  // src/accordion/src/makeAccordionAccessible/makeAccordionAccessible.ts
@@ -1494,6 +1494,323 @@ function makeTabsAccessible({ tabListId, tabsClass, tabPanelsClass, orientation
1494
1494
  return { activateTab, cleanup, refresh };
1495
1495
  }
1496
1496
 
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
+ };
1526
+ }
1527
+ };
1528
+ var StaticBuilder = class {
1529
+ constructor(sink) {
1530
+ this.sink = sink;
1531
+ }
1532
+ target(targetName) {
1533
+ return new StaticTargetBuilder(targetName, this.sink);
1534
+ }
1535
+ };
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
+ });
1577
+ }
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,
1591
+ 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
+ });
1617
+ }
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 };
1624
+ }
1625
+ };
1626
+ var ContractBuilder = class {
1627
+ constructor(componentName) {
1628
+ this.componentName = componentName;
1629
+ }
1630
+ metaValue = {};
1631
+ selectorsValue = {};
1632
+ relationshipInvariants = [];
1633
+ staticAssertions = [];
1634
+ dynamicTests = [];
1635
+ meta(meta) {
1636
+ this.metaValue = { ...this.metaValue, ...meta };
1637
+ return this;
1638
+ }
1639
+ selectors(selectors) {
1640
+ this.selectorsValue = { ...this.selectorsValue, ...selectors };
1641
+ return this;
1642
+ }
1643
+ relationship(invariant) {
1644
+ this.relationshipInvariants.push(invariant);
1645
+ return this;
1646
+ }
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
+ });
1681
+ return this;
1682
+ }
1683
+ static(builderFn) {
1684
+ builderFn(new StaticBuilder(this.staticAssertions));
1685
+ return this;
1686
+ }
1687
+ when(key) {
1688
+ return new DynamicChain(key, this.dynamicTests, this.selectorsValue);
1689
+ }
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
+ }
1726
+ }
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
+ }
1745
+ }
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";
1755
+ };
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
+ );
1762
+ }
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
+ );
1774
+ }
1775
+ });
1776
+ });
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
+ }
1807
+ };
1808
+ function contract(componentName, define) {
1809
+ const builder = new ContractBuilder(componentName);
1810
+ define(builder);
1811
+ return new FluentContract(builder.build());
1812
+ }
1813
+
1497
1814
  // src/utils/test/src/test.ts
1498
1815
  import { axe } from "jest-axe";
1499
1816
 
@@ -1510,7 +1827,7 @@ async function runContractTests(componentName, component, strictness) {
1510
1827
  const resolvedPath = new URL(contractPath, import.meta.url).pathname;
1511
1828
  const contractData = await fs.readFile(resolvedPath, "utf-8");
1512
1829
  const componentContract = JSON.parse(contractData);
1513
- const totalTests = componentContract.static[0].assertions.length + componentContract.dynamic.length;
1830
+ const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
1514
1831
  reporter.start(componentName, totalTests);
1515
1832
  const failures = [];
1516
1833
  const passes = [];
@@ -1534,6 +1851,82 @@ async function runContractTests(componentName, component, strictness) {
1534
1851
  let staticPassed = 0;
1535
1852
  let staticFailed = 0;
1536
1853
  let staticWarnings = 0;
1854
+ for (const rel of componentContract.relationships || []) {
1855
+ const relationshipLevel = normalizeLevel(rel.level);
1856
+ if (rel.type === "aria-reference") {
1857
+ const fromSelector = componentContract.selectors[rel.from];
1858
+ const toSelector = componentContract.selectors[rel.to];
1859
+ const relDescription = `${rel.from}.${rel.attribute} references ${rel.to}`;
1860
+ if (!fromSelector || !toSelector) {
1861
+ const outcome = classifyFailure(`Relationship selector missing: from="${rel.from}" or to="${rel.to}" not found in selectors.`, rel.level);
1862
+ if (outcome.status === "fail") staticFailed += 1;
1863
+ if (outcome.status === "warn") staticWarnings += 1;
1864
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
1865
+ continue;
1866
+ }
1867
+ const fromTarget = component.querySelector(fromSelector);
1868
+ const toTarget = component.querySelector(toSelector);
1869
+ if (!fromTarget || !toTarget) {
1870
+ const outcome = classifyFailure(`Relationship target not found: ${!fromTarget ? rel.from : rel.to}.`, rel.level);
1871
+ if (outcome.status === "fail") staticFailed += 1;
1872
+ if (outcome.status === "warn") staticWarnings += 1;
1873
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
1874
+ continue;
1875
+ }
1876
+ const toId = toTarget.getAttribute("id");
1877
+ const attrValue = fromTarget.getAttribute(rel.attribute) || "";
1878
+ if (!toId) {
1879
+ const outcome = classifyFailure(`Relationship target "${rel.to}" must have an id for ${rel.attribute} validation.`, rel.level);
1880
+ if (outcome.status === "fail") staticFailed += 1;
1881
+ if (outcome.status === "warn") staticWarnings += 1;
1882
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
1883
+ continue;
1884
+ }
1885
+ const references = attrValue.split(/\s+/).filter(Boolean);
1886
+ if (!references.includes(toId)) {
1887
+ const outcome = classifyFailure(`Expected ${rel.from} ${rel.attribute} to reference id "${toId}", found "${attrValue}".`, rel.level);
1888
+ if (outcome.status === "fail") staticFailed += 1;
1889
+ if (outcome.status === "warn") staticWarnings += 1;
1890
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
1891
+ continue;
1892
+ }
1893
+ passes.push(`Relationship valid: ${rel.from}.${rel.attribute} -> ${rel.to} (id=${toId}).`);
1894
+ staticPassed += 1;
1895
+ reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
1896
+ continue;
1897
+ }
1898
+ if (rel.type === "contains") {
1899
+ const parentSelector = componentContract.selectors[rel.parent];
1900
+ const childSelector = componentContract.selectors[rel.child];
1901
+ const relDescription = `${rel.parent} contains ${rel.child}`;
1902
+ if (!parentSelector || !childSelector) {
1903
+ const outcome = classifyFailure(`Relationship selector missing: parent="${rel.parent}" or child="${rel.child}" not found in selectors.`, rel.level);
1904
+ if (outcome.status === "fail") staticFailed += 1;
1905
+ if (outcome.status === "warn") staticWarnings += 1;
1906
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
1907
+ continue;
1908
+ }
1909
+ const parentTarget = component.querySelector(parentSelector);
1910
+ if (!parentTarget) {
1911
+ const outcome = classifyFailure(`Relationship parent target not found: ${rel.parent}.`, rel.level);
1912
+ if (outcome.status === "fail") staticFailed += 1;
1913
+ if (outcome.status === "warn") staticWarnings += 1;
1914
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
1915
+ continue;
1916
+ }
1917
+ const nestedChild = parentTarget.querySelector(childSelector);
1918
+ if (!nestedChild) {
1919
+ const outcome = classifyFailure(`Expected ${rel.parent} to contain descendant matching selector for ${rel.child}.`, rel.level);
1920
+ if (outcome.status === "fail") staticFailed += 1;
1921
+ if (outcome.status === "warn") staticWarnings += 1;
1922
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
1923
+ continue;
1924
+ }
1925
+ passes.push(`Relationship valid: ${rel.parent} contains ${rel.child}.`);
1926
+ staticPassed += 1;
1927
+ reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
1928
+ }
1929
+ }
1537
1930
  for (const test of componentContract.static[0].assertions) {
1538
1931
  if (test.target !== "relative") {
1539
1932
  const staticLevel = normalizeLevel(test.level);
@@ -1589,6 +1982,7 @@ async function runContractTests(componentName, component, strictness) {
1589
1982
  }
1590
1983
 
1591
1984
  // src/utils/test/src/test.ts
1985
+ import path from "path";
1592
1986
  async function testUiComponent(componentName, component, url, options = {}) {
1593
1987
  if (!componentName || typeof componentName !== "string") {
1594
1988
  throw new Error("\u274C testUiComponent requires a valid componentName (string)");
@@ -1627,24 +2021,34 @@ Error: ${error instanceof Error ? error.message : String(error)}`
1627
2021
  return null;
1628
2022
  }
1629
2023
  let strictness = normalizeStrictness(options.strictness);
1630
- if (options.strictness === void 0 && typeof window === "undefined") {
2024
+ let config = {};
2025
+ let configBaseDir = typeof process !== "undefined" ? process.cwd() : "";
2026
+ if (typeof process !== "undefined" && typeof process.cwd === "function") {
1631
2027
  try {
1632
- const { loadConfig } = await import("./configLoader-IT4PWCJB.js");
1633
- const { config } = await loadConfig(process.cwd());
1634
- const componentStrictness = config.test?.components?.find((comp) => comp?.name === componentName)?.strictness;
1635
- strictness = normalizeStrictness(componentStrictness ?? config.test?.strictness);
2028
+ const { loadConfig } = await import("./configLoader-WTGJAP4Z.js");
2029
+ const result2 = await loadConfig(process.cwd());
2030
+ config = result2.config;
2031
+ if (result2.configPath) {
2032
+ configBaseDir = path.dirname(result2.configPath);
2033
+ }
2034
+ if (options.strictness === void 0) {
2035
+ const componentStrictness = config.test?.components?.find((comp) => comp?.name === componentName)?.strictness;
2036
+ strictness = normalizeStrictness(componentStrictness ?? config.test?.strictness);
2037
+ }
1636
2038
  } catch {
1637
- strictness = "balanced";
2039
+ if (options.strictness === void 0) {
2040
+ strictness = "balanced";
2041
+ }
1638
2042
  }
1639
2043
  }
1640
- let contract;
2044
+ let contract2;
1641
2045
  try {
1642
2046
  if (url) {
1643
2047
  const devServerUrl = await checkDevServer(url);
1644
2048
  if (devServerUrl) {
1645
2049
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
1646
- const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-UAOFNS7Z.js");
1647
- contract = await runContractTestsPlaywright(componentName, devServerUrl, strictness);
2050
+ const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-XBWJZMR3.js");
2051
+ contract2 = await runContractTestsPlaywright(componentName, devServerUrl, strictness, config, configBaseDir);
1648
2052
  } else {
1649
2053
  throw new Error(
1650
2054
  `\u274C Dev server not running at ${url}
@@ -1653,7 +2057,7 @@ Please start your dev server and try again.`
1653
2057
  }
1654
2058
  } else if (component) {
1655
2059
  console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
1656
- contract = await runContractTests(componentName, component, strictness);
2060
+ contract2 = await runContractTests(componentName, component, strictness);
1657
2061
  } else {
1658
2062
  throw new Error("\u274C Either component or URL must be provided");
1659
2063
  }
@@ -1666,13 +2070,13 @@ Please start your dev server and try again.`
1666
2070
  const result = {
1667
2071
  violations: results.violations,
1668
2072
  raw: results,
1669
- contract
2073
+ contract: contract2
1670
2074
  };
1671
- if (contract.failures.length > 0 && url === "Playwright") {
2075
+ if (contract2.failures.length > 0 && url === "Playwright") {
1672
2076
  throw new Error(
1673
2077
  `
1674
- \u274C ${contract.failures.length} accessibility contract test${contract.failures.length > 1 ? "s" : ""} failed (Playwright mode)
1675
- \u2705 ${contract.passes.length} test${contract.passes.length > 1 ? "s" : ""} passed
2078
+ \u274C ${contract2.failures.length} accessibility contract test${contract2.failures.length > 1 ? "s" : ""} failed (Playwright mode)
2079
+ \u2705 ${contract2.passes.length} test${contract2.passes.length > 1 ? "s" : ""} passed
1676
2080
 
1677
2081
  \u{1F4CB} Review the detailed test report above for specific failures.
1678
2082
  \u{1F4A1} Contract tests validate ARIA attributes and keyboard interactions per W3C APG guidelines.`
@@ -1747,6 +2151,7 @@ async function cleanupTests() {
1747
2151
  }
1748
2152
  export {
1749
2153
  cleanupTests,
2154
+ contract,
1750
2155
  makeAccordionAccessible,
1751
2156
  makeBlockAccessible,
1752
2157
  makeCheckboxAccessible,
@@ -0,0 +1,38 @@
1
+ import { expect } from '@playwright/test';
2
+
3
+ // src/utils/test/src/component-strategies/AccordionComponentStrategy.ts
4
+ var AccordionComponentStrategy = class {
5
+ constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
6
+ this.mainSelector = mainSelector;
7
+ this.selectors = selectors;
8
+ this.actionTimeoutMs = actionTimeoutMs;
9
+ this.assertionTimeoutMs = assertionTimeoutMs;
10
+ }
11
+ async resetState(page) {
12
+ if (!this.selectors.panel || !this.selectors.trigger || this.selectors.popup) {
13
+ return;
14
+ }
15
+ const triggerSelector = this.selectors.trigger;
16
+ const panelSelector = this.selectors.panel;
17
+ if (!triggerSelector || !panelSelector) return;
18
+ const allTriggers = await page.locator(triggerSelector).all();
19
+ for (const trigger of allTriggers) {
20
+ const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
21
+ const triggerPanel = await trigger.getAttribute("aria-controls");
22
+ if (isExpanded && triggerPanel) {
23
+ await trigger.click({ timeout: this.actionTimeoutMs });
24
+ const panel = page.locator(`#${triggerPanel}`);
25
+ await expect(panel).toBeHidden({ timeout: this.assertionTimeoutMs }).catch(() => {
26
+ });
27
+ }
28
+ }
29
+ }
30
+ async shouldSkipTest() {
31
+ return false;
32
+ }
33
+ getMainSelector() {
34
+ return this.mainSelector;
35
+ }
36
+ };
37
+
38
+ export { AccordionComponentStrategy };
@@ -0,0 +1,60 @@
1
+ import { expect } from '@playwright/test';
2
+
3
+ // src/utils/test/src/component-strategies/ComboboxComponentStrategy.ts
4
+ var ComboboxComponentStrategy = class {
5
+ constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
6
+ this.mainSelector = mainSelector;
7
+ this.selectors = selectors;
8
+ this.actionTimeoutMs = actionTimeoutMs;
9
+ this.assertionTimeoutMs = assertionTimeoutMs;
10
+ }
11
+ async resetState(page) {
12
+ if (!this.selectors.popup) return;
13
+ const popupSelector = this.selectors.popup;
14
+ const popupElement = page.locator(popupSelector).first();
15
+ const isPopupVisible = await popupElement.isVisible().catch(() => false);
16
+ if (!isPopupVisible) return;
17
+ let listBoxClosed = false;
18
+ let closeSelector = this.selectors.input;
19
+ if (!closeSelector && this.selectors.focusable) {
20
+ closeSelector = this.selectors.focusable;
21
+ } else if (!closeSelector) {
22
+ closeSelector = this.selectors.button;
23
+ }
24
+ if (closeSelector) {
25
+ const closeElement = page.locator(closeSelector).first();
26
+ await closeElement.focus();
27
+ await page.keyboard.press("Escape");
28
+ listBoxClosed = await expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
29
+ }
30
+ if (!listBoxClosed && this.selectors.button) {
31
+ const buttonElement = page.locator(this.selectors.button).first();
32
+ await buttonElement.click({ timeout: this.actionTimeoutMs });
33
+ listBoxClosed = await expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
34
+ }
35
+ if (!listBoxClosed) {
36
+ await page.mouse.click(10, 10);
37
+ listBoxClosed = await expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
38
+ }
39
+ if (!listBoxClosed) {
40
+ throw new Error(
41
+ `\u274C FATAL: Cannot close combobox popup between tests. Popup remains visible after trying:
42
+ 1. Escape key
43
+ 2. Clicking button
44
+ 3. Clicking outside
45
+ This indicates a problem with the combobox component's close functionality.`
46
+ );
47
+ }
48
+ if (this.selectors.input) {
49
+ await page.locator(this.selectors.input).first().clear();
50
+ }
51
+ }
52
+ async shouldSkipTest() {
53
+ return false;
54
+ }
55
+ getMainSelector() {
56
+ return this.mainSelector;
57
+ }
58
+ };
59
+
60
+ export { ComboboxComponentStrategy };
@@ -0,0 +1,77 @@
1
+ import { expect } from '@playwright/test';
2
+
3
+ // src/utils/test/src/component-strategies/MenuComponentStrategy.ts
4
+ var MenuComponentStrategy = class {
5
+ constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
6
+ this.mainSelector = mainSelector;
7
+ this.selectors = selectors;
8
+ this.actionTimeoutMs = actionTimeoutMs;
9
+ this.assertionTimeoutMs = assertionTimeoutMs;
10
+ }
11
+ async resetState(page) {
12
+ if (!this.selectors.popup) return;
13
+ const popupSelector = this.selectors.popup;
14
+ const popupElement = page.locator(popupSelector).first();
15
+ const isPopupVisible = await popupElement.isVisible().catch(() => false);
16
+ if (!isPopupVisible) return;
17
+ let menuClosed = false;
18
+ let closeSelector = this.selectors.input;
19
+ if (!closeSelector && this.selectors.focusable) {
20
+ closeSelector = this.selectors.focusable;
21
+ } else if (!closeSelector) {
22
+ closeSelector = this.selectors.trigger;
23
+ }
24
+ if (closeSelector) {
25
+ const closeElement = page.locator(closeSelector).first();
26
+ await closeElement.focus();
27
+ await page.keyboard.press("Escape");
28
+ menuClosed = await expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
29
+ }
30
+ if (!menuClosed && this.selectors.trigger) {
31
+ const triggerElement = page.locator(this.selectors.trigger).first();
32
+ await triggerElement.click({ timeout: this.actionTimeoutMs });
33
+ menuClosed = await expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
34
+ }
35
+ if (!menuClosed) {
36
+ await page.mouse.click(10, 10);
37
+ menuClosed = await expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
38
+ }
39
+ if (!menuClosed) {
40
+ throw new Error(
41
+ `\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
42
+ 1. Escape key
43
+ 2. Clicking trigger
44
+ 3. Clicking outside
45
+ This indicates a problem with the menu component's close functionality.`
46
+ );
47
+ }
48
+ if (this.selectors.input) {
49
+ await page.locator(this.selectors.input).first().clear();
50
+ }
51
+ if (this.selectors.trigger) {
52
+ const triggerElement = page.locator(this.selectors.trigger).first();
53
+ await triggerElement.focus();
54
+ }
55
+ }
56
+ async shouldSkipTest(test, page) {
57
+ const requiresSubmenu = test.action.some(
58
+ (act) => act.target === "submenu" || act.target === "submenuTrigger" || act.target === "submenuItems"
59
+ ) || test.assertions.some(
60
+ (assertion) => assertion.target === "submenu" || assertion.target === "submenuTrigger" || assertion.target === "submenuItems"
61
+ );
62
+ if (!requiresSubmenu) {
63
+ return false;
64
+ }
65
+ const submenuTriggerSelector = this.selectors.submenuTrigger;
66
+ if (!submenuTriggerSelector) {
67
+ return true;
68
+ }
69
+ const submenuTriggerCount = await page.locator(submenuTriggerSelector).count();
70
+ return submenuTriggerCount === 0;
71
+ }
72
+ getMainSelector() {
73
+ return this.mainSelector;
74
+ }
75
+ };
76
+
77
+ export { MenuComponentStrategy };