aria-ease 6.12.2 → 6.14.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.
package/dist/index.cjs CHANGED
@@ -612,7 +612,7 @@ var init_MenuComponentStrategy = __esm({
612
612
  if (!closeSelector && this.selectors.focusable) {
613
613
  closeSelector = this.selectors.focusable;
614
614
  } else if (!closeSelector) {
615
- closeSelector = this.selectors.trigger;
615
+ closeSelector = this.selectors.main;
616
616
  }
617
617
  if (closeSelector) {
618
618
  const closeElement = page.locator(closeSelector).first();
@@ -620,8 +620,8 @@ var init_MenuComponentStrategy = __esm({
620
620
  await page.keyboard.press("Escape");
621
621
  menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
622
622
  }
623
- if (!menuClosed && this.selectors.trigger) {
624
- const triggerElement = page.locator(this.selectors.trigger).first();
623
+ if (!menuClosed && this.selectors.main) {
624
+ const triggerElement = page.locator(this.selectors.main).first();
625
625
  await triggerElement.click({ timeout: this.actionTimeoutMs });
626
626
  menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
627
627
  }
@@ -641,8 +641,8 @@ This indicates a problem with the menu component's close functionality.`
641
641
  if (this.selectors.input) {
642
642
  await page.locator(this.selectors.input).first().clear();
643
643
  }
644
- if (this.selectors.trigger) {
645
- const triggerElement = page.locator(this.selectors.trigger).first();
644
+ if (this.selectors.main) {
645
+ const triggerElement = page.locator(this.selectors.main).first();
646
646
  await triggerElement.focus();
647
647
  }
648
648
  }
@@ -1559,12 +1559,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
1559
1559
  const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
1560
1560
  const isCustomContract = !!componentConfig?.contractPath;
1561
1561
  const reporter = new ContractReporter(true, isCustomContract);
1562
- const defaultTimeouts = {
1563
- actionTimeoutMs: 400,
1564
- assertionTimeoutMs: 400,
1565
- navigationTimeoutMs: 3e4,
1566
- componentReadyTimeoutMs: 5e3
1567
- };
1562
+ const defaultTimeouts = { actionTimeoutMs: 400, assertionTimeoutMs: 400, navigationTimeoutMs: 3e4, componentReadyTimeoutMs: 5e3 };
1568
1563
  const globalDisableTimeouts = config?.test?.disableTimeouts === true;
1569
1564
  const componentDisableTimeouts = componentConfig?.disableTimeouts === true;
1570
1565
  const disableTimeouts = componentDisableTimeouts || globalDisableTimeouts;
@@ -1576,11 +1571,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
1576
1571
  }
1577
1572
  return value;
1578
1573
  };
1579
- const actionTimeoutMs = resolveTimeout(
1580
- componentConfig?.actionTimeoutMs,
1581
- config?.test?.actionTimeoutMs,
1582
- defaultTimeouts.actionTimeoutMs
1583
- );
1574
+ const actionTimeoutMs = resolveTimeout(componentConfig?.actionTimeoutMs, config?.test?.actionTimeoutMs, defaultTimeouts.actionTimeoutMs);
1584
1575
  const assertionTimeoutMs = resolveTimeout(
1585
1576
  componentConfig?.assertionTimeoutMs,
1586
1577
  config?.test?.assertionTimeoutMs,
@@ -1680,8 +1671,8 @@ This usually means:
1680
1671
  );
1681
1672
  }
1682
1673
  reporter.start(componentName, totalTests, apgUrl);
1683
- if (componentName === "menu" && componentContract.selectors.trigger) {
1684
- await page.locator(componentContract.selectors.trigger).first().waitFor({ state: "attached", timeout: componentReadyTimeoutMs }).catch(() => {
1674
+ if (componentName === "menu" && componentContract.selectors.main) {
1675
+ await page.locator(componentContract.selectors.main).first().waitFor({ state: "visible", timeout: componentReadyTimeoutMs }).catch(() => {
1685
1676
  });
1686
1677
  }
1687
1678
  const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
@@ -1690,7 +1681,42 @@ This usually means:
1690
1681
  let staticFailed = 0;
1691
1682
  let staticWarnings = 0;
1692
1683
  for (const rel of componentContract.relationships || []) {
1684
+ if (strategy && typeof strategy.resetState === "function") {
1685
+ try {
1686
+ await strategy.resetState(page);
1687
+ } catch (err) {
1688
+ warnings.push(`Warning: resetState failed before relationship test: ${err instanceof Error ? err.message : String(err)}`);
1689
+ }
1690
+ }
1693
1691
  const relationshipLevel = normalizeLevel(rel.level);
1692
+ if (Array.isArray(rel.setup) && rel.setup.length > 0) {
1693
+ let isAllowedType2 = function(t) {
1694
+ return allowedTypes.includes(t);
1695
+ };
1696
+ var isAllowedType = isAllowedType2;
1697
+ const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
1698
+ const relDescription = rel.type === "aria-reference" ? `${rel.from}.${rel.attribute} references ${rel.to}` : `${rel.parent} contains ${rel.child}`;
1699
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
1700
+ const toSetupAction = (a) => ({
1701
+ ...a,
1702
+ type: isAllowedType2(a.type) ? a.type : "click"
1703
+ });
1704
+ const setupActions = rel.setup.map(toSetupAction);
1705
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, relDescription, ["submenu", "submenuTrigger", "submenuItems"]);
1706
+ if (setupResult.skip) {
1707
+ skipped.push(setupResult.message || "Setup action skipped");
1708
+ reporter.reportStaticTest(relDescription, "skip", setupResult.message, relationshipLevel);
1709
+ continue;
1710
+ }
1711
+ if (!setupResult.success) {
1712
+ const failure = `Relationship setup failed: ${setupResult.error}`;
1713
+ const outcome = classifyFailure(failure, rel.level);
1714
+ if (outcome.status === "fail") staticFailed += 1;
1715
+ if (outcome.status === "warn") staticWarnings += 1;
1716
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
1717
+ continue;
1718
+ }
1719
+ }
1694
1720
  if (componentName === "menu" && !hasSubmenuCapability) {
1695
1721
  const involvesSubmenu = isSubmenuRelation(rel);
1696
1722
  if (involvesSubmenu) {
@@ -1863,7 +1889,53 @@ This usually means:
1863
1889
  }
1864
1890
  }
1865
1891
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
1892
+ async function runSetupActions(setup, actionExecutor, strategy2, page2, description, skipKeywords = []) {
1893
+ if (!Array.isArray(setup) || setup.length === 0) return { success: true };
1894
+ if (strategy2 && typeof strategy2.resetState === "function") {
1895
+ await strategy2.resetState(page2);
1896
+ }
1897
+ for (const setupAct of setup) {
1898
+ let setupResult = { success: true };
1899
+ try {
1900
+ if (setupAct.type === "focus") {
1901
+ if (setupAct.target === "relative" && setupAct.relativeTarget) {
1902
+ setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
1903
+ } else {
1904
+ setupResult = await actionExecutor.focus(setupAct.target);
1905
+ }
1906
+ } else if (setupAct.type === "type" && setupAct.value) {
1907
+ setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
1908
+ } else if (setupAct.type === "click") {
1909
+ setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
1910
+ } else if (setupAct.type === "keypress" && setupAct.key) {
1911
+ setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
1912
+ } else if (setupAct.type === "hover") {
1913
+ setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
1914
+ } else {
1915
+ continue;
1916
+ }
1917
+ } catch (err) {
1918
+ setupResult = { success: false, error: err instanceof Error ? err.message : String(err) };
1919
+ }
1920
+ if (!setupResult.success) {
1921
+ const setupMsg = setupResult.error || "Setup action failed";
1922
+ const isSkip = skipKeywords.some((kw) => description.includes(kw) || setupMsg.includes(kw));
1923
+ if (isSkip) {
1924
+ return { success: false, skip: true, message: `Skipping test - capability not present: ${setupMsg}` };
1925
+ }
1926
+ return { success: false, error: setupMsg };
1927
+ }
1928
+ }
1929
+ return { success: true };
1930
+ }
1866
1931
  for (const test of componentContract.static[0]?.assertions || []) {
1932
+ if (strategy && typeof strategy.resetState === "function") {
1933
+ try {
1934
+ await strategy.resetState(page);
1935
+ } catch (err) {
1936
+ warnings.push(`Warning: resetState failed before static test: ${err instanceof Error ? err.message : String(err)}`);
1937
+ }
1938
+ }
1867
1939
  if (test.target === "relative") continue;
1868
1940
  const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
1869
1941
  const staticLevel = normalizeLevel(test.level);
@@ -1873,6 +1945,33 @@ This usually means:
1873
1945
  reporter.reportStaticTest(staticDescription, "skip", skipMessage, staticLevel);
1874
1946
  continue;
1875
1947
  }
1948
+ if (Array.isArray(test.setup) && test.setup.length > 0) {
1949
+ let isAllowedType2 = function(t) {
1950
+ return allowedTypes.includes(t);
1951
+ };
1952
+ var isAllowedType = isAllowedType2;
1953
+ const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
1954
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
1955
+ const toSetupAction = (a) => ({
1956
+ ...a,
1957
+ type: isAllowedType2(a.type) ? a.type : "click"
1958
+ });
1959
+ const setupActions = test.setup.map(toSetupAction);
1960
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, staticDescription, ["submenu", "submenuTrigger", "submenuItems"]);
1961
+ if (setupResult.skip) {
1962
+ skipped.push(setupResult.message || "Setup action skipped");
1963
+ reporter.reportStaticTest(staticDescription, "skip", setupResult.message, staticLevel);
1964
+ continue;
1965
+ }
1966
+ if (!setupResult.success) {
1967
+ const failure = `Static setup failed: ${setupResult.error}`;
1968
+ const outcome = classifyFailure(failure, test.level);
1969
+ if (outcome.status === "fail") staticFailed += 1;
1970
+ if (outcome.status === "warn") staticWarnings += 1;
1971
+ reporter.reportStaticTest(staticDescription, outcome.status, outcome.detail, outcome.level);
1972
+ continue;
1973
+ }
1974
+ }
1876
1975
  const targetSelector = componentContract.selectors[test.target];
1877
1976
  if (!targetSelector) {
1878
1977
  const failure = `Selector for target ${test.target} not found.`;
@@ -1999,31 +2098,26 @@ This usually means:
1999
2098
  const dynamicLevel = normalizeLevel(dynamicTest.level);
2000
2099
  const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
2001
2100
  if (Array.isArray(setup) && setup.length > 0) {
2002
- for (const setupAct of setup) {
2003
- let setupResult;
2004
- if (setupAct.type === "focus") {
2005
- if (setupAct.target === "relative" && setupAct.relativeTarget) {
2006
- setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
2007
- } else {
2008
- setupResult = await actionExecutor.focus(setupAct.target);
2009
- }
2010
- } else if (setupAct.type === "type" && setupAct.value) {
2011
- setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
2012
- } else if (setupAct.type === "click") {
2013
- setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
2014
- } else if (setupAct.type === "keypress" && setupAct.key) {
2015
- setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
2016
- } else if (setupAct.type === "hover") {
2017
- setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
2018
- } else {
2019
- continue;
2020
- }
2021
- if (!setupResult.success) {
2022
- const setupMsg = setupResult.error || "Setup action failed";
2023
- const outcome = classifyFailure(`Setup failed: ${setupMsg}`, dynamicTest.level);
2024
- reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
2025
- continue;
2026
- }
2101
+ let isAllowedType2 = function(t) {
2102
+ return allowedTypes.includes(t);
2103
+ };
2104
+ var isAllowedType = isAllowedType2;
2105
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
2106
+ const toSetupAction = (a) => ({
2107
+ ...a,
2108
+ type: isAllowedType2(a.type) ? a.type : "click"
2109
+ });
2110
+ const setupActions = setup.map(toSetupAction);
2111
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, dynamicTest.description, ["submenu", "submenuTrigger", "submenuItems"]);
2112
+ if (setupResult.skip) {
2113
+ skipped.push(setupResult.message || "Setup action skipped");
2114
+ reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, "skip", setupResult.message);
2115
+ continue;
2116
+ }
2117
+ if (!setupResult.success) {
2118
+ const outcome = classifyFailure(`Setup failed: ${setupResult.error}`, dynamicTest.level);
2119
+ reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
2120
+ continue;
2027
2121
  }
2028
2122
  }
2029
2123
  const failuresBeforeTest = failures.length;
@@ -3828,23 +3922,7 @@ function makeTabsAccessible({ tabListId, tabsClass, tabPanelsClass, orientation
3828
3922
  return { activateTab, cleanup, refresh };
3829
3923
  }
3830
3924
 
3831
- // src/utils/test/dsl/src/state-packs/comboboxStatePack.ts
3832
- function hasCapabilities(ctx, requiredCaps) {
3833
- return requiredCaps.some((cap) => ctx.capabilities.includes(cap));
3834
- }
3835
- function resolveSetup(setup, ctx) {
3836
- if (Array.isArray(setup) && setup.length && !setup[0].when) {
3837
- setup = [{ when: ["keyboard"], steps: () => setup }];
3838
- }
3839
- for (const strat of setup) {
3840
- if (hasCapabilities(ctx, strat.when)) {
3841
- return strat.steps(ctx);
3842
- }
3843
- }
3844
- throw new Error(
3845
- `No setup strategy matches capabilities: ${ctx.capabilities.join(", ")}`
3846
- );
3847
- }
3925
+ // src/utils/test/dsl/src/state-packs/combobox/comboboxStatePack.ts
3848
3926
  var COMBOBOX_STATES = {
3849
3927
  "popup.open": {
3850
3928
  setup: [
@@ -4120,9 +4198,313 @@ function isInputNotFilled() {
4120
4198
  ];
4121
4199
  }
4122
4200
 
4201
+ // src/utils/test/dsl/src/state-packs/menu/menuStatePack.ts
4202
+ var MENU_STATES = {
4203
+ "popup.open": {
4204
+ setup: [
4205
+ {
4206
+ when: ["keyboard"],
4207
+ steps: () => [
4208
+ { type: "keypress", target: "main", key: "Enter" }
4209
+ ]
4210
+ },
4211
+ {
4212
+ when: ["pointer"],
4213
+ steps: () => [
4214
+ { type: "click", target: "main" }
4215
+ ]
4216
+ }
4217
+ ],
4218
+ assertion: isMenuPopupOpen
4219
+ },
4220
+ "popup.closed": {
4221
+ setup: [
4222
+ {
4223
+ when: ["keyboard"],
4224
+ steps: () => [
4225
+ // component resets after each test so popup is closed
4226
+ ]
4227
+ },
4228
+ {
4229
+ when: ["pointer"],
4230
+ steps: () => []
4231
+ }
4232
+ ],
4233
+ assertion: isMenuPopupClosed
4234
+ },
4235
+ "main.focused": {
4236
+ setup: [
4237
+ {
4238
+ when: ["keyboard"],
4239
+ steps: () => [
4240
+ { type: "focus", target: "main" }
4241
+ ]
4242
+ }
4243
+ ],
4244
+ assertion: isMainFocused2
4245
+ },
4246
+ "main.notFocused": {
4247
+ setup: [
4248
+ {
4249
+ when: ["keyboard"],
4250
+ steps: () => [
4251
+ //what to do here?
4252
+ ]
4253
+ }
4254
+ ],
4255
+ assertion: isMainNotFocused2
4256
+ },
4257
+ "activeItem.first": {
4258
+ requires: ["popup.open"],
4259
+ setup: [
4260
+ {
4261
+ when: ["keyboard"],
4262
+ steps: () => [
4263
+ // By default, the first item should be active when the menu opens, so no action is needed to set this state
4264
+ ]
4265
+ }
4266
+ ],
4267
+ assertion: isActiveItemFirst
4268
+ },
4269
+ "activeItem.last": {
4270
+ requires: ["popup.open"],
4271
+ setup: [
4272
+ {
4273
+ when: ["keyboard"],
4274
+ steps: () => [
4275
+ { type: "keypress", target: "main", key: "ArrowUp" }
4276
+ ]
4277
+ }
4278
+ ],
4279
+ assertion: isActiveItemLast
4280
+ },
4281
+ "submenu.open": {
4282
+ requires: ["popup.open"],
4283
+ setup: [
4284
+ {
4285
+ when: ["keyboard"],
4286
+ steps: () => [
4287
+ { type: "keypress", target: "submenuTrigger", key: "ArrowRight" }
4288
+ ]
4289
+ },
4290
+ {
4291
+ when: ["pointer"],
4292
+ steps: () => [
4293
+ { type: "click", target: "submenuTrigger" }
4294
+ ]
4295
+ }
4296
+ ],
4297
+ assertion: isSubmenuPopupOpen
4298
+ },
4299
+ "submenu.closed": {
4300
+ requires: ["submenu.open"],
4301
+ setup: [
4302
+ {
4303
+ when: ["keyboard"],
4304
+ steps: () => [
4305
+ { type: "keypress", target: "submenuTrigger", key: "ArrowLeft" }
4306
+ ]
4307
+ },
4308
+ {
4309
+ when: ["pointer"],
4310
+ steps: () => [
4311
+ { type: "click", target: "submenuTrigger" }
4312
+ ]
4313
+ }
4314
+ ],
4315
+ assertion: isSubmenuPopupClosed
4316
+ },
4317
+ "submenuTrigger.focused": {
4318
+ setup: [
4319
+ {
4320
+ when: ["keyboard"],
4321
+ steps: () => [
4322
+ { type: "focus", target: "submenuTrigger" }
4323
+ ]
4324
+ }
4325
+ ],
4326
+ assertion: isSubmenuTriggerFocused
4327
+ },
4328
+ "submenuTrigger.notFocused": {
4329
+ setup: [
4330
+ {
4331
+ when: ["keyboard"],
4332
+ steps: () => [
4333
+ //what to do here?
4334
+ ]
4335
+ }
4336
+ ],
4337
+ assertion: isSubmenuTriggerNotFocused
4338
+ },
4339
+ "submenuActiveItem.first": {
4340
+ requires: ["submenu.open"],
4341
+ setup: [
4342
+ {
4343
+ when: ["keyboard"],
4344
+ steps: () => [
4345
+ // By default, the first item should be active when the submenu opens, so no action is needed to set this state
4346
+ ]
4347
+ },
4348
+ {
4349
+ when: ["pointer"],
4350
+ steps: () => []
4351
+ }
4352
+ ],
4353
+ assertion: isSubmenuActiveItemFirst
4354
+ }
4355
+ };
4356
+ function isMenuPopupOpen() {
4357
+ return [
4358
+ {
4359
+ target: "popup",
4360
+ assertion: "toBeVisible",
4361
+ failureMessage: "Expected popup to be visible"
4362
+ },
4363
+ {
4364
+ target: "main",
4365
+ assertion: "toHaveAttribute",
4366
+ attribute: "aria-expanded",
4367
+ expectedValue: "true",
4368
+ failureMessage: "Expect menu main to have aria-expanded='true'."
4369
+ }
4370
+ ];
4371
+ }
4372
+ function isMenuPopupClosed() {
4373
+ return [
4374
+ {
4375
+ target: "popup",
4376
+ assertion: "notToBeVisible",
4377
+ failureMessage: "Expected popup to be closed"
4378
+ },
4379
+ {
4380
+ target: "main",
4381
+ assertion: "toHaveAttribute",
4382
+ attribute: "aria-expanded",
4383
+ expectedValue: "false",
4384
+ failureMessage: "Expect menu main to have aria-expanded='false'."
4385
+ }
4386
+ ];
4387
+ }
4388
+ function isMainFocused2() {
4389
+ return [
4390
+ {
4391
+ target: "main",
4392
+ assertion: "toHaveFocus",
4393
+ failureMessage: "Expected menu main to be focused."
4394
+ }
4395
+ ];
4396
+ }
4397
+ function isMainNotFocused2() {
4398
+ return [
4399
+ {
4400
+ target: "main",
4401
+ assertion: "notToHaveFocus",
4402
+ failureMessage: "Expected menu main to not have focused."
4403
+ }
4404
+ ];
4405
+ }
4406
+ function isActiveItemFirst() {
4407
+ return [
4408
+ {
4409
+ target: "relative",
4410
+ assertion: "toHaveFocus",
4411
+ expectedValue: "first",
4412
+ failureMessage: "First menu item should have focus."
4413
+ }
4414
+ ];
4415
+ }
4416
+ function isActiveItemLast() {
4417
+ return [
4418
+ {
4419
+ target: "relative",
4420
+ assertion: "toHaveFocus",
4421
+ expectedValue: "last",
4422
+ failureMessage: "Last menu item should have focus."
4423
+ }
4424
+ ];
4425
+ }
4426
+ function isSubmenuPopupOpen() {
4427
+ return [
4428
+ {
4429
+ target: "submenu",
4430
+ assertion: "toBeVisible",
4431
+ failureMessage: "Expected submenu to be visible"
4432
+ },
4433
+ {
4434
+ target: "submenuTrigger",
4435
+ assertion: "toHaveAttribute",
4436
+ attribute: "aria-expanded",
4437
+ expectedValue: "true",
4438
+ failureMessage: "Expect submenu trigger to have aria-expanded='true'."
4439
+ }
4440
+ ];
4441
+ }
4442
+ function isSubmenuPopupClosed() {
4443
+ return [
4444
+ {
4445
+ target: "submenu",
4446
+ assertion: "notToBeVisible",
4447
+ failureMessage: "Expected submenu to be closed"
4448
+ },
4449
+ {
4450
+ target: "submenuTrigger",
4451
+ assertion: "toHaveAttribute",
4452
+ attribute: "aria-expanded",
4453
+ expectedValue: "false",
4454
+ failureMessage: "Expect submenu trigger to have aria-expanded='false'."
4455
+ }
4456
+ ];
4457
+ }
4458
+ function isSubmenuTriggerFocused() {
4459
+ return [
4460
+ {
4461
+ target: "submenuTrigger",
4462
+ assertion: "toHaveFocus",
4463
+ failureMessage: "Expected submenu trigger to be focused."
4464
+ }
4465
+ ];
4466
+ }
4467
+ function isSubmenuTriggerNotFocused() {
4468
+ return [
4469
+ {
4470
+ target: "submenuTrigger",
4471
+ assertion: "notToHaveFocus",
4472
+ failureMessage: "Expected submenu trigger to not have focused."
4473
+ }
4474
+ ];
4475
+ }
4476
+ function isSubmenuActiveItemFirst() {
4477
+ return [
4478
+ {
4479
+ target: "submenuItems",
4480
+ assertion: "toHaveFocus",
4481
+ failureMessage: "First interactive item in the submenu should have focus after Right Arrow open the submenu."
4482
+ }
4483
+ ];
4484
+ }
4485
+
4486
+ // src/utils/test/dsl/src/state-packs/Capability.ts
4487
+ function hasCapabilities(ctx, requiredCaps) {
4488
+ return requiredCaps.some((cap) => ctx.capabilities.includes(cap));
4489
+ }
4490
+ function resolveSetup(setup, ctx) {
4491
+ if (Array.isArray(setup) && setup.length && !setup[0].when) {
4492
+ setup = [{ when: ["keyboard"], steps: () => setup }];
4493
+ }
4494
+ for (const strat of setup) {
4495
+ if (hasCapabilities(ctx, strat.when)) {
4496
+ return strat.steps(ctx);
4497
+ }
4498
+ }
4499
+ throw new Error(
4500
+ `No setup strategy matches capabilities: ${ctx.capabilities.join(", ")}`
4501
+ );
4502
+ }
4503
+
4123
4504
  // src/utils/test/dsl/src/contractBuilder.ts
4124
4505
  var STATE_PACKS = {
4125
- "combobox": COMBOBOX_STATES
4506
+ "combobox": COMBOBOX_STATES,
4507
+ "menu": MENU_STATES
4126
4508
  // Add more mappings as needed
4127
4509
  };
4128
4510
  var FluentContract = class {
@@ -4153,30 +4535,94 @@ var ContractBuilder = class {
4153
4535
  return this;
4154
4536
  }
4155
4537
  relationships(fn) {
4538
+ const statePack = this.statePack;
4539
+ const ctx = { capabilities: ["keyboard"] };
4540
+ const resolveAllSetups = (stateName, visited = /* @__PURE__ */ new Set()) => {
4541
+ if (visited.has(stateName)) return [];
4542
+ visited.add(stateName);
4543
+ const s = statePack[stateName];
4544
+ if (!s) return [];
4545
+ let actions = [];
4546
+ if (Array.isArray(s.requires)) {
4547
+ for (const req of s.requires) {
4548
+ actions = actions.concat(resolveAllSetups(req, visited));
4549
+ }
4550
+ }
4551
+ if (s.setup) actions = actions.concat(resolveSetup(s.setup, ctx));
4552
+ return actions;
4553
+ };
4156
4554
  const api = {
4157
- ariaReference: (from, attribute, to) => ({
4158
- required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required" }),
4159
- optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" }),
4160
- recommended: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "recommended" })
4161
- }),
4162
- contains: (parent, child) => ({
4163
- required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required" }),
4164
- optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" }),
4165
- recommended: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "recommended" })
4166
- })
4555
+ ariaReference: (from, attribute, to) => {
4556
+ return {
4557
+ requires: (state) => {
4558
+ const setupActions = resolveAllSetups(state, /* @__PURE__ */ new Set());
4559
+ return {
4560
+ required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required", setup: setupActions }),
4561
+ optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional", setup: setupActions }),
4562
+ recommended: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "recommended", setup: setupActions })
4563
+ };
4564
+ },
4565
+ required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required" }),
4566
+ optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" }),
4567
+ recommended: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "recommended" })
4568
+ };
4569
+ },
4570
+ contains: (parent, child) => {
4571
+ return {
4572
+ requires: (state) => {
4573
+ const setupActions = resolveAllSetups(state, /* @__PURE__ */ new Set());
4574
+ return {
4575
+ required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required", setup: setupActions }),
4576
+ optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional", setup: setupActions }),
4577
+ recommended: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "recommended", setup: setupActions })
4578
+ };
4579
+ },
4580
+ required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required" }),
4581
+ optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" }),
4582
+ recommended: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "recommended" })
4583
+ };
4584
+ }
4167
4585
  };
