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.
@@ -16,7 +16,7 @@ var StrategyRegistry = class {
16
16
  registerBuiltInStrategies() {
17
17
  this.builtInStrategies.set(
18
18
  "menu",
19
- () => import('./MenuComponentStrategy-VKZQYLBE.js').then(
19
+ () => import('./MenuComponentStrategy-6XWU5KLW.js').then(
20
20
  (m) => m.MenuComponentStrategy
21
21
  )
22
22
  );
@@ -656,12 +656,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
656
656
  const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
657
657
  const isCustomContract = !!componentConfig?.contractPath;
658
658
  const reporter = new ContractReporter(true, isCustomContract);
659
- const defaultTimeouts = {
660
- actionTimeoutMs: 400,
661
- assertionTimeoutMs: 400,
662
- navigationTimeoutMs: 3e4,
663
- componentReadyTimeoutMs: 5e3
664
- };
659
+ const defaultTimeouts = { actionTimeoutMs: 400, assertionTimeoutMs: 400, navigationTimeoutMs: 3e4, componentReadyTimeoutMs: 5e3 };
665
660
  const globalDisableTimeouts = config?.test?.disableTimeouts === true;
666
661
  const componentDisableTimeouts = componentConfig?.disableTimeouts === true;
667
662
  const disableTimeouts = componentDisableTimeouts || globalDisableTimeouts;
@@ -673,11 +668,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
673
668
  }
674
669
  return value;
675
670
  };
676
- const actionTimeoutMs = resolveTimeout(
677
- componentConfig?.actionTimeoutMs,
678
- config?.test?.actionTimeoutMs,
679
- defaultTimeouts.actionTimeoutMs
680
- );
671
+ const actionTimeoutMs = resolveTimeout(componentConfig?.actionTimeoutMs, config?.test?.actionTimeoutMs, defaultTimeouts.actionTimeoutMs);
681
672
  const assertionTimeoutMs = resolveTimeout(
682
673
  componentConfig?.assertionTimeoutMs,
683
674
  config?.test?.assertionTimeoutMs,
@@ -777,8 +768,8 @@ This usually means:
777
768
  );
778
769
  }
779
770
  reporter.start(componentName, totalTests, apgUrl);
