aria-ease 6.11.0 → 6.12.1

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 (37) hide show
  1. package/README.md +3 -3
  2. package/dist/{ComboboxComponentStrategy-OGRVZXAF.js → ComboboxComponentStrategy-DU342VMB.js} +7 -7
  3. package/dist/RelativeTargetResolver-DJAITO6D.js +7 -0
  4. package/dist/{audit-RM6TCZ5C.js → audit-JYEPKLHR.js} +5 -0
  5. package/dist/{chunk-XERMSYEH.js → chunk-4DU5Z5BR.js} +0 -23
  6. package/dist/{chunk-NI3MQCAS.js → chunk-GJGUY643.js} +2 -2
  7. package/dist/chunk-GLT43UVH.js +43 -0
  8. package/dist/cli.cjs +147 -84
  9. package/dist/cli.js +5 -5
  10. package/dist/{configLoader-DWHOHXHL.js → configLoader-Q7N5XV4P.js} +2 -2
  11. package/dist/{configLoader-UJZHQBYS.js → configLoader-REHK3S3Q.js} +1 -1
  12. package/dist/{contractTestRunnerPlaywright-QDXSK3FE.js → contractTestRunnerPlaywright-H24LQ45R.js} +113 -72
  13. package/dist/{contractTestRunnerPlaywright-WNWQYSXZ.js → contractTestRunnerPlaywright-NL3JNJYH.js} +113 -72
  14. package/dist/index.cjs +248 -112
  15. package/dist/index.d.cts +3 -0
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.js +124 -41
  18. package/dist/src/combobox/index.cjs +1 -0
  19. package/dist/src/combobox/index.js +1 -0
  20. package/dist/src/utils/test/{ComboboxComponentStrategy-5AECQSRN.js → ComboboxComponentStrategy-XKQ72RFD.js} +7 -7
  21. package/dist/src/utils/test/RelativeTargetResolver-G2XDN2VV.js +1 -0
  22. package/dist/src/utils/test/{chunk-XERMSYEH.js → chunk-4DU5Z5BR.js} +1 -23
  23. package/dist/src/utils/test/chunk-GLT43UVH.js +41 -0
  24. package/dist/src/utils/test/{configLoader-SHJSRG2A.js → configLoader-NA7IBCS3.js} +2 -2
  25. package/dist/src/utils/test/{contractTestRunnerPlaywright-Z2AHXSNM.js → contractTestRunnerPlaywright-5FT6K2WN.js} +111 -71
  26. package/dist/src/utils/test/dsl/index.cjs +106 -29
  27. package/dist/src/utils/test/dsl/index.d.cts +3 -0
  28. package/dist/src/utils/test/dsl/index.d.ts +3 -0
  29. package/dist/src/utils/test/dsl/index.js +106 -29
  30. package/dist/src/utils/test/index.cjs +135 -76
  31. package/dist/src/utils/test/index.js +17 -11
  32. package/dist/{test-O3J4ZPQR.js → test-FYSJXQWO.js} +17 -12
  33. package/package.json +3 -4
  34. package/dist/src/utils/test/aria-contracts/accordion/accordion.contract.json +0 -290
  35. package/dist/src/utils/test/aria-contracts/combobox/combobox.listbox.contract.json +0 -463
  36. package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +0 -562
  37. package/dist/src/utils/test/aria-contracts/tabs/tabs.contract.json +0 -361
package/dist/index.cjs CHANGED
@@ -31,31 +31,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  ));
32
32
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
33
 
34
- // src/utils/test/contract/contract.json
35
- var contract_default;
36
- var init_contract = __esm({
37
- "src/utils/test/contract/contract.json"() {
38
- contract_default = {
39
- menu: {
40
- path: "./aria-contracts/menu/menu.contract.json",
41
- component: "menu"
42
- },
43
- "combobox.listbox": {
44
- path: "./aria-contracts/combobox/combobox.listbox.contract.json",
45
- component: "combobox.listbox"
46
- },
47
- accordion: {
48
- path: "./aria-contracts/accordion/accordion.contract.json",
49
- component: "accordion"
50
- },
51
- tabs: {
52
- path: "./aria-contracts/tabs/tabs.contract.json",
53
- component: "tabs"
54
- }
55
- };
56
- }
57
- });
58
-
59
34
  // src/utils/test/src/ContractReporter.ts
60
35
  var ContractReporter;
