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.cjs CHANGED
@@ -452,6 +452,23 @@ function validateConfig(config) {
452
452
  if (typeof cfg.test !== "object" || cfg.test === null) {
453
453
  errors.push("test must be an object");
454
454
  } else {
455
+ if (cfg.test.disableTimeouts !== void 0 && typeof cfg.test.disableTimeouts !== "boolean") {
456
+ errors.push("test.disableTimeouts must be a boolean when provided");
457
+ }
458
+ const testTimeoutFields = [
459
+ "actionTimeoutMs",
460
+ "assertionTimeoutMs",
461
+ "navigationTimeoutMs",
462
+ "componentReadyTimeoutMs"
463
+ ];
464
+ for (const field of testTimeoutFields) {
465
+ const value = cfg.test[field];
466
+ if (value !== void 0) {
467
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
468
+ errors.push(`test.${field} must be a non-negative number when provided`);
469
+ }
470
+ }
471
+ }
455
472
  if (cfg.test.components !== void 0) {
456
473
  if (!Array.isArray(cfg.test.components)) {
457
474
  errors.push("test.components must be an array");
@@ -472,6 +489,23 @@ function validateConfig(config) {
472
489
  if (comp.strictness !== void 0 && !["minimal", "balanced", "strict", "paranoid"].includes(comp.strictness)) {
473
490
  errors.push(`test.components[${idx}].strictness must be one of: minimal, balanced, strict, paranoid`);
474
491
  }
492
+ if (comp.disableTimeouts !== void 0 && typeof comp.disableTimeouts !== "boolean") {
493
+ errors.push(`test.components[${idx}].disableTimeouts must be a boolean when provided`);
494
+ }
495
+ const componentTimeoutFields = [
496
+ "actionTimeoutMs",
497
+ "assertionTimeoutMs",
498
+ "navigationTimeoutMs",
499
+ "componentReadyTimeoutMs"
500
+ ];
501
+ for (const field of componentTimeoutFields) {
502
+ const value = comp[field];
503
+ if (value !== void 0) {
504
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
505
+ errors.push(`test.components[${idx}].${field} must be a non-negative number when provided`);
506
+ }
507
+ }
508
+ }
475
509
  }
476
510
  });
477
511
  }