4168
4586
  fn(api);
4169
4587
  return this;
4170
4588
  }
4171
4589
  static(fn) {
4172
4590
  const api = {
4173
- target: (target) => ({
4174
- has: (attribute, expectedValue) => ({
4175
- required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required" }),
4176
- optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" }),
4177
- recommended: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "recommended" })
4178
- })
4179
- })
4591
+ target: (target) => {
4592
+ return {
4593
+ requires: (state) => {
4594
+ const statePack = this.statePack;
4595
+ const ctx = { capabilities: ["keyboard"] };
4596
+ const resolveAllSetups = (stateName, visited = /* @__PURE__ */ new Set()) => {
4597
+ if (visited.has(stateName)) return [];
4598
+ visited.add(stateName);
4599
+ const s = statePack[stateName];
4600
+ if (!s) return [];
4601
+ let actions = [];
4602
+ if (Array.isArray(s.requires)) {
4603
+ for (const req of s.requires) {
4604
+ actions = actions.concat(resolveAllSetups(req, visited));
4605
+ }
4606
+ }
4607
+ if (s.setup) actions = actions.concat(resolveSetup(s.setup, ctx));
4608
+ return actions;
4609
+ };
4610
+ const setupActions = resolveAllSetups(state, /* @__PURE__ */ new Set());
4611
+ return {
4612
+ has: (attribute, expectedValue) => ({
4613
+ required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required", setup: setupActions }),
4614
+ optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional", setup: setupActions }),
4615
+ recommended: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "recommended", setup: setupActions })
4616
+ })
4617
+ };
4618
+ },
4619
+ has: (attribute, expectedValue) => ({
4620
+ required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required" }),
4621
+ optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" }),
4622
+ recommended: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "recommended" })
4623
+ })
4624
+ };
4625
+ }
4180
4626
  };
4181
4627
  fn(api);
4182
4628
  return this;