780
- if (componentName === "menu" && componentContract.selectors.trigger) {
781
- await page.locator(componentContract.selectors.trigger).first().waitFor({ state: "attached", timeout: componentReadyTimeoutMs }).catch(() => {
771
+ if (componentName === "menu" && componentContract.selectors.main) {
772
+ await page.locator(componentContract.selectors.main).first().waitFor({ state: "visible", timeout: componentReadyTimeoutMs }).catch(() => {
782
773
  });
783
774
  }
784
775
  const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
@@ -787,7 +778,42 @@ This usually means:
787
778
  let staticFailed = 0;
788
779
  let staticWarnings = 0;
789
780
  for (const rel of componentContract.relationships || []) {
781
+ if (strategy && typeof strategy.resetState === "function") {
782
+ try {
783
+ await strategy.resetState(page);
784
+ } catch (err) {
785
+ warnings.push(`Warning: resetState failed before relationship test: ${err instanceof Error ? err.message : String(err)}`);
786
+ }
787
+ }
790
788
  const relationshipLevel = normalizeLevel(rel.level);
789
+ if (Array.isArray(rel.setup) && rel.setup.length > 0) {
790
+ let isAllowedType2 = function(t) {
791
+ return allowedTypes.includes(t);
792
+ };
793
+ var isAllowedType = isAllowedType2;
794
+ const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
795
+ const relDescription = rel.type === "aria-reference" ? `${rel.from}.${rel.attribute} references ${rel.to}` : `${rel.parent} contains ${rel.child}`;
796
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
797
+ const toSetupAction = (a) => ({
798
+ ...a,
799
+ type: isAllowedType2(a.type) ? a.type : "click"
800
+ });
801
+ const setupActions = rel.setup.map(toSetupAction);
802
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, relDescription, ["submenu", "submenuTrigger", "submenuItems"]);
803
+ if (setupResult.skip) {
804
+ skipped.push(setupResult.message || "Setup action skipped");
805
+ reporter.reportStaticTest(relDescription, "skip", setupResult.message, relationshipLevel);
806
+ continue;
807
+ }
808
+ if (!setupResult.success) {
809
+ const failure = `Relationship setup failed: ${setupResult.error}`;
810
+ const outcome = classifyFailure(failure, rel.level);
811
+ if (outcome.status === "fail") staticFailed += 1;
812
+ if (outcome.status === "warn") staticWarnings += 1;
813
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
814
+ continue;
815
+ }
816
+ }
791
817
  if (componentName === "menu" && !hasSubmenuCapability) {
792
818
  const involvesSubmenu = isSubmenuRelation(rel);
793
819
  if (involvesSubmenu) {
@@ -960,7 +986,53 @@ This usually means:
960
986
  }
961
987
  }
962
988
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
989
+ async function runSetupActions(setup, actionExecutor, strategy2, page2, description, skipKeywords = []) {
990
+ if (!Array.isArray(setup) || setup.length === 0) return { success: true };
991
+ if (strategy2 && typeof strategy2.resetState === "function") {
992
+ await strategy2.resetState(page2);
993
+ }
994
+ for (const setupAct of setup) {
995
+ let setupResult = { success: true };
996
+ try {
997
+ if (setupAct.type === "focus") {
998
+ if (setupAct.target === "relative" && setupAct.relativeTarget) {
999
+ setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
1000
+ } else {
1001
+ setupResult = await actionExecutor.focus(setupAct.target);
1002
+ }
1003
+ } else if (setupAct.type === "type" && setupAct.value) {
1004
+ setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
1005
+ } else if (setupAct.type === "click") {
1006
+ setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
1007
+ } else if (setupAct.type === "keypress" && setupAct.key) {
1008
+ setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
1009
+ } else if (setupAct.type === "hover") {
1010
+ setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
1011
+ } else {
1012
+ continue;
1013
+ }
1014
+ } catch (err) {
1015
+ setupResult = { success: false, error: err instanceof Error ? err.message : String(err) };
1016
+ }
1017
+ if (!setupResult.success) {
1018
+ const setupMsg = setupResult.error || "Setup action failed";
1019
+ const isSkip = skipKeywords.some((kw) => description.includes(kw) || setupMsg.includes(kw));
1020
+ if (isSkip) {
1021
+ return { success: false, skip: true, message: `Skipping test - capability not present: ${setupMsg}` };
1022
+ }
1023
+ return { success: false, error: setupMsg };
1024
+ }
1025
+ }
1026
+ return { success: true };
1027
+ }
963
1028
  for (const test of componentContract.static[0]?.assertions || []) {
1029
+ if (strategy && typeof strategy.resetState === "function") {
1030
+ try {
1031
+ await strategy.resetState(page);
1032
+ } catch (err) {
1033
+ warnings.push(`Warning: resetState failed before static test: ${err instanceof Error ? err.message : String(err)}`);
1034
+ }
1035
+ }
964
1036
  if (test.target === "relative") continue;
965
1037
  const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
966
1038
  const staticLevel = normalizeLevel(test.level);
@@ -970,6 +1042,33 @@ This usually means:
970
1042
  reporter.reportStaticTest(staticDescription, "skip", skipMessage, staticLevel);
971
1043
  continue;
972
1044
  }
1045
+ if (Array.isArray(test.setup) && test.setup.length > 0) {
1046
+ let isAllowedType2 = function(t) {
1047
+ return allowedTypes.includes(t);
1048
+ };
1049
+ var isAllowedType = isAllowedType2;
1050
+ const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
1051
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
1052
+ const toSetupAction = (a) => ({
1053
+ ...a,
1054
+ type: isAllowedType2(a.type) ? a.type : "click"
1055
+ });
1056
+ const setupActions = test.setup.map(toSetupAction);
1057
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, staticDescription, ["submenu", "submenuTrigger", "submenuItems"]);
1058
+ if (setupResult.skip) {
1059
+ skipped.push(setupResult.message || "Setup action skipped");
1060
+ reporter.reportStaticTest(staticDescription, "skip", setupResult.message, staticLevel);
1061
+ continue;
1062
+ }
1063
+ if (!setupResult.success) {
1064
+ const failure = `Static setup failed: ${setupResult.error}`;
1065
+ const outcome = classifyFailure(failure, test.level);
1066
+ if (outcome.status === "fail") staticFailed += 1;
1067
+ if (outcome.status === "warn") staticWarnings += 1;
1068
+ reporter.reportStaticTest(staticDescription, outcome.status, outcome.detail, outcome.level);
1069
+ continue;
1070
+ }
1071
+ }
973
1072
  const targetSelector = componentContract.selectors[test.target];
974
1073
  if (!targetSelector) {
975
1074
  const failure = `Selector for target ${test.target} not found.`;
@@ -1096,31 +1195,26 @@ This usually means:
1096
1195
  const dynamicLevel = normalizeLevel(dynamicTest.level);
1097
1196
  const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
1098
1197
  if (Array.isArray(setup) && setup.length > 0) {
1099
- for (const setupAct of setup) {
1100
- let setupResult;
1101
- if (setupAct.type === "focus") {
1102
- if (setupAct.target === "relative" && setupAct.relativeTarget) {
1103
- setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
1104
- } else {
1105
- setupResult = await actionExecutor.focus(setupAct.target);
1106
- }
1107
- } else if (setupAct.type === "type" && setupAct.value) {
1108
- setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
1109
- } else if (setupAct.type === "click") {
1110
- setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
1111
- } else if (setupAct.type === "keypress" && setupAct.key) {
1112
- setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
1113
- } else if (setupAct.type === "hover") {
1114
- setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
1115
- } else {
1116
- continue;
1117
- }
1118
- if (!setupResult.success) {
1119
- const setupMsg = setupResult.error || "Setup action failed";
1120
- const outcome = classifyFailure(`Setup failed: ${setupMsg}`, dynamicTest.level);
1121
- reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
1122
- continue;
1123
- }
1198
+ let isAllowedType2 = function(t) {
1199
+ return allowedTypes.includes(t);
1200
+ };
1201
+ var isAllowedType = isAllowedType2;
1202
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
1203
+ const toSetupAction = (a) => ({
1204
+ ...a,
1205
+ type: isAllowedType2(a.type) ? a.type : "click"
1206
+ });
1207
+ const setupActions = setup.map(toSetupAction);
1208
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, dynamicTest.description, ["submenu", "submenuTrigger", "submenuItems"]);
1209
+ if (setupResult.skip) {
1210
+ skipped.push(setupResult.message || "Setup action skipped");
1211
+ reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, "skip", setupResult.message);
1212
+ continue;
1213
+ }
1214
+ if (!setupResult.success) {
1215
+ const outcome = classifyFailure(`Setup failed: ${setupResult.error}`, dynamicTest.level);
1216
+ reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
1217
+ continue;
1124
1218
  }
1125
1219
  }
1126
1220
  const failuresBeforeTest = failures.length;
@@ -1,22 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // src/utils/test/dsl/src/state-packs/comboboxStatePack.ts
4
- function hasCapabilities(ctx, requiredCaps) {
5
- return requiredCaps.some((cap) => ctx.capabilities.includes(cap));
6
- }
7
- function resolveSetup(setup, ctx) {
8
- if (Array.isArray(setup) && setup.length && !setup[0].when) {
9
- setup = [{ when: ["keyboard"], steps: () => setup }];
10
- }
11
- for (const strat of setup) {
12
- if (hasCapabilities(ctx, strat.when)) {
13
- return strat.steps(ctx);
14
- }
15
- }
16
- throw new Error(
17
- `No setup strategy matches capabilities: ${ctx.capabilities.join(", ")}`
18
- );
19
- }
3
+ // src/utils/test/dsl/src/state-packs/combobox/comboboxStatePack.ts
20
4
  var COMBOBOX_STATES = {
21
5
  "popup.open": {
22
6
  setup: [
@@ -292,9 +276,313 @@ function isInputNotFilled() {
292
276
  ];
293
277
  }
294
278
 
279
+ // src/utils/test/dsl/src/state-packs/menu/menuStatePack.ts
280
+ var MENU_STATES = {
281
+ "popup.open": {
282
+ setup: [
283
+ {
284
+ when: ["keyboard"],
285
+ steps: () => [
286
+ { type: "keypress", target: "main", key: "Enter" }
287
+ ]
288
+ },
289
+ {
290
+ when: ["pointer"],
291
+ steps: () => [
292
+ { type: "click", target: "main" }
293
+ ]
294
+ }
295
+ ],
296
+ assertion: isMenuPopupOpen
297
+ },
298
+ "popup.closed": {
299
+ setup: [
300
+ {
301
+ when: ["keyboard"],
302
+ steps: () => [
303
+ // component resets after each test so popup is closed
304
+ ]
305
+ },
306
+ {
307
+ when: ["pointer"],
308
+ steps: () => []
309
+ }
310
+ ],
311
+ assertion: isMenuPopupClosed
312
+ },
313
+ "main.focused": {
314
+ setup: [
315
+ {
316
+ when: ["keyboard"],
317
+ steps: () => [
318
+ { type: "focus", target: "main" }
319
+ ]
320
+ }
321
+ ],
322
+ assertion: isMainFocused2
323
+ },
324
+ "main.notFocused": {
325
+ setup: [
326
+ {
327
+ when: ["keyboard"],
328
+ steps: () => [
329
+ //what to do here?
330
+ ]
331
+ }
332
+ ],
333
+ assertion: isMainNotFocused2
334
+ },
335
+ "activeItem.first": {
336
+ requires: ["popup.open"],
337
+ setup: [
338
+ {
339
+ when: ["keyboard"],
340
+ steps: () => [
341
+ // By default, the first item should be active when the menu opens, so no action is needed to set this state
342
+ ]
343
+ }
344
+ ],
345
+ assertion: isActiveItemFirst
346
+ },
347
+ "activeItem.last": {
348
+ requires: ["popup.open"],
349
+ setup: [
350
+ {
351
+ when: ["keyboard"],
352
+ steps: () => [
353
+ { type: "keypress", target: "main", key: "ArrowUp" }
354
+ ]
355
+ }
356
+ ],
357
+ assertion: isActiveItemLast
358
+ },
359
+ "submenu.open": {
360
+ requires: ["popup.open"],
361
+ setup: [
362
+ {
363
+ when: ["keyboard"],
364
+ steps: () => [
365
+ { type: "keypress", target: "submenuTrigger", key: "ArrowRight" }
366
+ ]
367
+ },
368
+ {
369
+ when: ["pointer"],
370
+ steps: () => [
371
+ { type: "click", target: "submenuTrigger" }
372
+ ]
373
+ }
374
+ ],
375
+ assertion: isSubmenuPopupOpen
376
+ },
377
+ "submenu.closed": {
378
+ requires: ["submenu.open"],
379
+ setup: [
380
+ {
381
+ when: ["keyboard"],
382
+ steps: () => [
383
+ { type: "keypress", target: "submenuTrigger", key: "ArrowLeft" }
384
+ ]
385
+ },
386
+ {
387
+ when: ["pointer"],
388
+ steps: () => [
389
+ { type: "click", target: "submenuTrigger" }
390
+ ]
391
+ }
392
+ ],
393
+ assertion: isSubmenuPopupClosed
394
+ },
395
+ "submenuTrigger.focused": {
396
+ setup: [
397
+ {
398
+ when: ["keyboard"],
399
+ steps: () => [
400
+ { type: "focus", target: "submenuTrigger" }
401
+ ]
402
+ }
403
+ ],
404
+ assertion: isSubmenuTriggerFocused
405
+ },
406
+ "submenuTrigger.notFocused": {
407
+ setup: [
408
+ {
409
+ when: ["keyboard"],
410
+ steps: () => [
411
+ //what to do here?
412
+ ]
413
+ }
414
+ ],
415
+ assertion: isSubmenuTriggerNotFocused
416
+ },
417
+ "submenuActiveItem.first": {
418
+ requires: ["submenu.open"],
419
+ setup: [
420
+ {
421
+ when: ["keyboard"],
422
+ steps: () => [
423
+ // By default, the first item should be active when the submenu opens, so no action is needed to set this state
424
+ ]
425
+ },
426
+ {
427
+ when: ["pointer"],
428
+ steps: () => []
429
+ }
430
+ ],
431
+ assertion: isSubmenuActiveItemFirst
432
+ }
433
+ };
434
+ function isMenuPopupOpen() {
435
+ return [
436
+ {
437
+ target: "popup",
438
+ assertion: "toBeVisible",
439
+ failureMessage: "Expected popup to be visible"
440
+ },
441
+ {
442
+ target: "main",
443
+ assertion: "toHaveAttribute",
444
+ attribute: "aria-expanded",
445
+ expectedValue: "true",
446
+ failureMessage: "Expect menu main to have aria-expanded='true'."
447
+ }
448
+ ];
449
+ }
450
+ function isMenuPopupClosed() {
451
+ return [
452
+ {
453
+ target: "popup",
454
+ assertion: "notToBeVisible",
455
+ failureMessage: "Expected popup to be closed"
456
+ },
457
+ {
458
+ target: "main",
459
+ assertion: "toHaveAttribute",
460
+ attribute: "aria-expanded",
461
+ expectedValue: "false",
462
+ failureMessage: "Expect menu main to have aria-expanded='false'."
463
+ }
464
+ ];
465
+ }
466
+ function isMainFocused2() {
467
+ return [
468
+ {
469
+ target: "main",
470
+ assertion: "toHaveFocus",
471
+ failureMessage: "Expected menu main to be focused."
472
+ }
473
+ ];
474
+ }
475
+ function isMainNotFocused2() {
476
+ return [
477
+ {
478
+ target: "main",
479
+ assertion: "notToHaveFocus",
480
+ failureMessage: "Expected menu main to not have focused."
481
+ }
482
+ ];
483
+ }
484
+ function isActiveItemFirst() {
485
+ return [
486
+ {
487
+ target: "relative",
488
+ assertion: "toHaveFocus",
489
+ expectedValue: "first",
490
+ failureMessage: "First menu item should have focus."
491
+ }
492
+ ];
493
+ }
494
+ function isActiveItemLast() {
495
+ return [
496
+ {
497
+ target: "relative",
498
+ assertion: "toHaveFocus",
499
+ expectedValue: "last",
500
+ failureMessage: "Last menu item should have focus."
501
+ }
502
+ ];
503
+ }
504
+ function isSubmenuPopupOpen() {
505
+ return [
506
+ {
507
+ target: "submenu",
508
+ assertion: "toBeVisible",
509
+ failureMessage: "Expected submenu to be visible"
510
+ },
511
+ {
512
+ target: "submenuTrigger",
513
+ assertion: "toHaveAttribute",
514
+ attribute: "aria-expanded",
515
+ expectedValue: "true",
516
+ failureMessage: "Expect submenu trigger to have aria-expanded='true'."
517
+ }
518
+ ];
519
+ }
520
+ function isSubmenuPopupClosed() {
521
+ return [
522
+ {
523
+ target: "submenu",
524
+ assertion: "notToBeVisible",
525
+ failureMessage: "Expected submenu to be closed"
526
+ },
527
+ {
528
+ target: "submenuTrigger",
529
+ assertion: "toHaveAttribute",
530
+ attribute: "aria-expanded",
531
+ expectedValue: "false",
532
+ failureMessage: "Expect submenu trigger to have aria-expanded='false'."
533
+ }
534
+ ];
535
+ }
536
+ function isSubmenuTriggerFocused() {
537
+ return [
538
+ {
539
+ target: "submenuTrigger",
540
+ assertion: "toHaveFocus",
541
+ failureMessage: "Expected submenu trigger to be focused."
542
+ }
543
+ ];
544
+ }
545
+ function isSubmenuTriggerNotFocused() {
546
+ return [
547
+ {
548
+ target: "submenuTrigger",
549
+ assertion: "notToHaveFocus",
550
+ failureMessage: "Expected submenu trigger to not have focused."
551
+ }
552
+ ];
553
+ }
554
+ function isSubmenuActiveItemFirst() {
555
+ return [
556
+ {
557
+ target: "submenuItems",
558
+ assertion: "toHaveFocus",
559
+ failureMessage: "First interactive item in the submenu should have focus after Right Arrow open the submenu."
560
+ }
561
+ ];
562
+ }
563
+
564
+ // src/utils/test/dsl/src/state-packs/Capability.ts
565
+ function hasCapabilities(ctx, requiredCaps) {
566
+ return requiredCaps.some((cap) => ctx.capabilities.includes(cap));
567
+ }
568
+ function resolveSetup(setup, ctx) {
569
+ if (Array.isArray(setup) && setup.length && !setup[0].when) {
570
+ setup = [{ when: ["keyboard"], steps: () => setup }];
571
+ }
572
+ for (const strat of setup) {
573
+ if (hasCapabilities(ctx, strat.when)) {
574
+ return strat.steps(ctx);
575
+ }
576
+ }
577
+ throw new Error(
578
+ `No setup strategy matches capabilities: ${ctx.capabilities.join(", ")}`
579
+ );
580
+ }
581
+
295
582
  // src/utils/test/dsl/src/contractBuilder.ts
296
583
  var STATE_PACKS = {
297
- "combobox": COMBOBOX_STATES
584
+ "combobox": COMBOBOX_STATES,
585
+ "menu": MENU_STATES
298
586
  // Add more mappings as needed
299
587
  };
300
588
  var FluentContract = class {
@@ -325,30 +613,94 @@ var ContractBuilder = class {
325
613
  return this;
326
614
  }
327
615
  relationships(fn) {
616
+ const statePack = this.statePack;
617
+ const ctx = { capabilities: ["keyboard"] };
618
+ const resolveAllSetups = (stateName, visited = /* @__PURE__ */ new Set()) => {
619
+ if (visited.has(stateName)) return [];
620
+ visited.add(stateName);
621
+ const s = statePack[stateName];
622
+ if (!s) return [];
623
+ let actions = [];
624
+ if (Array.isArray(s.requires)) {
625
+ for (const req of s.requires) {
626
+ actions = actions.concat(resolveAllSetups(req, visited));
627
+ }
628
+ }
629
+ if (s.setup) actions = actions.concat(resolveSetup(s.setup, ctx));
630
+ return actions;
631
+ };
328
632
  const api = {
329
- ariaReference: (from, attribute, to) => ({
330
- required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required" }),
331
- optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" }),
332
- recommended: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "recommended" })
333
- }),
334
- contains: (parent, child) => ({
335
- required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required" }),
336
- optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" }),
337
- recommended: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "recommended" })
338
- })
633
+ ariaReference: (from, attribute, to) => {
634
+ return {
635
+ requires: (state) => {
636
+ const setupActions = resolveAllSetups(state, /* @__PURE__ */ new Set());
637
+ return {
638
+ required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required", setup: setupActions }),
639
+ optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional", setup: setupActions }),
640
+ recommended: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "recommended", setup: setupActions })
641
+ };
642
+ },
643
+ required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required" }),
644
+ optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" }),
645
+ recommended: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "recommended" })
646
+ };
647
+ },
648
+ contains: (parent, child) => {
649
+ return {
650
+ requires: (state) => {
651
+ const setupActions = resolveAllSetups(state, /* @__PURE__ */ new Set());
652
+ return {
653
+ required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required", setup: setupActions }),
654
+ optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional", setup: setupActions }),
655
+ recommended: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "recommended", setup: setupActions })
656
+ };
657
+ },
658
+ required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required" }),
659
+ optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" }),
660
+ recommended: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "recommended" })
661
+ };
662
+ }
339
663
  };