@@ -1050,8 +1084,41 @@ var init_ActionExecutor = __esm({
1050
1084
  /**
1051
1085
  * Execute focus action
1052
1086
  */
1053
- async focus(target) {
1087
+ /**
1088
+ * Execute focus action (supports absolute, relative, and virtual focus)
1089
+ * @param target - selector key (e.g. "input", "button", "relative", or "virtual")
1090
+ * @param relativeTarget - for relative focus (e.g. "first", "last")
1091
+ * @param virtualId - for virtual focus (aria-activedescendant value)
1092
+ */
1093
+ async focus(target, relativeTarget, virtualId) {
1054
1094
  try {
1095
+ 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.` };
1099
+ }
1100
+ const input = this.page.locator(inputSelector).first();
1101
+ const exists = await input.count();
1102
+ if (!exists) {
1103
+ return { success: false, error: `Input element not found for virtual focus.` };
1104
+ }
1105
+ await input.evaluate((el, id) => {
1106
+ el.setAttribute("aria-activedescendant", id);
1107
+ }, virtualId);
1108
+ return { success: true };
1109
+ }
1110
+ if (target === "relative" && relativeTarget) {
1111
+ const relativeSelector = this.selectors.relative;
1112
+ if (!relativeSelector) {
1113
+ return { success: false, error: `Relative selector not defined for focus action.` };
1114
+ }
1115
+ const element = await RelativeTargetResolver.resolve(this.page, relativeSelector, relativeTarget);
1116
+ if (!element) {
1117
+ return { success: false, error: `Could not resolve relative target ${relativeTarget} for focus.` };
1118
+ }
1119
+ await element.focus({ timeout: this.timeoutMs });
1120
+ return { success: true };
1121
+ }
1055
1122
  const selector = this.selectors[target];
1056
1123
  if (!selector) {
1057
1124
  return { success: false, error: `Selector for focus target ${target} not found.` };
@@ -1252,10 +1319,10 @@ var init_AssertionRunner = __esm({
1252
1319
  /**
1253
1320
  * Resolve the target element for an assertion
1254
1321
  */
1255
- async resolveTarget(targetName, relativeTarget) {
1322
+ async resolveTarget(targetName, relativeTarget, selectorKey) {
1256
1323
  try {
1257
1324
  if (targetName === "relative") {
1258
- const relativeSelector = this.selectors.relative;
1325
+ const relativeSelector = selectorKey ? this.selectors[selectorKey] : this.selectors.relative;
1259
1326
  if (!relativeSelector) {
1260
1327
  return { target: null, error: "Relative selector is not defined in the contract." };
1261
1328
  }
@@ -1442,10 +1509,30 @@ var init_AssertionRunner = __esm({
1442
1509
  failMessage: `CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity.`
1443
1510
  };
1444
1511
  }
1445
- const { target, error } = await this.resolveTarget(assertion.target, assertion.relativeTarget || assertion.expectedValue);
1512
+ const { target, error } = await this.resolveTarget(
1513
+ assertion.target,
1514
+ assertion.relativeTarget || assertion.expectedValue,
1515
+ assertion.selectorKey
1516
+ );
1446
1517
  if (error || !target) {
1447
1518
  return { success: false, failMessage: error || `Target ${assertion.target} not found.`, target: null };
1448
1519
  }
1520
+ if (assertion.target === "input" && assertion.attribute === "aria-activedescendant" && assertion.expectedValue === "!empty" && assertion.relativeTarget && assertion.selectorKey) {
1521
+ const optionLocator = await RelativeTargetResolver.resolve(this.page, this.selectors[assertion.selectorKey], assertion.relativeTarget);
1522
+ const optionId = optionLocator ? await optionLocator.getAttribute("id") : null;
1523
+ const inputId = await target.getAttribute("aria-activedescendant");
1524
+ if (optionId && inputId === optionId) {
1525
+ return {
1526
+ success: true,
1527
+ passMessage: `input[aria-activedescendant] matches id of ${assertion.relativeTarget}(${assertion.selectorKey}). Test: "${testDescription}".`
1528
+ };
1529
+ } else {
1530
+ return {
1531
+ success: false,
1532
+ failMessage: `input[aria-activedescendant] should match id of ${assertion.relativeTarget}(${assertion.selectorKey}), found "${inputId}".`
1533
+ };
1534
+ }
1535
+ }
1449
1536
  switch (assertion.assertion) {
1450
1537
  case "toBeVisible":
1451
1538
  return this.validateVisibility(target, assertion.target, true, assertion.failureMessage || "", testDescription);
@@ -1492,8 +1579,43 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
1492
1579
  const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
1493
1580
  const isCustomContract = !!componentConfig?.path;
1494
1581
  const reporter = new ContractReporter(true, isCustomContract);
1495
- const actionTimeoutMs = 400;
1496
- const assertionTimeoutMs = 400;
1582
+ const defaultTimeouts = {
1583
+ actionTimeoutMs: 400,
1584
+ assertionTimeoutMs: 400,
1585
+ navigationTimeoutMs: 3e4,
1586
+ componentReadyTimeoutMs: 5e3
1587
+ };
1588
+ const globalDisableTimeouts = config?.test?.disableTimeouts === true;
1589
+ const componentDisableTimeouts = componentConfig?.disableTimeouts === true;
1590
+ const disableTimeouts = componentDisableTimeouts || globalDisableTimeouts;
1591
+ const resolveTimeout = (componentValue, globalValue, fallback) => {
1592
+ if (disableTimeouts) return 0;
1593
+ const value = componentValue ?? globalValue;
1594
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
1595
+ return fallback;
1596
+ }
1597
+ return value;
1598
+ };
1599
+ const actionTimeoutMs = resolveTimeout(
1600
+ componentConfig?.actionTimeoutMs,
1601
+ config?.test?.actionTimeoutMs,
1602
+ defaultTimeouts.actionTimeoutMs
1603
+ );
1604
+ const assertionTimeoutMs = resolveTimeout(
1605
+ componentConfig?.assertionTimeoutMs,
1606
+ config?.test?.assertionTimeoutMs,
1607
+ defaultTimeouts.assertionTimeoutMs
1608
+ );
1609
+ const navigationTimeoutMs = resolveTimeout(
1610
+ componentConfig?.navigationTimeoutMs,
1611
+ config?.test?.navigationTimeoutMs,
1612
+ defaultTimeouts.navigationTimeoutMs
1613
+ );
1614
+ const componentReadyTimeoutMs = resolveTimeout(
1615
+ componentConfig?.componentReadyTimeoutMs,
1616
+ config?.test?.componentReadyTimeoutMs,
1617
+ defaultTimeouts.componentReadyTimeoutMs
1618
+ );
1497
1619
  const strictnessMode = normalizeStrictness(strictness);
1498
1620
  let contractPath = componentConfig?.path;
1499
1621
  if (!contractPath) {
@@ -1551,7 +1673,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
1551
1673
  try {
1552
1674
  await page.goto(url, {
1553
1675
  waitUntil: "domcontentloaded",
1554
- timeout: 3e4
1676
+ timeout: navigationTimeoutMs
1555
1677
  });
1556
1678
  } catch (error) {
1557
1679
  throw new Error(
@@ -1569,7 +1691,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
1569
1691
  throw new Error(`CRITICAL: No selector found in contract for ${componentName}`);
1570
1692
  }
1571
1693
  try {
1572
- await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
1694
+ await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: componentReadyTimeoutMs });
1573
1695
  } catch (error) {
1574
1696
  throw new Error(
1575
1697
  `
@@ -1583,18 +1705,26 @@ This usually means:
1583
1705
  }
1584
1706
  reporter.start(componentName, totalTests, apgUrl);
1585
1707
  if (componentName === "menu" && componentContract.selectors.trigger) {
1586
- await page.locator(componentContract.selectors.trigger).first().waitFor({
1587
- state: "attached",
1588
- timeout: 5e3
1589
- }).catch(() => {
1708
+ await page.locator(componentContract.selectors.trigger).first().waitFor({ state: "attached", timeout: componentReadyTimeoutMs }).catch(() => {
1590
1709
  });
1591
1710
  }
1592
1711
  const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
1712
+ const isSubmenuRelation = (rel) => rel.type === "aria-reference" && [rel.from, rel.to].some((name) => ["submenu", "submenuTrigger", "submenuItems"].includes(name || "")) || rel.type === "contains" && [rel.parent, rel.child].some((name) => ["submenu", "submenuTrigger", "submenuItems"].includes(name || ""));
1593
1713
  let staticPassed = 0;
1594
1714
  let staticFailed = 0;
1595
1715
  let staticWarnings = 0;
1596
1716
  for (const rel of componentContract.relationships || []) {
1597
1717
  const relationshipLevel = normalizeLevel(rel.level);
1718
+ if (componentName === "menu" && !hasSubmenuCapability) {
1719
+ const involvesSubmenu = isSubmenuRelation(rel);
1720
+ if (involvesSubmenu) {
1721
+ const relDescription = rel.type === "aria-reference" ? `${rel.from}.${rel.attribute} references ${rel.to}` : `${rel.parent} contains ${rel.child}`;
1722
+ const skipMessage = `Skipping submenu relationship assertion: no submenu capability detected in rendered component.`;
1723
+ skipped.push(skipMessage);
1724
+ reporter.reportStaticTest(relDescription, "skip", skipMessage, relationshipLevel);
1725
+ continue;
1726
+ }
1727
+ }
1598
1728
  if (rel.type === "aria-reference") {
1599
1729
  const relDescription = `${rel.from}.${rel.attribute} references ${rel.to}`;
1600
1730
  const fromSelector = componentContract.selectors[rel.from];
@@ -1614,6 +1744,12 @@ This usually means:
1614
1744
  const fromExists = await fromTarget.count() > 0;
1615
1745
  const toExists = await toTarget.count() > 0;
1616
1746
  if (!fromExists || !toExists) {
1747
+ if (componentName === "menu" && isSubmenuRelation(rel)) {
1748
+ const skipMessage = "Skipping submenu relationship assertion in static phase: submenu elements are not present until submenu is opened.";
1749
+ skipped.push(skipMessage);
1750
+ reporter.reportStaticTest(relDescription, "skip", skipMessage, relationshipLevel);
1751
+ continue;
1752
+ }
1617
1753
  const outcome = classifyFailure(
1618
1754
  `Relationship target not found: ${!fromExists ? rel.from : rel.to}.`,
1619
1755
  rel.level
@@ -1669,6 +1805,12 @@ This usually means:
1669
1805
  const parent = page.locator(parentSelector).first();
1670
1806
  const parentExists = await parent.count() > 0;
1671
1807
  if (!parentExists) {
1808
+ if (componentName === "menu" && isSubmenuRelation(rel)) {
1809
+ const skipMessage = "Skipping submenu relationship assertion in static phase: submenu container is not present until submenu is opened.";
1810
+ skipped.push(skipMessage);
1811
+ reporter.reportStaticTest(relDescription, "skip", skipMessage, relationshipLevel);
1812
+ continue;
1813
+ }
1672
1814
  const outcome = classifyFailure(`Relationship parent target not found: ${rel.parent}.`, rel.level);
1673
1815
  if (outcome.status === "fail") staticFailed += 1;
1674
1816
  if (outcome.status === "warn") staticWarnings += 1;
@@ -1678,6 +1820,12 @@ This usually means:
1678
1820
  const descendants = parent.locator(childSelector);
1679
1821
  const descendantCount = await descendants.count();
1680
1822
  if (descendantCount < 1) {
1823
+ if (componentName === "menu" && isSubmenuRelation(rel)) {
1824
+ const skipMessage = "Skipping submenu relationship assertion in static phase: submenu descendants are not present until submenu is opened.";
1825
+ skipped.push(skipMessage);
1826
+ reporter.reportStaticTest(relDescription, "skip", skipMessage, relationshipLevel);
1827
+ continue;
1828
+ }
1681
1829
  const outcome = classifyFailure(
1682
1830
  `Expected ${rel.parent} to contain descendant matching selector for ${rel.child}.`,
1683
1831
  rel.level
@@ -1801,11 +1949,6 @@ This usually means:
1801
1949
  failures.push(`CRITICAL: Browser/page closed before completing all tests. ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests skipped.`);
1802
1950
  break;
1803
1951
  }
1804
- const { action, assertions } = dynamicTest;
1805
- const failuresBeforeTest = failures.length;
1806
- const warningsBeforeTest = warnings.length;
1807
- const skippedBeforeTest = skipped.length;
1808
- const dynamicLevel = normalizeLevel(dynamicTest.level);
1809
1952
  try {
1810
1953
  await strategy.resetState(page);
1811
1954
  } catch (error) {
@@ -1813,6 +1956,40 @@ This usually means:
1813
1956
  reporter.error(errorMessage);
1814
1957
  throw error;
1815
1958
  }
1959
+ const { setup = [], action, assertions } = dynamicTest;
1960
+ const dynamicLevel = normalizeLevel(dynamicTest.level);
1961
+ const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
1962
+ if (Array.isArray(setup) && setup.length > 0) {
1963
+ for (const setupAct of setup) {
1964
+ let setupResult;
1965
+ if (setupAct.type === "focus") {
1966
+ if (setupAct.target === "relative" && setupAct.relativeTarget) {
1967
+ setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
1968
+ } else {
1969
+ setupResult = await actionExecutor.focus(setupAct.target);
1970
+ }
1971
+ } else if (setupAct.type === "type" && setupAct.value) {
1972
+ setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
1973
+ } else if (setupAct.type === "click") {
1974
+ setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
1975
+ } else if (setupAct.type === "keypress" && setupAct.key) {
1976
+ setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
1977
+ } else if (setupAct.type === "hover") {
1978
+ setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
1979
+ } else {
1980
+ continue;
1981
+ }
1982
+ if (!setupResult.success) {
1983
+ const setupMsg = setupResult.error || "Setup action failed";
1984
+ const outcome = classifyFailure(`Setup failed: ${setupMsg}`, dynamicTest.level);
1985
+ reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
1986
+ continue;
1987
+ }
1988
+ }
1989
+ }
1990
+ const failuresBeforeTest = failures.length;
1991
+ const warningsBeforeTest = warnings.length;
1992
+ const skippedBeforeTest = skipped.length;
1816
1993
  const shouldSkipTest = await strategy.shouldSkipTest(dynamicTest, page);
1817
1994
  if (shouldSkipTest) {
1818
1995
  const skipMessage = `Skipping test - component-specific conditions not met`;
@@ -1820,7 +1997,6 @@ This usually means:
1820
1997
  reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, "skip", skipMessage);
1821
1998
  continue;
1822
1999
  }
1823
- const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
1824
2000
  const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
1825
2001
  let shouldAbortCurrentTest = false;
1826
2002
  let actionOutcome = null;
@@ -1832,7 +2008,11 @@ This usually means:
1832
2008
  }
1833
2009
  let result;
1834
2010
  if (act.type === "focus") {
1835
- result = await actionExecutor.focus(act.target);
2011
+ if (act.target === "relative" && act.relativeTarget) {
2012
+ result = await actionExecutor.focus("relative", act.relativeTarget);
2013
+ } else {
2014
+ result = await actionExecutor.focus(act.target);
2015
+ }
1836
2016
  } else if (act.type === "type" && act.value) {
1837
2017
  result = await actionExecutor.type(act.target, act.value);
1838
2018
  } else if (act.type === "click") {
@@ -1910,7 +2090,14 @@ This usually means:
1910
2090
  Make sure your dev server is running at ${url}`);
1911
2091
  } else if (error.message.includes("Timeout") && error.message.includes("waitFor")) {
1912
2092
  throw new Error(
1913
- "\n\u274C CRITICAL: Component not found on page!\nThe component selector could not be found within 30 seconds.\nThis usually means:\n - The component didn't render\n - The URL is incorrect\n - The component selector was not provided to the component utility, or a wrong selector was used\n"
2093
+ `
2094
+ \u274C CRITICAL: Component not found on page!
2095
+ The component selector could not be found within ${componentReadyTimeoutMs}ms.
2096
+ This usually means:
2097
+ - The component didn't render
2098
+ - The URL is incorrect
2099
+ - The component selector was not provided to the component utility, or a wrong selector was used
2100
+ `
1914
2101
  );
1915
2102
  } else if (error.message.includes("Target page, context or browser has been closed")) {
1916
2103
  throw new Error(
@@ -3087,16 +3274,12 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
3087
3274
  }
3088
3275
  function setActiveDescendant(index) {
3089
3276
  const visibleItems = getVisibleItems();
3090
- visibleItems.forEach((item) => {
3091
- item.setAttribute("aria-selected", "false");
3092
- });
3093
3277
  if (index >= 0 && index < visibleItems.length) {
3094
3278
  const activeItem = visibleItems[index];
3095
3279
  const itemId = activeItem.id || `${listBoxId}-option-${index}`;
3096
3280
  if (!activeItem.id) {
3097
3281
  activeItem.id = itemId;
3098
3282
  }
3099
- activeItem.setAttribute("aria-selected", "true");
3100
3283
  comboboxInput.setAttribute("aria-activedescendant", itemId);
3101
3284
  if (typeof activeItem.scrollIntoView === "function") {
3102
3285
  activeItem.scrollIntoView({ block: "nearest", behavior: "smooth" });
@@ -3129,8 +3312,6 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
3129
3312
  comboboxInput.setAttribute("aria-activedescendant", "");
3130
3313
  listBox.style.display = "none";
3131
3314
  activeIndex = -1;
3132
- const visibleItems = getVisibleItems();
3133
- visibleItems.forEach((item) => item.setAttribute("aria-selected", "false"));
3134
3315
  if (callback?.onOpenChange) {
3135
3316
  try {
3136
3317
  callback.onOpenChange(false);
@@ -3142,6 +3323,7 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
3142
3323
  function selectOption(item) {
3143
3324
  const value = item.textContent?.trim() || "";
3144
3325
  comboboxInput.value = value;
3326
+ item.setAttribute("aria-selected", "true");
3145
3327
  closeListbox();
3146
3328
  if (callback?.onSelect) {
3147
3329
  try {
@@ -3188,6 +3370,10 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
3188
3370
  } else if (comboboxInput.value) {
3189
3371
  event.preventDefault();
3190
3372
  comboboxInput.value = "";
3373
+ const visibleItems2 = getVisibleItems();
3374
+ visibleItems2.forEach((item) => {
3375
+ if (item.getAttribute("aria-selected") === "true") item.setAttribute("aria-selected", "false");
3376
+ });
3191
3377
  if (callback?.onClear) {
3192
3378
  try {
3193
3379
  callback.onClear();
@@ -3266,9 +3452,24 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
3266
3452
  function initializeOptions() {
3267
3453
  const items = listBox.querySelectorAll(`.${listBoxItemsClass}`);
3268
3454
  if (items.length === 0) return;
3455
+ let selectedValue = null;
3456
+ for (const item of items) {
3457
+ if (item.getAttribute("aria-selected") === "true") {
3458
+ selectedValue = item.textContent?.trim() || null;
3459
+ break;
3460
+ }
3461
+ }
3462
+ if (!selectedValue && comboboxInput.value) {
3463
+ selectedValue = comboboxInput.value.trim();
3464
+ }
3269
3465
  items.forEach((item, index) => {
3270
3466
  item.setAttribute("role", "option");
3271
- item.setAttribute("aria-selected", "false");
3467
+ const itemValue = item.textContent?.trim() || "";
3468
+ if (selectedValue && itemValue === selectedValue) {
3469
+ item.setAttribute("aria-selected", "true");
3470
+ } else {
3471
+ item.setAttribute("aria-selected", "false");
3472
+ }
3272
3473
  const currentId = item.getAttribute("id");
3273
3474
  if (!currentId || currentId === "") {
3274
3475
  const itemId = `${listBoxId}-option-${index}`;
@@ -3559,315 +3760,384 @@ function makeTabsAccessible({ tabListId, tabsClass, tabPanelsClass, orientation
3559
3760
  return { activateTab, cleanup, refresh };
3560
3761
  }
3561
3762
 
3562
- // src/utils/test/dsl/index.ts
3563
- var FluentContract = class {
3564
- constructor(jsonContract) {
3565
- this.jsonContract = jsonContract;
3566
- }
3567
- toJSON() {
3568
- return this.jsonContract;
3569
- }
3570
- };
3571
- var StaticTargetBuilder = class {
3572
- constructor(targetName, sink) {
3573
- this.targetName = targetName;
3574
- this.sink = sink;
3575
- }
3576
- has(attribute, expectedValue) {
3577
- const create = (level) => {
3578
- this.sink.push({
3579
- target: this.targetName,
3580
- attribute,
3581
- expectedValue,
3582
- failureMessage: `Expected ${this.targetName} to have ${attribute}${expectedValue !== void 0 ? `=${expectedValue}` : ""}.`,
3583
- level
3584
- });
3585
- };
3586
- return {
3587
- required: () => create("required"),
3588
- recommended: () => create("recommended"),
3589
- optional: () => create("optional")
3590
- };
3763
+ // src/utils/test/dsl/src/state-packs/comboboxStatePack.ts
3764
+ function hasCapabilities(ctx, requiredCaps) {
3765
+ return requiredCaps.some((cap) => ctx.capabilities.includes(cap));
3766
+ }
3767
+ function resolveSetup(setup, ctx) {
3768
+ if (Array.isArray(setup) && setup.length && !setup[0].when) {
3769
+ setup = [{ when: ["keyboard"], steps: () => setup }];
3591
3770
  }
3592
- };
3593
- var StaticBuilder = class {
3594
- constructor(sink) {
3595
- this.sink = sink;
3771
+ for (const strat of setup) {
3772
+ if (hasCapabilities(ctx, strat.when)) {
3773
+ return strat.steps(ctx);
3774
+ }
3596
3775
  }
3597
- target(targetName) {
3598
- return new StaticTargetBuilder(targetName, this.sink);
3776
+ throw new Error(
3777
+ `No setup strategy matches capabilities: ${ctx.capabilities.join(", ")}`
3778
+ );
3779
+ }
3780
+ var COMBOBOX_STATES = {
3781
+ "listbox.open": {
3782
+ setup: [
3783
+ {
3784
+ when: ["keyboard", "textInput"],
3785
+ steps: () => [
3786
+ { type: "keypress", target: "input", key: "ArrowDown" }
3787
+ ]
3788
+ },
3789
+ {
3790
+ when: ["pointer"],
3791
+ steps: () => [
3792
+ { type: "click", target: "button" }
3793
+ ]
3794
+ }
3795
+ ],
3796
+ assertion: isComboboxOpen
3797
+ },
3798
+ "listbox.closed": {
3799
+ setup: [
3800
+ {
3801
+ when: ["keyboard"],
3802
+ steps: () => [
3803
+ /* { type: "keypress", target: "input", key: "Escape" } */
3804
+ ]
3805
+ },
3806
+ {
3807
+ when: ["pointer"],
3808
+ steps: () => [
3809
+ /* { type: "click", target: "button" } */
3810
+ ]
3811
+ }
3812
+ ],
3813
+ assertion: isComboboxClosed
3814
+ },
3815
+ "input.focused": {
3816
+ setup: [
3817
+ {
3818
+ when: ["keyboard"],
3819
+ steps: () => [
3820
+ { type: "focus", target: "input" }
3821
+ ]
3822
+ }
3823
+ ],
3824
+ assertion: isInputFocused
3825
+ },
3826
+ "input.filled": {
3827
+ setup: [
3828
+ {
3829
+ when: ["keyboard", "textInput"],
3830
+ steps: () => [
3831
+ { type: "type", target: "input", value: "test" }
3832
+ ]
3833
+ }
3834
+ ],
3835
+ assertion: isInputFilled
3836
+ },
3837
+ "activeOption.first": {
3838
+ requires: ["listbox.open"],
3839
+ setup: [
3840
+ {
3841
+ when: ["keyboard"],
3842
+ steps: () => [
3843
+ { type: "keypress", target: "input", key: "ArrowDown" }
3844
+ ]
3845
+ }
3846
+ ],
3847
+ assertion: isActiveDescendantNotEmpty
3848
+ },
3849
+ "activeOption.last": {
3850
+ requires: ["activeOption.first"],
3851
+ setup: [
3852
+ {
3853
+ when: ["keyboard"],
3854
+ steps: () => [
3855
+ { type: "keypress", target: "input", key: "ArrowUp" }
3856
+ ]
3857
+ }
3858
+ ],
3859
+ assertion: isActiveDescendantNotEmpty
3860
+ },
3861
+ "selectedOption.first": {
3862
+ requires: ["listbox.open"],
3863
+ setup: [
3864
+ {
3865
+ when: ["pointer"],
3866
+ steps: () => [
3867
+ { type: "click", target: "relative", relativeTarget: "first" }
3868
+ ]
3869
+ }
3870
+ ],
3871
+ assertion: () => isAriaSelected("first")
3872
+ },
3873
+ "selectedOption.last": {
3874
+ requires: ["listbox.open"],
3875
+ setup: [
3876
+ {
3877
+ when: ["pointer"],
3878
+ steps: () => [
3879
+ { type: "click", target: "relative", relativeTarget: "last" }
3880
+ ]
3881
+ }
3882
+ ],
3883
+ assertion: () => isAriaSelected("last")
3599
3884
  }
3600
3885
  };
3601
- var DynamicChain = class {
3602
- constructor(key, testsSink, selectors) {
3603
- this.key = key;
3604
- this.testsSink = testsSink;
3605
- this.selectors = selectors;
3606
- }
3607
- selectorTarget = "";
3608
- actions = [];
3609
- assertions = [];
3610
- explicitDescription = "";
3611
- on(target) {
3612
- this.selectorTarget = target;
3613
- this.actions.push({ type: "keypress", target, key: this.key });
3614
- return this;
3615
- }
3616
- describe(description) {
3617
- this.explicitDescription = description;
3618
- return this;
3619
- }
3620
- focus(targetExpression) {
3621
- const parsed = this.parseRelativeExpression(targetExpression);
3622
- if (parsed) {
3623
- if (!this.selectors[parsed.selectorKey]) {
3624
- const availableSelectors = Object.keys(this.selectors).sort().join(", ") || "(none)";
3625
- throw new Error(
3626
- `Invalid focus target expression "${targetExpression}": selector "${parsed.selectorKey}" is not defined. Available selectors: ${availableSelectors}`
3627
- );
3628
- }
3629
- if (!this.selectors.relative && this.selectors[parsed.selectorKey]) {
3630
- this.selectors.relative = this.selectors[parsed.selectorKey];
3631
- }
3632
- this.assertions.push({
3633
- target: "relative",
3634
- assertion: "toHaveFocus",
3635
- relativeTarget: parsed.relativeTarget
3636
- });
3637
- } else {
3638
- this.assertions.push({
3639
- target: targetExpression,
3640
- assertion: "toHaveFocus"
3641
- });
3886
+ function isComboboxOpen() {
3887
+ return [
3888
+ {
3889
+ target: "listbox",
3890
+ assertion: "toBeVisible",
3891
+ failureMessage: "Expected listbox to be visible"
3892
+ },
3893
+ {
3894
+ target: "input",
3895
+ assertion: "toHaveAttribute",
3896
+ attribute: "aria-expanded",
3897
+ expectedValue: "true",
3898
+ failureMessage: "Expect combobox input to have aria-expanded='true'"
3642
3899
  }
3643
- return this;
3644
- }
3645
- visible(target) {
3646
- this.assertions.push({ target, assertion: "toBeVisible" });
3647
- return this;
3648
- }
3649
- hidden(target) {
3650
- this.assertions.push({ target, assertion: "notToBeVisible" });
3651
- return this;
3652
- }
3653
- has(target, attribute, expectedValue) {
3654
- this.assertions.push({
3655
- target,
3900
+ ];
3901
+ }
3902
+ function isComboboxClosed() {
3903
+ return [
3904
+ {
3905
+ target: "listbox",
3906
+ assertion: "notToBeVisible",
3907
+ failureMessage: "Expected listbox to be closed"
3908
+ },
3909
+ {
3910
+ target: "input",
3656
3911
  assertion: "toHaveAttribute",
3657
- attribute,
3658
- expectedValue
3659
- });
3660
- return this;
3661
- }
3662
- required() {
3663
- this.finalize("required");
3664
- }
3665
- recommended() {
3666
- this.finalize("recommended");
3667
- }
3668
- optional() {
3669
- this.finalize("optional");
3670
- }
3671
- finalize(level) {
3672
- if (!this.selectorTarget) {
3673
- throw new Error("Dynamic contract chain requires .on(<selectorKey>) before level terminator.");
3912
+ attribute: "aria-expanded",
3913
+ expectedValue: "false",
3914
+ failureMessage: "Expect combobox input to have aria-expanded='false'"
3674
3915
  }
3675
- const description = this.explicitDescription || `Pressing ${this.key} on ${this.selectorTarget} satisfies expected behavior.`;
3676
- this.testsSink.push({
3677
- description,
3678
- level,
3679
- action: this.actions,
3680
- assertions: this.assertions.map((a) => ({ ...a, level }))
3681
- });
3916
+ ];
3917
+ }
3918
+ function isActiveDescendantNotEmpty() {
3919
+ return [
3920
+ {
3921
+ target: "input",
3922
+ assertion: "toHaveAttribute",
3923
+ attribute: "aria-activedescendant",
3924
+ expectedValue: "!empty",
3925
+ failureMessage: "Expected aria-activedescendant to not be empty"
3926
+ }
3927
+ ];
3928
+ }
3929
+ function isAriaSelected(index) {
3930
+ return [
3931
+ {
3932
+ target: "relative",
3933
+ relativeTarget: index,
3934
+ assertion: "toHaveAttribute",
3935
+ attribute: "aria-selected",
3936
+ expectedValue: "true",
3937
+ failureMessage: `Expected ${index} option to have aria-selected='true'`
3938
+ }
3939
+ ];
3940
+ }
3941
+ function isInputFocused() {
3942
+ return [
3943
+ {
3944
+ target: "input",
3945
+ assertion: "toHaveFocus",
3946
+ failureMessage: "Expected input to be focused"
3947
+ }
3948
+ ];
3949
+ }
3950
+ function isInputFilled() {
3951
+ return [
3952
+ {
3953
+ target: "input",
3954
+ assertion: "toHaveValue",
3955
+ expectedValue: "test",
3956
+ failureMessage: "Expected input to have the value 'test'"
3957
+ }
3958
+ ];
3959
+ }
3960
+
3961
+ // src/utils/test/dsl/src/contractBuilder.ts
3962
+ var STATE_PACKS = {
3963
+ "combobox.listbox": COMBOBOX_STATES
3964
+ // Add more mappings as needed
3965
+ };
3966
+ var FluentContract = class {
3967
+ constructor(jsonContract) {
3968
+ this.jsonContract = jsonContract;
3682
3969
  }
3683
- parseRelativeExpression(input) {
3684
- const match = input.match(/^(next|previous|first|last)\(([^)]+)\)$/);
3685
- if (!match) return null;
3686
- const relativeTarget = match[1];
3687
- const selectorKey = match[2].trim();
3688
- return { relativeTarget, selectorKey };
3970
+ toJSON() {
3971
+ return this.jsonContract;
3689
3972
  }
3690
3973
  };
3691
3974
  var ContractBuilder = class {
3692
3975
  constructor(componentName) {
3693
3976
  this.componentName = componentName;
3977
+ this.statePack = STATE_PACKS[componentName] || {};
3694
3978
  }
3695
3979
  metaValue = {};
3696
3980
  selectorsValue = {};
3697
3981
  relationshipInvariants = [];
3698
3982
  staticAssertions = [];
3699
3983
  dynamicTests = [];
3984
+ statePack;
3700
3985
  meta(meta) {
3701
- this.metaValue = { ...this.metaValue, ...meta };
3986
+ this.metaValue = meta;
3702
3987
  return this;
3703
3988
  }
3704
3989
  selectors(selectors) {
3705
- this.selectorsValue = { ...this.selectorsValue, ...selectors };
3990
+ this.selectorsValue = selectors;
3706
3991
  return this;
3707
3992
  }
3708
- relationship(invariant) {
3709
- this.relationshipInvariants.push(invariant);
3993
+ relationships(fn) {
3994
+ const api = {
3995
+ ariaReference: (from, attribute, to) => ({
3996
+ 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" })
3998
+ }),
3999
+ contains: (parent, child) => ({
4000
+ required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required" }),
4001
+ optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" })
4002
+ })
4003
+ };
4004
+ fn(api);
3710
4005
  return this;
3711
4006
  }
3712
- relationships(builderFn) {
3713
- builderFn({
3714
- ariaReference: (from, attribute, to) => {
3715
- const create = (level) => {
3716
- this.relationshipInvariants.push({
3717
- type: "aria-reference",
3718
- from,
3719
- attribute,
3720
- to,
3721
- level
3722
- });
3723
- };
3724
- return {
3725
- required: () => create("required"),
3726
- recommended: () => create("recommended"),
3727
- optional: () => create("optional")
3728
- };
3729
- },
3730
- contains: (parent, child) => {
3731
- const create = (level) => {
3732
- this.relationshipInvariants.push({
3733
- type: "contains",
3734
- parent,
3735
- child,
3736
- level
3737
- });
3738
- };
3739
- return {
3740
- required: () => create("required"),
3741
- recommended: () => create("recommended"),
3742
- optional: () => create("optional")
3743
- };
3744
- }
3745
- });
4007
+ static(fn) {
4008
+ const api = {
4009
+ target: (target) => ({
4010
+ has: (attribute, expectedValue) => ({
4011
+ required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required" }),
4012
+ optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" })
4013
+ })
4014
+ })
4015
+ };
4016
+ fn(api);
4017
+ return this;
4018
+ }
4019
+ when(event) {
4020
+ return new DynamicTestBuilder(this, this.statePack, event);
4021
+ }
4022
+ addDynamicTest(test) {
4023
+ this.dynamicTests.push(test);
4024
+ }
4025
+ build() {
4026
+ return {
4027
+ meta: this.metaValue,
4028
+ selectors: this.selectorsValue,
4029
+ relationships: this.relationshipInvariants.length ? this.relationshipInvariants : void 0,
4030
+ static: this.staticAssertions.length ? [{ assertions: this.staticAssertions }] : [],
4031
+ dynamic: this.dynamicTests
4032
+ };
4033
+ }
4034
+ };
4035
+ var DynamicTestBuilder = class {
4036
+ constructor(parent, statePack, event) {
4037
+ this.parent = parent;
4038
+ this.statePack = statePack;
4039
+ this.event = event;
4040
+ }
4041
+ _as;
4042
+ _on;
4043
+ _given = [];
4044
+ _then = [];
4045
+ _desc = "";
4046
+ _level = "required";
4047
+ as(actionType) {
4048
+ this._as = actionType;
3746
4049
  return this;
3747
4050
  }
3748
- static(builderFn) {
3749
- builderFn(new StaticBuilder(this.staticAssertions));
4051
+ on(target) {
4052
+ this._on = target;
3750
4053
  return this;
3751
4054
  }
3752
- when(key) {
3753
- return new DynamicChain(key, this.dynamicTests, this.selectorsValue);
4055
+ given(states) {
4056
+ this._given = Array.isArray(states) ? states : [states];
4057
+ return this;
3754
4058
  }
3755
- validateRelationshipInvariants() {
3756
- if (this.relationshipInvariants.length === 0) {
3757
- return;
3758
- }
3759
- const selectorKeys = new Set(Object.keys(this.selectorsValue));
3760
- const available = Object.keys(this.selectorsValue).sort().join(", ");
3761
- const errors = [];
3762
- this.relationshipInvariants.forEach((invariant, index) => {
3763
- const prefix = `relationships[${index}] (${invariant.type})`;
3764
- if (invariant.type === "aria-reference") {
3765
- if (!selectorKeys.has(invariant.from)) {
3766
- errors.push(`${prefix}: "from" references unknown selector "${invariant.from}"`);
3767
- }
3768
- if (!selectorKeys.has(invariant.to)) {
3769
- errors.push(`${prefix}: "to" references unknown selector "${invariant.to}"`);
3770
- }
3771
- }
3772
- if (invariant.type === "contains") {
3773
- if (!selectorKeys.has(invariant.parent)) {
3774
- errors.push(`${prefix}: "parent" references unknown selector "${invariant.parent}"`);
3775
- }
3776
- if (!selectorKeys.has(invariant.child)) {
3777
- errors.push(`${prefix}: "child" references unknown selector "${invariant.child}"`);
3778
- }
3779
- }
3780
- });
3781
- if (errors.length > 0) {
3782
- const availableSelectorsMessage = available.length > 0 ? available : "(none)";
3783
- throw new Error(
3784
- [
3785
- `Contract invariant validation failed for component "${this.componentName}".`,
3786
- ...errors.map((error) => `- ${error}`),
3787
- `Available selectors: ${availableSelectorsMessage}`
3788
- ].join("\n")
3789
- );
3790
- }
4059
+ then(states) {
4060
+ this._then = Array.isArray(states) ? states : [states];
4061
+ return this;
3791
4062
  }
3792
- validateStaticTargets() {
3793
- const selectorKeys = new Set(Object.keys(this.selectorsValue));
3794
- const available = Object.keys(this.selectorsValue).sort().join(", ") || "(none)";
3795
- const errors = [];
3796
- this.staticAssertions.forEach((assertion, index) => {
3797
- if (!selectorKeys.has(assertion.target)) {
3798
- errors.push(`static.assertions[${index}]: target "${assertion.target}" is not defined in selectors`);
3799
- }
3800
- });
3801
- if (errors.length > 0) {
3802
- throw new Error(
3803
- [
3804
- `Contract static target validation failed for component "${this.componentName}".`,
3805
- ...errors.map((error) => `- ${error}`),
3806
- `Available selectors: ${available}`
3807
- ].join("\n")
3808
- );
3809
- }
4063
+ describe(desc) {
4064
+ this._desc = desc;
4065
+ return this;
3810
4066
  }
3811
- validateDynamicTargets() {
3812
- const selectorKeys = new Set(Object.keys(this.selectorsValue));
3813
- const available = Object.keys(this.selectorsValue).sort().join(", ") || "(none)";
3814
- const errors = [];
3815
- const isValidActionTarget = (target) => {
3816
- return selectorKeys.has(target) || target === "document" || target === "relative";
3817
- };
3818
- const isValidAssertionTarget = (target) => {
3819
- return selectorKeys.has(target) || target === "relative";
4067
+ required() {
4068
+ this._level = "required";
4069
+ this._finalize();
4070
+ return this.parent;
4071
+ }
4072
+ optional() {
4073
+ this._level = "optional";
4074
+ this._finalize();
4075
+ return this.parent;
4076
+ }
4077
+ recommended() {
4078
+ this._level = "recommended";
4079
+ this._finalize();
4080
+ return this.parent;
4081
+ }
4082
+ _finalize() {
4083
+ const capabilityMap = {
4084
+ keypress: "keyboard",
4085
+ click: "pointer",
4086
+ type: "textInput",
4087
+ focus: "keyboard",
4088
+ hover: "pointer"
4089
+ // add more mappings as needed
3820
4090
  };
3821
- this.dynamicTests.forEach((test, testIndex) => {
3822
- test.action.forEach((action, actionIndex) => {
3823
- if (!isValidActionTarget(action.target)) {
3824
- errors.push(
3825
- `dynamic[${testIndex}].action[${actionIndex}]: target "${action.target}" is not defined in selectors`
3826
- );
3827
- }
3828
- });
3829
- test.assertions.forEach((assertion, assertionIndex) => {
3830
- if (!isValidAssertionTarget(assertion.target)) {
3831
- errors.push(
3832
- `dynamic[${testIndex}].assertions[${assertionIndex}]: target "${assertion.target}" is not defined in selectors`
3833
- );
4091
+ const capability = capabilityMap[this._as || "keyboard"] || (this._as || "keyboard");
4092
+ const ctx = { capabilities: [capability] };
4093
+ const resolveAllSetups = (stateName, visited = /* @__PURE__ */ new Set()) => {
4094
+ if (visited.has(stateName)) return [];
4095
+ visited.add(stateName);
4096
+ const s = this.statePack[stateName];
4097
+ if (!s) return [];
4098
+ let actions = [];
4099
+ if (Array.isArray(s.requires)) {
4100
+ for (const req of s.requires) {
4101
+ actions = actions.concat(resolveAllSetups(req, visited));
3834
4102
  }
3835
- if (assertion.target === "relative" && !this.selectorsValue.relative) {
3836
- errors.push(
3837
- `dynamic[${testIndex}].assertions[${assertionIndex}]: target "relative" requires selectors.relative to be defined`
3838
- );
4103
+ }
4104
+ if (s.setup) actions = actions.concat(resolveSetup(s.setup, ctx));
4105
+ return actions;
4106
+ };
4107
+ const setup = [];
4108
+ for (const state of this._given) {
4109
+ setup.push(...resolveAllSetups(state));
4110
+ }
4111
+ const assertions = [];
4112
+ for (const state of this._then) {
4113
+ const s = this.statePack[state];
4114
+ if (s && s.assertion !== void 0) {
4115
+ let value = s.assertion;
4116
+ if (typeof value === "function") {
4117
+ try {
4118
+ value = value();
4119
+ } catch (e) {
4120
+ throw new Error(`Error calling assertion function for state '${state}': ${e.message}`);
4121
+ }
3839
4122
  }
3840
- });
3841
- });
3842
- if (errors.length > 0) {
3843
- throw new Error(
3844
- [
3845
- `Contract dynamic target validation failed for component "${this.componentName}".`,
3846
- ...errors.map((error) => `- ${error}`),
3847
- `Available selectors: ${available}`,
3848
- `Allowed special targets: document, relative`
3849
- ].join("\n")
3850
- );
4123
+ if (Array.isArray(value)) assertions.push(...value);
4124
+ else assertions.push(value);
4125
+ }
3851
4126
  }
3852
- }
3853
- build() {
3854
- this.validateRelationshipInvariants();
3855
- this.validateStaticTargets();
3856
- this.validateDynamicTargets();
3857
- const fallbackId = this.metaValue.id || `aria-ease.contract.${this.componentName}`;
3858
- return {
3859
- meta: {
3860
- id: fallbackId,
3861
- version: this.metaValue.version || "1.0.0",
3862
- description: this.metaValue.description || `Fluent contract for ${this.componentName}`,
3863
- source: this.metaValue.source,
3864
- W3CName: this.metaValue.W3CName
3865
- },
3866
- selectors: this.selectorsValue,
3867
- relationships: this.relationshipInvariants,
3868
- static: [{ assertions: this.staticAssertions }],
3869
- dynamic: this.dynamicTests
3870
- };
4127
+ const action = [
4128
+ {
4129
+ type: this._as,
4130
+ target: this._on,
4131
+ key: this._as === "keypress" ? this.event : void 0
4132
+ }
4133
+ ];
4134
+ this.parent.addDynamicTest({
4135
+ description: this._desc || "",
4136
+ level: this._level,
4137
+ action,
4138
+ assertions,
4139
+ ...setup.length ? { setup } : {}
4140
+ });
3871
4141
  }
3872
4142
  };
3873
4143
  function createContract(componentName, define) {