61
36
  var init_ContractReporter = __esm({
@@ -376,9 +351,7 @@ async function getOrCreateContext() {
376
351
  if (!sharedContext) {
377
352
  const browser = await getOrCreateBrowser();
378
353
  sharedContext = await browser.newContext({
379
- // Isolated context - no permissions, no geolocation, etc.
380
354
  permissions: [],
381
- // Ignore HTTPS errors for local dev servers
382
355
  ignoreHTTPSErrors: true
383
356
  });
384
357
  }
@@ -480,8 +453,8 @@ function validateConfig(config) {
480
453
  if (typeof comp.name !== "string") {
481
454
  errors.push(`test.components[${idx}].name must be a string`);
482
455
  }
483
- if (comp.path !== void 0 && typeof comp.path !== "string") {
484
- errors.push(`test.components[${idx}].path must be a string when provided`);
456
+ if (comp.contractPath !== void 0 && typeof comp.contractPath !== "string") {
457
+ errors.push(`test.components[${idx}].contractPath must be a string when provided`);
485
458
  }
486
459
  if (comp.strategyPath !== void 0 && typeof comp.strategyPath !== "string") {
487
460
  errors.push(`test.components[${idx}].strategyPath must be a string when provided`);
@@ -765,7 +738,7 @@ var init_ComboboxComponentStrategy = __esm({
765
738
  const popupElement = page.locator(popupSelector).first();
766
739
  const isPopupVisible = await popupElement.isVisible().catch(() => false);
767
740
  if (!isPopupVisible) return;
768
- let listBoxClosed = false;
741
+ let popupClosed = false;
769
742
  let closeSelector = this.selectors.input;
770
743
  if (!closeSelector && this.selectors.focusable) {
771
744
  closeSelector = this.selectors.focusable;
@@ -776,18 +749,18 @@ var init_ComboboxComponentStrategy = __esm({
776
749
  const closeElement = page.locator(closeSelector).first();
777
750
  await closeElement.focus();
778
751
  await page.keyboard.press("Escape");
779
- listBoxClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
752
+ popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
780
753
  }
781
- if (!listBoxClosed && this.selectors.button) {
754
+ if (!popupClosed && this.selectors.button) {
782
755
  const buttonElement = page.locator(this.selectors.button).first();
783
756
  await buttonElement.click({ timeout: this.actionTimeoutMs });
784
- listBoxClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
757
+ popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
785
758
  }
786
- if (!listBoxClosed) {
759
+ if (!popupClosed) {
787
760
  await page.mouse.click(10, 10);
788
- listBoxClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
761
+ popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
789
762
  }
790
- if (!listBoxClosed) {
763
+ if (!popupClosed) {
791
764
  throw new Error(
792
765
  `\u274C FATAL: Cannot close combobox popup between tests. Popup remains visible after trying:
793
766
  1. Escape key
@@ -885,12 +858,6 @@ var init_StrategyRegistry = __esm({
885
858
  (m) => m.TabsComponentStrategy
886
859
  )
887
860
  );
888
- this.builtInStrategies.set(
889
- "combobox.listbox",
890
- () => Promise.resolve().then(() => (init_ComboboxComponentStrategy(), ComboboxComponentStrategy_exports)).then(
891
- (m) => m.ComboboxComponentStrategy
892
- )
893
- );
894
861
  }
895
862
  /**
896
863
  * Load a strategy - either from custom path or built-in registry
@@ -937,15 +904,14 @@ var init_StrategyRegistry = __esm({
937
904
  });
938
905
 
939
906
  // src/utils/test/src/ComponentDetector.ts
940
- var import_fs, import_path3, import_meta2, ComponentDetector;
907
+ var import_fs, import_path3, import_meta, ComponentDetector;
941
908
  var init_ComponentDetector = __esm({
942
909
  "src/utils/test/src/ComponentDetector.ts"() {
943
910
  "use strict";
944
911
  import_fs = require("fs");
945
912
  import_path3 = __toESM(require("path"), 1);
946
- init_contract();
947
913
  init_StrategyRegistry();
948
- import_meta2 = {};
914
+ import_meta = {};
949
915
  ComponentDetector = class {
950
916
  static strategyRegistry = new StrategyRegistry();
951
917
  static isComponentConfig(value) {
@@ -965,11 +931,7 @@ var init_ComponentDetector = __esm({
965
931
  */
966
932
  static async detect(componentName, componentConfig, actionTimeoutMs = 400, assertionTimeoutMs = 400, configBaseDir) {
967
933
  const typedComponentConfig = this.isComponentConfig(componentConfig) ? componentConfig : void 0;
968
- let contractPath = typedComponentConfig?.path;
969
- if (!contractPath) {
970
- const contractTyped = contract_default;
971
- contractPath = contractTyped[componentName]?.path;
972
- }
934
+ const contractPath = typedComponentConfig?.contractPath;
973
935
  if (!contractPath) {
974
936
  throw new Error(`Contract path not found for component: ${componentName}`);
975
937
  }
@@ -988,7 +950,7 @@ var init_ComponentDetector = __esm({
988
950
  (0, import_fs.readFileSync)(cwdResolved, "utf-8");
989
951
  return cwdResolved;
990
952
  } catch {
991
- return new URL(contractPath, import_meta2.url).pathname;
953
+ return new URL(contractPath, import_meta.url).pathname;
992
954
  }
993
955
  })();
994
956
  const contractData = (0, import_fs.readFileSync)(resolvedPath, "utf-8");
@@ -1002,7 +964,7 @@ var init_ComponentDetector = __esm({
1002
964
  if (!strategyClass) {
1003
965
  return null;
1004
966
  }
1005
- const mainSelector = selectors.trigger || selectors.input || selectors.tablist || selectors.container;
967
+ const mainSelector = selectors.main;
1006
968
  if (componentName === "tabs") {
1007
969
  return new strategyClass(mainSelector, selectors);
1008
970
  }
@@ -1018,6 +980,10 @@ var init_ComponentDetector = __esm({
1018
980
  });
1019
981
 
1020
982
  // src/utils/test/src/RelativeTargetResolver.ts
983
+ var RelativeTargetResolver_exports = {};
984
+ __export(RelativeTargetResolver_exports, {
985
+ RelativeTargetResolver: () => RelativeTargetResolver
986
+ });
1021
987
  var RelativeTargetResolver;
1022
988
  var init_RelativeTargetResolver = __esm({
1023
989
  "src/utils/test/src/RelativeTargetResolver.ts"() {
@@ -1093,16 +1059,16 @@ var init_ActionExecutor = __esm({
1093
1059
  async focus(target, relativeTarget, virtualId) {
1094
1060
  try {
1095
1061
  if (target === "virtual" && virtualId) {
1096
- const inputSelector = this.selectors.input;
1097
- if (!inputSelector) {
1098
- return { success: false, error: `Input selector not defined for virtual focus.` };
1062
+ const mainSelector = this.selectors.main;
1063
+ if (!mainSelector) {
1064
+ return { success: false, error: `Main selector not defined for virtual focus.` };
1099
1065
  }
1100
- const input = this.page.locator(inputSelector).first();
1101
- const exists = await input.count();
1066
+ const main = this.page.locator(mainSelector).first();
1067
+ const exists = await main.count();
1102
1068
  if (!exists) {
1103
- return { success: false, error: `Input element not found for virtual focus.` };
1069
+ return { success: false, error: `Main element not found for virtual focus.` };
1104
1070
  }
1105
- await input.evaluate((el, id) => {
1071
+ await main.evaluate((el, id) => {
1106
1072
  el.setAttribute("aria-activedescendant", id);
1107
1073
  }, virtualId);
1108
1074
  return { success: true };
@@ -1404,6 +1370,10 @@ var init_AssertionRunner = __esm({
1404
1370
  };
1405
1371
  }
1406
1372
  }
1373
+ if (typeof expectedValue !== "string") {
1374
+ console.error("[AssertionRunner] expectedValue is not a string:", expectedValue);
1375
+ throw new Error(`AssertionRunner: expectedValue for attribute assertion must be a string, but got: ${JSON.stringify(expectedValue)}`);
1376
+ }
1407
1377
  const expectedValues = expectedValue.split(" | ").map((v) => v.trim());
1408
1378
  const attributeValue = await target.getAttribute(attribute);
1409
1379
  if (attributeValue !== null && expectedValues.includes(attributeValue)) {
@@ -1577,7 +1547,7 @@ __export(contractTestRunnerPlaywright_exports, {
1577
1547
  });
1578
1548
  async function runContractTestsPlaywright(componentName, url, strictness, config, configBaseDir) {
1579
1549
  const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
1580
- const isCustomContract = !!componentConfig?.path;
1550
+ const isCustomContract = !!componentConfig?.contractPath;
1581
1551
  const reporter = new ContractReporter(true, isCustomContract);
1582
1552
  const defaultTimeouts = {
1583
1553
  actionTimeoutMs: 400,
@@ -1617,11 +1587,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
1617
1587
  defaultTimeouts.componentReadyTimeoutMs
1618
1588
  );
1619
1589
  const strictnessMode = normalizeStrictness(strictness);
1620
- let contractPath = componentConfig?.path;
1621
- if (!contractPath) {
1622
- const contractTyped = contract_default;
1623
- contractPath = contractTyped[componentName]?.path;
1624
- }
1590
+ const contractPath = componentConfig?.contractPath;
1625
1591
  if (!contractPath) {
1626
1592
  throw new Error(`Contract path not found for component: ${componentName}`);
1627
1593
  }
@@ -1640,7 +1606,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
1640
1606
  (0, import_fs2.readFileSync)(cwdResolved, "utf-8");
1641
1607
  return cwdResolved;
1642
1608
  } catch {
1643
- return new URL(contractPath, import_meta3.url).pathname;
1609
+ return new URL(contractPath, import_meta2.url).pathname;
1644
1610
  }
1645
1611
  })();
1646
1612
  const contractData = (0, import_fs2.readFileSync)(resolvedPath, "utf-8");
@@ -1840,6 +1806,52 @@ This usually means:
1840
1806
  reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
1841
1807
  }
1842
1808
  }
1809
+ async function resolveExpectedValue(expectedValue, selectors, page2, context = {}) {
1810
+ if (!expectedValue || typeof expectedValue !== "object" || !("ref" in expectedValue)) return expectedValue;
1811
+ let refSelector;
1812
+ if (expectedValue.ref === "relative") {
1813
+ if (!expectedValue.relativeTarget || !context.relativeBaseSelector) return void 0;
1814
+ const baseLocator = page2.locator(context.relativeBaseSelector);
1815
+ const count = await baseLocator.count();
1816
+ let idx = 0;
1817
+ if (expectedValue.relativeTarget === "first") idx = 0;
1818
+ else if (expectedValue.relativeTarget === "second") idx = 1;
1819
+ else if (expectedValue.relativeTarget === "last") idx = count - 1;
1820
+ else if (!isNaN(Number(expectedValue.relativeTarget))) idx = Number(expectedValue.relativeTarget);
1821
+ else idx = 0;
1822
+ if (idx < 0 || idx >= count) return void 0;
1823
+ const relElem = baseLocator.nth(idx);
1824
+ return await getPropertyFromLocator(relElem, expectedValue.property || expectedValue.attribute);
1825
+ } else {
1826
+ refSelector = selectors[expectedValue.ref];
1827
+ if (!refSelector) throw new Error(`Selector for ref '${expectedValue.ref}' not found in contract selectors.`);
1828
+ const refLocator = page2.locator(refSelector).first();
1829
+ return await getPropertyFromLocator(refLocator, expectedValue.property || expectedValue.attribute);
1830
+ }
1831
+ }
1832
+ async function getPropertyFromLocator(locator, property) {
1833
+ if (!locator) return void 0;
1834
+ if (!property || property === "id") {
1835
+ return await locator.getAttribute("id") ?? void 0;
1836
+ } else if (property === "class") {
1837
+ return await locator.getAttribute("class") ?? void 0;
1838
+ } else if (property === "textContent") {
1839
+ return await locator.evaluate((el) => el.textContent ?? void 0);
1840
+ } else if (property.startsWith("aria-")) {
1841
+ return await locator.getAttribute(property) ?? void 0;
1842
+ } else if (property.endsWith("*")) {
1843
+ const attrs = await locator.evaluate((el) => {
1844
+ const out = [];
1845
+ for (const attr of Array.from(el.attributes)) {
1846
+ if (attr.name.startsWith("aria-")) out.push(`${attr.name}=${attr.value}`);
1847
+ }
1848
+ return out.join(";");
1849
+ });
1850
+ return attrs;
1851
+ } else {
1852
+ return await locator.getAttribute(property) ?? void 0;
1853
+ }
1854
+ }
1843
1855
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
1844
1856
  for (const test of componentContract.static[0]?.assertions || []) {
1845
1857
  if (test.target === "relative") continue;
@@ -1882,6 +1894,22 @@ This usually means:
1882
1894
  }
1883
1895
  return false;
1884
1896
  };
1897
+ let expectedValue = test.expectedValue;
1898
+ if (test.expectedValue && typeof test.expectedValue === "object" && "ref" in test.expectedValue) {
1899
+ const context = {};
1900
+ const relTarget = test.relativeTarget;
1901
+ if (test.expectedValue.ref === "relative" && test.target === "relative" && relTarget) {
1902
+ const baseSel = componentContract.selectors[relTarget];
1903
+ if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
1904
+ context.relativeBaseSelector = baseSel;
1905
+ } else if (test.expectedValue.ref === "relative" && relTarget) {
1906
+ const baseSel = componentContract.selectors[relTarget];
1907
+ if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
1908
+ context.relativeBaseSelector = baseSel;
1909
+ }
1910
+ expectedValue = await resolveExpectedValue(test.expectedValue, componentContract.selectors, page, context);
1911
+ console.log("Expected value in static check", expectedValue);
1912
+ }
1885
1913
  if (!test.expectedValue) {
1886
1914
  const attributes = test.attribute.split(" | ");
1887
1915
  let hasAny = false;
@@ -1915,16 +1943,17 @@ This usually means:
1915
1943
  reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
1916
1944
  }
1917
1945
  } else {
1918
- if (isRedundantCheck(targetSelector, test.attribute, test.expectedValue)) {
1919
- passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
1946
+ if (isRedundantCheck(targetSelector, test.attribute, typeof expectedValue === "string" ? expectedValue : void 0)) {
1947
+ passes.push(`${test.attribute}="${expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
1920
1948
  staticPassed += 1;
1921
1949
  reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
1922
1950
  } else {
1951
+ const valueToCheck = expectedValue ?? "";
1923
1952
  const result = await staticAssertionRunner.validateAttribute(
1924
1953
  target,
1925
1954
  test.target,
1926
1955
  test.attribute,
1927
- test.expectedValue,
1956
+ valueToCheck,
1928
1957
  test.failureMessage,
1929
1958
  "Static ARIA Test"
1930
1959
  );
@@ -2042,7 +2071,33 @@ This usually means:
2042
2071
  continue;
2043
2072
  }
2044
2073
  for (const assertion of assertions) {
2045
- const result = await assertionRunner.validate(assertion, dynamicTest.description);
2074
+ let expectedValue;
2075
+ if (assertion.expectedValue && typeof assertion.expectedValue === "object" && "ref" in assertion.expectedValue) {
2076
+ if (assertion.expectedValue.ref === "relative") {
2077
+ const { RelativeTargetResolver: RelativeTargetResolver2 } = await Promise.resolve().then(() => (init_RelativeTargetResolver(), RelativeTargetResolver_exports));
2078
+ const relativeSelector = componentContract.selectors.relative;
2079
+ if (!relativeSelector) throw new Error("Relative selector not defined in contract selectors.");
2080
+ const relTarget = assertion.relativeTarget || "first";
2081
+ const relElem = await RelativeTargetResolver2.resolve(page, relativeSelector, relTarget);
2082
+ if (!relElem) throw new Error(`Could not resolve relative target '${relTarget}' for expectedValue.`);
2083
+ const prop = assertion.expectedValue.property || assertion.expectedValue.attribute || "id";
2084
+ if (prop === "textContent") {
2085
+ expectedValue = await relElem.evaluate((el) => el.textContent ?? void 0);
2086
+ } else {
2087
+ const attr = await relElem.getAttribute(prop);
2088
+ expectedValue = attr === null ? void 0 : attr;
2089
+ }
2090
+ } else {
2091
+ expectedValue = await resolveExpectedValue(assertion.expectedValue, componentContract.selectors, page, {});
2092
+ }
2093
+ } else if (typeof assertion.expectedValue === "string" || typeof assertion.expectedValue === "undefined") {
2094
+ expectedValue = assertion.expectedValue;
2095
+ } else {
2096
+ expectedValue = "";
2097
+ }
2098
+ const assertionToRun = { ...assertion, expectedValue };
2099
+ const valueToCheck = expectedValue ?? "";
2100
+ const result = await assertionRunner.validate({ ...assertionToRun, expectedValue: valueToCheck }, dynamicTest.description);
2046
2101
  if (result.success && result.passMessage) {
2047
2102
  passes.push(result.passMessage);
2048
2103
  } else if (!result.success && result.failMessage) {
@@ -2112,20 +2167,19 @@ This usually means:
2112
2167
  }
2113
2168
  return { passes, failures, skipped, warnings };
2114
2169
  }
2115
- var import_fs2, import_path4, import_meta3;
2170
+ var import_fs2, import_path4, import_meta2;
2116
2171
  var init_contractTestRunnerPlaywright = __esm({
2117
2172
  "src/utils/test/src/contractTestRunnerPlaywright.ts"() {
2118
2173
  "use strict";
2119
2174
  import_fs2 = require("fs");
2120
2175
  import_path4 = __toESM(require("path"), 1);
2121
- init_contract();
2122
2176
  init_playwrightTestHarness();
2123
2177
  init_ComponentDetector();
2124
2178
  init_ContractReporter();
2125
2179
  init_ActionExecutor();
2126
2180
  init_AssertionRunner();
2127
2181
  init_strictness();
2128
- import_meta3 = {};
2182
+ import_meta2 = {};
2129
2183
  }
2130
2184
  });
2131
2185
 
@@ -3370,6 +3424,7 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
3370
3424
  } else if (comboboxInput.value) {
3371
3425
  event.preventDefault();
3372
3426
  comboboxInput.value = "";
3427
+ comboboxInput.setAttribute("aria-activedescendant", "");
3373
3428
  const visibleItems2 = getVisibleItems();
3374
3429
  visibleItems2.forEach((item) => {
3375
3430
  if (item.getAttribute("aria-selected") === "true") item.setAttribute("aria-selected", "false");
@@ -3778,7 +3833,7 @@ function resolveSetup(setup, ctx) {
3778
3833
  );
3779
3834
  }
3780
3835
  var COMBOBOX_STATES = {
3781
- "listbox.open": {
3836
+ "popup.open": {
3782
3837
  setup: [
3783
3838
  {
3784
3839
  when: ["keyboard", "textInput"],
@@ -3795,7 +3850,7 @@ var COMBOBOX_STATES = {
3795
3850
  ],
3796
3851
  assertion: isComboboxOpen
3797
3852
  },
3798
- "listbox.closed": {
3853
+ "popup.closed": {
3799
3854
  setup: [
3800
3855
  {
3801
3856
  when: ["keyboard"],
@@ -3810,18 +3865,18 @@ var COMBOBOX_STATES = {
3810
3865
  ]
3811
3866
  }
3812
3867
  ],
3813
- assertion: isComboboxClosed
3868
+ assertion: [...isComboboxClosed(), ...isActiveDescendantEmpty()]
3814
3869
  },
3815
- "input.focused": {
3870
+ "main.focused": {
3816
3871
  setup: [
3817
3872
  {
3818
3873
  when: ["keyboard"],
3819
3874
  steps: () => [
3820
- { type: "focus", target: "input" }
3875
+ { type: "focus", target: "main" }
3821
3876
  ]
3822
3877
  }
3823
3878
  ],
3824
- assertion: isInputFocused
3879
+ assertion: isMainFocused
3825
3880
  },
3826
3881
  "input.filled": {
3827
3882
  setup: [
@@ -3834,8 +3889,19 @@ var COMBOBOX_STATES = {
3834
3889
  ],
3835
3890
  assertion: isInputFilled
3836
3891
  },
3892
+ "input.notFilled": {
3893
+ setup: [
3894
+ {
3895
+ when: ["keyboard", "textInput"],
3896
+ steps: () => [
3897
+ { type: "type", target: "input", value: "" }
3898
+ ]
3899
+ }
3900
+ ],
3901
+ assertion: isInputNotFilled
3902
+ },
3837
3903
  "activeOption.first": {
3838
- requires: ["listbox.open"],
3904
+ requires: ["popup.open"],
3839
3905
  setup: [
3840
3906
  {
3841
3907
  when: ["keyboard"],
@@ -3844,7 +3910,7 @@ var COMBOBOX_STATES = {
3844
3910
  ]
3845
3911
  }
3846
3912
  ],
3847
- assertion: isActiveDescendantNotEmpty
3913
+ assertion: isActiveDescendantFirst
3848
3914
  },
3849
3915
  "activeOption.last": {
3850
3916
  requires: ["activeOption.first"],
@@ -3856,10 +3922,30 @@ var COMBOBOX_STATES = {
3856
3922
  ]
3857
3923
  }
3858
3924
  ],
3925
+ assertion: isActiveDescendantLast
3926
+ },
3927
+ "activeDescendant.notEmpty": {
3928
+ requires: [],
3929
+ setup: [
3930
+ {
3931
+ when: ["keyboard"],
3932
+ steps: () => []
3933
+ }
3934
+ ],
3859
3935
  assertion: isActiveDescendantNotEmpty
3860
3936
  },
3937
+ "activeDescendant.Empty": {
3938
+ requires: [],
3939
+ setup: [
3940
+ {
3941
+ when: ["keyboard"],
3942
+ steps: () => []
3943
+ }
3944
+ ],
3945
+ assertion: isActiveDescendantEmpty
3946
+ },
3861
3947
  "selectedOption.first": {
3862
- requires: ["listbox.open"],
3948
+ requires: ["popup.open"],
3863
3949
  setup: [
3864
3950
  {
3865
3951
  when: ["pointer"],
@@ -3871,7 +3957,7 @@ var COMBOBOX_STATES = {
3871
3957
  assertion: () => isAriaSelected("first")
3872
3958
  },
3873
3959
  "selectedOption.last": {
3874
- requires: ["listbox.open"],
3960
+ requires: ["popup.open"],
3875
3961
  setup: [
3876
3962
  {
3877
3963
  when: ["pointer"],
@@ -3886,43 +3972,76 @@ var COMBOBOX_STATES = {
3886
3972
  function isComboboxOpen() {
3887
3973
  return [
3888
3974
  {
3889
- target: "listbox",
3975
+ target: "popup",
3890
3976
  assertion: "toBeVisible",
3891
- failureMessage: "Expected listbox to be visible"
3977
+ failureMessage: "Expected popup to be visible"
3892
3978
  },
3893
3979
  {
3894
- target: "input",
3980
+ target: "main",
3895
3981
  assertion: "toHaveAttribute",
3896
3982
  attribute: "aria-expanded",
3897
3983
  expectedValue: "true",
3898
- failureMessage: "Expect combobox input to have aria-expanded='true'"
3984
+ failureMessage: "Expect combobox main to have aria-expanded='true'."
3899
3985
  }
3900
3986
  ];
3901
3987
  }
3902
3988
  function isComboboxClosed() {
3903
3989
  return [
3904
3990
  {
3905
- target: "listbox",
3991
+ target: "popup",
3906
3992
  assertion: "notToBeVisible",
3907
- failureMessage: "Expected listbox to be closed"
3993
+ failureMessage: "Expected popup to be closed"
3908
3994
  },
3909
3995
  {
3910
- target: "input",
3996
+ target: "main",
3911
3997
  assertion: "toHaveAttribute",
3912
3998
  attribute: "aria-expanded",
3913
3999
  expectedValue: "false",
3914
- failureMessage: "Expect combobox input to have aria-expanded='false'"
4000
+ failureMessage: "Expect combobox main to have aria-expanded='false'."
4001
+ }
4002
+ ];
4003
+ }
4004
+ function isActiveDescendantFirst() {
4005
+ return [
4006
+ {
4007
+ target: "main",
4008
+ assertion: "toHaveAttribute",
4009
+ attribute: "aria-activedescendant",
4010
+ expectedValue: { ref: "relative", relativeTarget: "first", property: "id" },
4011
+ failureMessage: "Expected aria-activedescendant on main to match the id of the first option."
4012
+ }
4013
+ ];
4014
+ }
4015
+ function isActiveDescendantLast() {
4016
+ return [
4017
+ {
4018
+ target: "main",
4019
+ assertion: "toHaveAttribute",
4020
+ attribute: "aria-activedescendant",
4021
+ expectedValue: { ref: "relative", relativeTarget: "last", property: "id" },
4022
+ failureMessage: "Expected aria-activedescendant on main to match the id of the last option."
3915
4023
  }
3916
4024
  ];
3917
4025
  }
3918
4026
  function isActiveDescendantNotEmpty() {
3919
4027
  return [
3920
4028
  {
3921
- target: "input",
4029
+ target: "main",
3922
4030
  assertion: "toHaveAttribute",
3923
4031
  attribute: "aria-activedescendant",
3924
4032
  expectedValue: "!empty",
3925
- failureMessage: "Expected aria-activedescendant to not be empty"
4033
+ failureMessage: "Expected aria-activedescendant on main to not be empty."
4034
+ }
4035
+ ];
4036
+ }
4037
+ function isActiveDescendantEmpty() {
4038
+ return [
4039
+ {
4040
+ target: "main",
4041
+ assertion: "toHaveAttribute",
4042
+ attribute: "aria-activedescendant",
4043
+ expectedValue: "",
4044
+ failureMessage: "Expected aria-activedescendant on main to be empty."
3926
4045
  }
3927
4046
  ];
3928
4047
  }
@@ -3934,16 +4053,16 @@ function isAriaSelected(index) {
3934
4053
  assertion: "toHaveAttribute",
3935
4054
  attribute: "aria-selected",
3936
4055
  expectedValue: "true",
3937
- failureMessage: `Expected ${index} option to have aria-selected='true'`
4056
+ failureMessage: `Expected ${index} option to have aria-selected='true'.`
3938
4057
  }
3939
4058
  ];
3940
4059
  }
3941
- function isInputFocused() {
4060
+ function isMainFocused() {
3942
4061
  return [
3943
4062
  {
3944
- target: "input",
4063
+ target: "main",
3945
4064
  assertion: "toHaveFocus",
3946
- failureMessage: "Expected input to be focused"
4065
+ failureMessage: "Expected main to be focused."
3947
4066
  }
3948
4067
  ];
3949
4068
  }
@@ -3953,14 +4072,24 @@ function isInputFilled() {
3953
4072
  target: "input",
3954
4073
  assertion: "toHaveValue",
3955
4074
  expectedValue: "test",
3956
- failureMessage: "Expected input to have the value 'test'"
4075
+ failureMessage: "Expected input to have the value 'test'."
4076
+ }
4077
+ ];
4078
+ }
4079
+ function isInputNotFilled() {
4080
+ return [
4081
+ {
4082
+ target: "input",
4083
+ assertion: "toHaveValue",
4084
+ expectedValue: "",
4085
+ failureMessage: "Expected input to have the value ''."
3957
4086
  }
3958
4087
  ];
3959
4088
  }
3960
4089
 
3961
4090
  // src/utils/test/dsl/src/contractBuilder.ts
3962
4091
  var STATE_PACKS = {
3963
- "combobox.listbox": COMBOBOX_STATES
4092
+ "combobox": COMBOBOX_STATES
3964
4093
  // Add more mappings as needed
3965
4094
  };
3966
4095
  var FluentContract = class {
@@ -3994,11 +4123,13 @@ var ContractBuilder = class {
3994
4123
  const api = {
3995
4124
  ariaReference: (from, attribute, to) => ({
3996
4125
  required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required" }),
3997
- optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" })
4126
+ optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" }),
4127
+ recommended: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "recommended" })
3998
4128
  }),
3999
4129
  contains: (parent, child) => ({
4000
4130
  required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required" }),
4001
- optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" })
4131
+ optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" }),
4132
+ recommended: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "recommended" })
4002
4133
  })
4003
4134
  };
4004
4135
  fn(api);
@@ -4009,7 +4140,8 @@ var ContractBuilder = class {
4009
4140
  target: (target) => ({
4010
4141
  has: (attribute, expectedValue) => ({
4011
4142
  required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required" }),
4012
- optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" })
4143
+ optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" }),
4144
+ recommended: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "recommended" })
4013
4145
  })
4014
4146
  })
4015
4147
  };
@@ -4150,21 +4282,16 @@ function createContract(componentName, define) {
4150
4282
  var import_jest_axe = require("jest-axe");
4151
4283
 
4152
4284
  // src/utils/test/src/contractTestRunner.ts
4153
- init_contract();
4154
4285
  var import_promises = __toESM(require("fs/promises"), 1);
4155
4286
  init_ContractReporter();
4156
4287
  init_strictness();
4157
- var import_meta = {};
4158
- async function runContractTests(componentName, component, strictness) {
4288
+ async function runContractTests(contractPath, componentName, component, strictness) {
4159
4289
  const reporter = new ContractReporter(false);
4160
4290
  const strictnessMode = normalizeStrictness(strictness);
4161
- const contractTyped = contract_default;
4162
- const contractPath = contractTyped[componentName]?.path;
4163
4291
  if (!contractPath) {
4164
- throw new Error(`No contract found for component: ${componentName}`);
4292
+ throw new Error(`No contract path provided for component: ${componentName}`);
4165
4293
  }
4166
- const resolvedPath = new URL(contractPath, import_meta.url).pathname;
4167
- const contractData = await import_promises.default.readFile(resolvedPath, "utf-8");
4294
+ const contractData = await import_promises.default.readFile(contractPath, "utf-8");
4168
4295
  const componentContract = JSON.parse(contractData);
4169
4296
  const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
4170
4297
  reporter.start(componentName, totalTests);
@@ -4299,7 +4426,7 @@ async function runContractTests(componentName, component, strictness) {
4299
4426
  staticPassed += 1;
4300
4427
  reporter.reportStaticTest(`${test.target} has ${test.attribute}`, "pass", void 0, staticLevel);
4301
4428
  }
4302
- } else if (!attributeValue || !test.expectedValue.split(" | ").includes(attributeValue)) {
4429
+ } else if (!attributeValue || typeof test.expectedValue === "string" && !test.expectedValue.split(" | ").includes(attributeValue)) {
4303
4430
  const outcome = classifyFailure(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`, test.level);
4304
4431
  if (outcome.status === "fail") staticFailed += 1;
4305
4432
  if (outcome.status === "warn") staticWarnings += 1;
@@ -4398,7 +4525,16 @@ Please start your dev server and try again.`
4398
4525
  }
4399
4526
  } else if (component) {
4400
4527
  console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
4401
- contract = await runContractTests(componentName, component, strictness);
4528
+ const contractPath = config.test?.components?.find((comp) => comp?.name === componentName)?.contractPath;
4529
+ if (!contractPath) {
4530
+ throw new Error(`\u274C No contract path found for component: ${componentName}`);
4531
+ }
4532
+ contract = await runContractTests(
4533
+ import_path6.default.resolve(configBaseDir, contractPath),
4534
+ componentName,
4535
+ component,
4536
+ strictness
4537
+ );
4402
4538
  } else {
4403
4539
  throw new Error("\u274C Either component or URL must be provided");
4404
4540
  }