340
664
  fn(api);
341
665
  return this;
342
666
  }
343
667
  static(fn) {
344
668
  const api = {
345
- target: (target) => ({
346
- has: (attribute, expectedValue) => ({
347
- required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required" }),
348
- optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" }),
349
- recommended: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "recommended" })
350
- })
351
- })
669
+ target: (target) => {
670
+ return {
671
+ requires: (state) => {
672
+ const statePack = this.statePack;
673
+ const ctx = { capabilities: ["keyboard"] };
674
+ const resolveAllSetups = (stateName, visited = /* @__PURE__ */ new Set()) => {
675
+ if (visited.has(stateName)) return [];
676
+ visited.add(stateName);
677
+ const s = statePack[stateName];
678
+ if (!s) return [];
679
+ let actions = [];
680
+ if (Array.isArray(s.requires)) {
681
+ for (const req of s.requires) {
682
+ actions = actions.concat(resolveAllSetups(req, visited));
683
+ }
684
+ }
685
+ if (s.setup) actions = actions.concat(resolveSetup(s.setup, ctx));
686
+ return actions;
687
+ };
688
+ const setupActions = resolveAllSetups(state, /* @__PURE__ */ new Set());
689
+ return {
690
+ has: (attribute, expectedValue) => ({
691
+ required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required", setup: setupActions }),
692
+ optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional", setup: setupActions }),
693
+ recommended: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "recommended", setup: setupActions })
694
+ })
695
+ };
696
+ },
697
+ has: (attribute, expectedValue) => ({
698
+ required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required" }),
699
+ optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" }),
700
+ recommended: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "recommended" })
701
+ })
702
+ };
703
+ }
352
704
  };
353
705
  fn(api);
354
706
  return this;