aria-ease 6.7.0 → 6.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -10
- package/bin/AccordionComponentStrategy-4ZEIQ2V6.js +42 -0
- package/bin/ComboboxComponentStrategy-OGRVZXAF.js +64 -0
- package/bin/MenuComponentStrategy-JAMTCSNF.js +81 -0
- package/bin/TabsComponentStrategy-3SQURPMX.js +29 -0
- package/bin/buildContracts-GBOY7UXG.js +437 -0
- package/bin/{chunk-VPBHLMAS.js → chunk-LMSKLN5O.js} +21 -0
- package/bin/chunk-PK5L2SAF.js +17 -0
- package/bin/{chunk-2TOYEY5L.js → chunk-XERMSYEH.js} +12 -3
- package/bin/cli.cjs +991 -128
- package/bin/cli.js +33 -2
- package/bin/{configLoader-XRF6VM4J.js → configLoader-Q6A4JLKW.js} +1 -1
- package/{dist/contractTestRunnerPlaywright-UAOFNS7Z.js → bin/contractTestRunnerPlaywright-ZZNWDUYP.js} +270 -219
- package/bin/{test-WRIJHN6H.js → test-OND56UUL.js} +97 -10
- package/dist/AccordionComponentStrategy-4ZEIQ2V6.js +42 -0
- package/dist/ComboboxComponentStrategy-OGRVZXAF.js +64 -0
- package/dist/MenuComponentStrategy-JAMTCSNF.js +81 -0
- package/dist/TabsComponentStrategy-3SQURPMX.js +29 -0
- package/dist/chunk-PK5L2SAF.js +17 -0
- package/dist/{chunk-2TOYEY5L.js → chunk-XERMSYEH.js} +12 -3
- package/dist/{configLoader-IT4PWCJB.js → configLoader-WTGJAP4Z.js} +21 -0
- package/{bin/contractTestRunnerPlaywright-UAOFNS7Z.js → dist/contractTestRunnerPlaywright-XBWJZMR3.js} +270 -219
- package/dist/index.cjs +800 -96
- package/dist/index.d.cts +136 -1
- package/dist/index.d.ts +136 -1
- package/dist/index.js +421 -16
- package/dist/src/utils/test/AccordionComponentStrategy-WRHZOEN6.js +38 -0
- package/dist/src/utils/test/ComboboxComponentStrategy-5AECQSRN.js +60 -0
- package/dist/src/utils/test/MenuComponentStrategy-VKZQYLBE.js +77 -0
- package/dist/src/utils/test/TabsComponentStrategy-BKG53SEV.js +26 -0
- package/dist/src/utils/test/aria-contracts/accordion/accordion.contract.json +5 -11
- package/dist/src/utils/test/aria-contracts/combobox/combobox.listbox.contract.json +1 -1
- package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +1 -1
- package/dist/src/utils/test/{chunk-2TOYEY5L.js → chunk-XERMSYEH.js} +12 -3
- package/dist/src/utils/test/{configLoader-LD4RV2WQ.js → configLoader-YE2CYGDG.js} +21 -0
- package/dist/src/utils/test/{contractTestRunnerPlaywright-IRJOAEMT.js → contractTestRunnerPlaywright-LC5OAVXB.js} +262 -200
- package/dist/src/utils/test/index.cjs +472 -88
- package/dist/src/utils/test/index.js +97 -12
- package/package.json +7 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var playwright = require('playwright');
|
|
4
|
-
var
|
|
4
|
+
var path3 = require('path');
|
|
5
5
|
var fs3 = require('fs-extra');
|
|
6
6
|
var test = require('@playwright/test');
|
|
7
|
+
var url = require('url');
|
|
7
8
|
var fs = require('fs');
|
|
8
9
|
var chalk = require('chalk');
|
|
9
10
|
var readline = require('readline');
|
|
@@ -13,7 +14,7 @@ var fs$1 = require('fs/promises');
|
|
|
13
14
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
14
15
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
16
|
|
|
16
|
-
var
|
|
17
|
+
var path3__default = /*#__PURE__*/_interopDefault(path3);
|
|
17
18
|
var fs3__default = /*#__PURE__*/_interopDefault(fs3);
|
|
18
19
|
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
19
20
|
var readline__default = /*#__PURE__*/_interopDefault(readline);
|
|
@@ -69,11 +70,13 @@ var init_ContractReporter = __esm({
|
|
|
69
70
|
skipped = 0;
|
|
70
71
|
warnings = 0;
|
|
71
72
|
isPlaywright = false;
|
|
73
|
+
isCustomContract = false;
|
|
72
74
|
apgUrl = "https://www.w3.org/WAI/ARIA/apg/";
|
|
73
75
|
hasPrintedStaticSection = false;
|
|
74
76
|
hasPrintedDynamicSection = false;
|
|
75
|
-
constructor(isPlaywright = false) {
|
|
77
|
+
constructor(isPlaywright = false, isCustomContract = false) {
|
|
76
78
|
this.isPlaywright = isPlaywright;
|
|
79
|
+
this.isCustomContract = isCustomContract;
|
|
77
80
|
}
|
|
78
81
|
log(message) {
|
|
79
82
|
process.stderr.write(message + "\n");
|
|
@@ -234,6 +237,13 @@ ${"\u2500".repeat(60)}`);
|
|
|
234
237
|
const totalPasses = this.staticPasses + dynamicPasses;
|
|
235
238
|
const totalFailures = this.staticFailures + dynamicFailures;
|
|
236
239
|
const totalRun = totalPasses + totalFailures + this.warnings;
|
|
240
|
+
const getComponentMessage = () => {
|
|
241
|
+
const componentDisplayName = `${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)}`;
|
|
242
|
+
if (this.isCustomContract) {
|
|
243
|
+
return `${componentDisplayName} component validates against your custom accessibility policy \u2713`;
|
|
244
|
+
}
|
|
245
|
+
return `${componentDisplayName} component meets Aria-Ease baseline WAI-ARIA expectations \u2713`;
|
|
246
|
+
};
|
|
237
247
|
if (failures.length > 0) {
|
|
238
248
|
this.reportFailures(failures);
|
|
239
249
|
}
|
|
@@ -245,7 +255,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
245
255
|
`);
|
|
246
256
|
if (totalFailures === 0 && this.skipped === 0 && this.warnings === 0) {
|
|
247
257
|
this.log(`\u2705 All ${totalRun} tests passed!`);
|
|
248
|
-
this.log(` ${
|
|
258
|
+
this.log(` ${getComponentMessage()}`);
|
|
249
259
|
} else if (totalFailures === 0) {
|
|
250
260
|
this.log(`\u2705 ${totalPasses}/${totalRun} tests passed`);
|
|
251
261
|
if (this.skipped > 0) {
|
|
@@ -254,7 +264,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
254
264
|
if (this.warnings > 0) {
|
|
255
265
|
this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings > 1 ? "s" : ""}`);
|
|
256
266
|
}
|
|
257
|
-
this.log(` ${
|
|
267
|
+
this.log(` ${getComponentMessage()}`);
|
|
258
268
|
} else {
|
|
259
269
|
this.log(`\u274C ${totalFailures} test${totalFailures > 1 ? "s" : ""} failed`);
|
|
260
270
|
this.log(`\u2705 ${totalPasses} test${totalPasses > 1 ? "s" : ""} passed`);
|
|
@@ -449,6 +459,9 @@ function validateConfig(config) {
|
|
|
449
459
|
if (comp.path !== void 0 && typeof comp.path !== "string") {
|
|
450
460
|
errors.push(`test.components[${idx}].path must be a string when provided`);
|
|
451
461
|
}
|
|
462
|
+
if (comp.strategyPath !== void 0 && typeof comp.strategyPath !== "string") {
|
|
463
|
+
errors.push(`test.components[${idx}].strategyPath must be a string when provided`);
|
|
464
|
+
}
|
|
452
465
|
if (comp.strictness !== void 0 && !["minimal", "balanced", "strict", "paranoid"].includes(comp.strictness)) {
|
|
453
466
|
errors.push(`test.components[${idx}].strictness must be one of: minimal, balanced, strict, paranoid`);
|
|
454
467
|
}
|
|
@@ -463,11 +476,29 @@ function validateConfig(config) {
|
|
|
463
476
|
}
|
|
464
477
|
}
|
|
465
478
|
}
|
|
479
|
+
if (cfg.contracts !== void 0) {
|
|
480
|
+
if (!Array.isArray(cfg.contracts)) {
|
|
481
|
+
errors.push("contracts must be an array");
|
|
482
|
+
} else {
|
|
483
|
+
cfg.contracts.forEach((contract, idx) => {
|
|
484
|
+
if (typeof contract !== "object" || contract === null) {
|
|
485
|
+
errors.push(`contracts[${idx}] must be an object`);
|
|
486
|
+
} else {
|
|
487
|
+
if (typeof contract.src !== "string") {
|
|
488
|
+
errors.push(`contracts[${idx}].src is required and must be a string`);
|
|
489
|
+
}
|
|
490
|
+
if (contract.out !== void 0 && typeof contract.out !== "string") {
|
|
491
|
+
errors.push(`contracts[${idx}].out must be a string`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
466
497
|
return { valid: errors.length === 0, errors };
|
|
467
498
|
}
|
|
468
499
|
async function loadConfigFile(filePath) {
|
|
469
500
|
try {
|
|
470
|
-
const ext =
|
|
501
|
+
const ext = path3__default.default.extname(filePath);
|
|
471
502
|
if (ext === ".json") {
|
|
472
503
|
const content = await fs3__default.default.readFile(filePath, "utf-8");
|
|
473
504
|
return JSON.parse(content);
|
|
@@ -492,7 +523,7 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
492
523
|
let foundPath = null;
|
|
493
524
|
const errors = [];
|
|
494
525
|
for (const name of configNames) {
|
|
495
|
-
const configPath =
|
|
526
|
+
const configPath = path3__default.default.resolve(cwd, name);
|
|
496
527
|
if (await fs3__default.default.pathExists(configPath)) {
|
|
497
528
|
foundPath = configPath;
|
|
498
529
|
loadedConfig = await loadConfigFile(configPath);
|
|
@@ -520,10 +551,16 @@ var init_configLoader = __esm({
|
|
|
520
551
|
"src/utils/cli/configLoader.ts"() {
|
|
521
552
|
}
|
|
522
553
|
});
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
554
|
+
|
|
555
|
+
// src/utils/test/src/component-strategies/MenuComponentStrategy.ts
|
|
556
|
+
var MenuComponentStrategy_exports = {};
|
|
557
|
+
__export(MenuComponentStrategy_exports, {
|
|
558
|
+
MenuComponentStrategy: () => MenuComponentStrategy
|
|
559
|
+
});
|
|
560
|
+
var MenuComponentStrategy;
|
|
561
|
+
var init_MenuComponentStrategy = __esm({
|
|
562
|
+
"src/utils/test/src/component-strategies/MenuComponentStrategy.ts"() {
|
|
563
|
+
MenuComponentStrategy = class {
|
|
527
564
|
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
528
565
|
this.mainSelector = mainSelector;
|
|
529
566
|
this.selectors = selectors;
|
|
@@ -560,19 +597,36 @@ var init_ComboboxComponentStrategy = __esm({
|
|
|
560
597
|
}
|
|
561
598
|
if (!menuClosed) {
|
|
562
599
|
throw new Error(
|
|
563
|
-
`\u274C FATAL: Cannot close
|
|
600
|
+
`\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
|
|
564
601
|
1. Escape key
|
|
565
602
|
2. Clicking trigger
|
|
566
603
|
3. Clicking outside
|
|
567
|
-
This indicates a problem with the
|
|
604
|
+
This indicates a problem with the menu component's close functionality.`
|
|
568
605
|
);
|
|
569
606
|
}
|
|
570
607
|
if (this.selectors.input) {
|
|
571
608
|
await page.locator(this.selectors.input).first().clear();
|
|
572
609
|
}
|
|
610
|
+
if (this.selectors.trigger) {
|
|
611
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
612
|
+
await triggerElement.focus();
|
|
613
|
+
}
|
|
573
614
|
}
|
|
574
|
-
async shouldSkipTest() {
|
|
575
|
-
|
|
615
|
+
async shouldSkipTest(test, page) {
|
|
616
|
+
const requiresSubmenu = test.action.some(
|
|
617
|
+
(act) => act.target === "submenu" || act.target === "submenuTrigger" || act.target === "submenuItems"
|
|
618
|
+
) || test.assertions.some(
|
|
619
|
+
(assertion) => assertion.target === "submenu" || assertion.target === "submenuTrigger" || assertion.target === "submenuItems"
|
|
620
|
+
);
|
|
621
|
+
if (!requiresSubmenu) {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
const submenuTriggerSelector = this.selectors.submenuTrigger;
|
|
625
|
+
if (!submenuTriggerSelector) {
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
const submenuTriggerCount = await page.locator(submenuTriggerSelector).count();
|
|
629
|
+
return submenuTriggerCount === 0;
|
|
576
630
|
}
|
|
577
631
|
getMainSelector() {
|
|
578
632
|
return this.mainSelector;
|
|
@@ -580,6 +634,12 @@ This indicates a problem with the combobox component's close functionality.`
|
|
|
580
634
|
};
|
|
581
635
|
}
|
|
582
636
|
});
|
|
637
|
+
|
|
638
|
+
// src/utils/test/src/component-strategies/AccordionComponentStrategy.ts
|
|
639
|
+
var AccordionComponentStrategy_exports = {};
|
|
640
|
+
__export(AccordionComponentStrategy_exports, {
|
|
641
|
+
AccordionComponentStrategy: () => AccordionComponentStrategy
|
|
642
|
+
});
|
|
583
643
|
var AccordionComponentStrategy;
|
|
584
644
|
var init_AccordionComponentStrategy = __esm({
|
|
585
645
|
"src/utils/test/src/component-strategies/AccordionComponentStrategy.ts"() {
|
|
@@ -618,10 +678,16 @@ var init_AccordionComponentStrategy = __esm({
|
|
|
618
678
|
};
|
|
619
679
|
}
|
|
620
680
|
});
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
681
|
+
|
|
682
|
+
// src/utils/test/src/component-strategies/ComboboxComponentStrategy.ts
|
|
683
|
+
var ComboboxComponentStrategy_exports = {};
|
|
684
|
+
__export(ComboboxComponentStrategy_exports, {
|
|
685
|
+
ComboboxComponentStrategy: () => ComboboxComponentStrategy
|
|
686
|
+
});
|
|
687
|
+
var ComboboxComponentStrategy;
|
|
688
|
+
var init_ComboboxComponentStrategy = __esm({
|
|
689
|
+
"src/utils/test/src/component-strategies/ComboboxComponentStrategy.ts"() {
|
|
690
|
+
ComboboxComponentStrategy = class {
|
|
625
691
|
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
626
692
|
this.mainSelector = mainSelector;
|
|
627
693
|
this.selectors = selectors;
|
|
@@ -634,60 +700,43 @@ var init_MenuComponentStrategy = __esm({
|
|
|
634
700
|
const popupElement = page.locator(popupSelector).first();
|
|
635
701
|
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
636
702
|
if (!isPopupVisible) return;
|
|
637
|
-
let
|
|
703
|
+
let listBoxClosed = false;
|
|
638
704
|
let closeSelector = this.selectors.input;
|
|
639
705
|
if (!closeSelector && this.selectors.focusable) {
|
|
640
706
|
closeSelector = this.selectors.focusable;
|
|
641
707
|
} else if (!closeSelector) {
|
|
642
|
-
closeSelector = this.selectors.
|
|
708
|
+
closeSelector = this.selectors.button;
|
|
643
709
|
}
|
|
644
710
|
if (closeSelector) {
|
|
645
711
|
const closeElement = page.locator(closeSelector).first();
|
|
646
712
|
await closeElement.focus();
|
|
647
713
|
await page.keyboard.press("Escape");
|
|
648
|
-
|
|
714
|
+
listBoxClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
649
715
|
}
|
|
650
|
-
if (!
|
|
651
|
-
const
|
|
652
|
-
await
|
|
653
|
-
|
|
716
|
+
if (!listBoxClosed && this.selectors.button) {
|
|
717
|
+
const buttonElement = page.locator(this.selectors.button).first();
|
|
718
|
+
await buttonElement.click({ timeout: this.actionTimeoutMs });
|
|
719
|
+
listBoxClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
654
720
|
}
|
|
655
|
-
if (!
|
|
721
|
+
if (!listBoxClosed) {
|
|
656
722
|
await page.mouse.click(10, 10);
|
|
657
|
-
|
|
723
|
+
listBoxClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
658
724
|
}
|
|
659
|
-
if (!
|
|
725
|
+
if (!listBoxClosed) {
|
|
660
726
|
throw new Error(
|
|
661
|
-
`\u274C FATAL: Cannot close
|
|
727
|
+
`\u274C FATAL: Cannot close combobox popup between tests. Popup remains visible after trying:
|
|
662
728
|
1. Escape key
|
|
663
|
-
2. Clicking
|
|
729
|
+
2. Clicking button
|
|
664
730
|
3. Clicking outside
|
|
665
|
-
This indicates a problem with the
|
|
731
|
+
This indicates a problem with the combobox component's close functionality.`
|
|
666
732
|
);
|
|
667
733
|
}
|
|
668
734
|
if (this.selectors.input) {
|
|
669
735
|
await page.locator(this.selectors.input).first().clear();
|
|
670
736
|
}
|
|
671
|
-
if (this.selectors.trigger) {
|
|
672
|
-
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
673
|
-
await triggerElement.focus();
|
|
674
|
-
}
|
|
675
737
|
}
|
|
676
|
-
async shouldSkipTest(
|
|
677
|
-
|
|
678
|
-
(act) => act.target === "submenu" || act.target === "submenuTrigger" || act.target === "submenuItems"
|
|
679
|
-
) || test.assertions.some(
|
|
680
|
-
(assertion) => assertion.target === "submenu" || assertion.target === "submenuTrigger" || assertion.target === "submenuItems"
|
|
681
|
-
);
|
|
682
|
-
if (!requiresSubmenu) {
|
|
683
|
-
return false;
|
|
684
|
-
}
|
|
685
|
-
const submenuTriggerSelector = this.selectors.submenuTrigger;
|
|
686
|
-
if (!submenuTriggerSelector) {
|
|
687
|
-
return true;
|
|
688
|
-
}
|
|
689
|
-
const submenuTriggerCount = await page.locator(submenuTriggerSelector).count();
|
|
690
|
-
return submenuTriggerCount === 0;
|
|
738
|
+
async shouldSkipTest() {
|
|
739
|
+
return false;
|
|
691
740
|
}
|
|
692
741
|
getMainSelector() {
|
|
693
742
|
return this.mainSelector;
|
|
@@ -697,6 +746,10 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
697
746
|
});
|
|
698
747
|
|
|
699
748
|
// src/utils/test/src/component-strategies/TabsComponentStrategy.ts
|
|
749
|
+
var TabsComponentStrategy_exports = {};
|
|
750
|
+
__export(TabsComponentStrategy_exports, {
|
|
751
|
+
TabsComponentStrategy: () => TabsComponentStrategy
|
|
752
|
+
});
|
|
700
753
|
var TabsComponentStrategy;
|
|
701
754
|
var init_TabsComponentStrategy = __esm({
|
|
702
755
|
"src/utils/test/src/component-strategies/TabsComponentStrategy.ts"() {
|
|
@@ -725,42 +778,163 @@ var init_TabsComponentStrategy = __esm({
|
|
|
725
778
|
};
|
|
726
779
|
}
|
|
727
780
|
});
|
|
781
|
+
var StrategyRegistry;
|
|
782
|
+
var init_StrategyRegistry = __esm({
|
|
783
|
+
"src/utils/test/src/StrategyRegistry.ts"() {
|
|
784
|
+
StrategyRegistry = class {
|
|
785
|
+
builtInStrategies = /* @__PURE__ */ new Map();
|
|
786
|
+
constructor() {
|
|
787
|
+
this.registerBuiltInStrategies();
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Register built-in strategies
|
|
791
|
+
*/
|
|
792
|
+
registerBuiltInStrategies() {
|
|
793
|
+
this.builtInStrategies.set(
|
|
794
|
+
"menu",
|
|
795
|
+
() => Promise.resolve().then(() => (init_MenuComponentStrategy(), MenuComponentStrategy_exports)).then(
|
|
796
|
+
(m) => m.MenuComponentStrategy
|
|
797
|
+
)
|
|
798
|
+
);
|
|
799
|
+
this.builtInStrategies.set(
|
|
800
|
+
"accordion",
|
|
801
|
+
() => Promise.resolve().then(() => (init_AccordionComponentStrategy(), AccordionComponentStrategy_exports)).then(
|
|
802
|
+
(m) => m.AccordionComponentStrategy
|
|
803
|
+
)
|
|
804
|
+
);
|
|
805
|
+
this.builtInStrategies.set(
|
|
806
|
+
"combobox",
|
|
807
|
+
() => Promise.resolve().then(() => (init_ComboboxComponentStrategy(), ComboboxComponentStrategy_exports)).then(
|
|
808
|
+
(m) => m.ComboboxComponentStrategy
|
|
809
|
+
)
|
|
810
|
+
);
|
|
811
|
+
this.builtInStrategies.set(
|
|
812
|
+
"tabs",
|
|
813
|
+
() => Promise.resolve().then(() => (init_TabsComponentStrategy(), TabsComponentStrategy_exports)).then(
|
|
814
|
+
(m) => m.TabsComponentStrategy
|
|
815
|
+
)
|
|
816
|
+
);
|
|
817
|
+
this.builtInStrategies.set(
|
|
818
|
+
"combobox.listbox",
|
|
819
|
+
() => Promise.resolve().then(() => (init_ComboboxComponentStrategy(), ComboboxComponentStrategy_exports)).then(
|
|
820
|
+
(m) => m.ComboboxComponentStrategy
|
|
821
|
+
)
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Load a strategy - either from custom path or built-in registry
|
|
826
|
+
* @param componentName - Component name (e.g., "menu", "accordion")
|
|
827
|
+
* @param customStrategyPath - Optional custom strategy file path
|
|
828
|
+
* @returns Strategy constructor function or null if not found
|
|
829
|
+
*/
|
|
830
|
+
async loadStrategy(componentName, customStrategyPath, configBaseDir) {
|
|
831
|
+
try {
|
|
832
|
+
if (customStrategyPath) {
|
|
833
|
+
try {
|
|
834
|
+
const resolvedCustomPath = path3__default.default.isAbsolute(customStrategyPath) ? customStrategyPath : path3__default.default.resolve(configBaseDir || process.cwd(), customStrategyPath);
|
|
835
|
+
const customModule = await import(url.pathToFileURL(resolvedCustomPath).href);
|
|
836
|
+
const strategy = customModule.default || customModule;
|
|
837
|
+
if (!strategy) {
|
|
838
|
+
throw new Error(`No default export found in ${customStrategyPath}`);
|
|
839
|
+
}
|
|
840
|
+
return strategy;
|
|
841
|
+
} catch (error) {
|
|
842
|
+
throw new Error(
|
|
843
|
+
`Failed to load custom strategy from ${customStrategyPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const builtInLoader = this.builtInStrategies.get(componentName);
|
|
848
|
+
if (!builtInLoader) {
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
return builtInLoader();
|
|
852
|
+
} catch (error) {
|
|
853
|
+
throw new Error(
|
|
854
|
+
`Strategy loading failed for ${componentName}: ${error instanceof Error ? error.message : String(error)}`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Check if a strategy exists (either built-in or custom path provided)
|
|
860
|
+
*/
|
|
861
|
+
has(componentName, customStrategyPath) {
|
|
862
|
+
return !!customStrategyPath || this.builtInStrategies.has(componentName);
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
});
|
|
728
867
|
var ComponentDetector;
|
|
729
868
|
var init_ComponentDetector = __esm({
|
|
730
869
|
"src/utils/test/src/ComponentDetector.ts"() {
|
|
731
|
-
init_ComboboxComponentStrategy();
|
|
732
|
-
init_AccordionComponentStrategy();
|
|
733
|
-
init_MenuComponentStrategy();
|
|
734
|
-
init_TabsComponentStrategy();
|
|
735
870
|
init_contract();
|
|
871
|
+
init_StrategyRegistry();
|
|
736
872
|
ComponentDetector = class {
|
|
737
|
-
static
|
|
738
|
-
|
|
739
|
-
|
|
873
|
+
static strategyRegistry = new StrategyRegistry();
|
|
874
|
+
static isComponentConfig(value) {
|
|
875
|
+
return typeof value === "object" && value !== null;
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Detect and instantiate a component strategy
|
|
879
|
+
* Supports:
|
|
880
|
+
* - Built-in strategies (menu, accordion, combobox, tabs)
|
|
881
|
+
* - Custom strategies via config (strategyPath)
|
|
882
|
+
* - Custom contract paths via config (path)
|
|
883
|
+
* @param componentName - Component name
|
|
884
|
+
* @param componentConfig - Component config from ariaease.config.js
|
|
885
|
+
* @param actionTimeoutMs - Action timeout in milliseconds
|
|
886
|
+
* @param assertionTimeoutMs - Assertion timeout in milliseconds
|
|
887
|
+
* @returns Instantiated ComponentStrategy or null
|
|
888
|
+
*/
|
|
889
|
+
static async detect(componentName, componentConfig, actionTimeoutMs = 400, assertionTimeoutMs = 400, configBaseDir) {
|
|
890
|
+
const typedComponentConfig = this.isComponentConfig(componentConfig) ? componentConfig : void 0;
|
|
891
|
+
let contractPath = typedComponentConfig?.path;
|
|
892
|
+
if (!contractPath) {
|
|
893
|
+
const contractTyped = contract_default;
|
|
894
|
+
contractPath = contractTyped[componentName]?.path;
|
|
895
|
+
}
|
|
740
896
|
if (!contractPath) {
|
|
741
897
|
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
742
898
|
}
|
|
743
|
-
const resolvedPath =
|
|
899
|
+
const resolvedPath = (() => {
|
|
900
|
+
if (path3__default.default.isAbsolute(contractPath)) return contractPath;
|
|
901
|
+
if (configBaseDir) {
|
|
902
|
+
const configResolved = path3__default.default.resolve(configBaseDir, contractPath);
|
|
903
|
+
try {
|
|
904
|
+
fs.readFileSync(configResolved, "utf-8");
|
|
905
|
+
return configResolved;
|
|
906
|
+
} catch {
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
const cwdResolved = path3__default.default.resolve(process.cwd(), contractPath);
|
|
910
|
+
try {
|
|
911
|
+
fs.readFileSync(cwdResolved, "utf-8");
|
|
912
|
+
return cwdResolved;
|
|
913
|
+
} catch {
|
|
914
|
+
return new URL(contractPath, (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).pathname;
|
|
915
|
+
}
|
|
916
|
+
})();
|
|
744
917
|
const contractData = fs.readFileSync(resolvedPath, "utf-8");
|
|
745
918
|
const componentContract = JSON.parse(contractData);
|
|
746
919
|
const selectors = componentContract.selectors;
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
return
|
|
754
|
-
}
|
|
755
|
-
if (componentName === "menu") {
|
|
756
|
-
const mainSelector = selectors.trigger || selectors.container;
|
|
757
|
-
return new MenuComponentStrategy(mainSelector, selectors, actionTimeoutMs, assertionTimeoutMs);
|
|
920
|
+
const strategyClass = await this.strategyRegistry.loadStrategy(
|
|
921
|
+
componentName,
|
|
922
|
+
typedComponentConfig?.strategyPath,
|
|
923
|
+
configBaseDir
|
|
924
|
+
);
|
|
925
|
+
if (!strategyClass) {
|
|
926
|
+
return null;
|
|
758
927
|
}
|
|
928
|
+
const mainSelector = selectors.trigger || selectors.input || selectors.tablist || selectors.container;
|
|
759
929
|
if (componentName === "tabs") {
|
|
760
|
-
|
|
761
|
-
return new TabsComponentStrategy(mainSelector, selectors);
|
|
930
|
+
return new strategyClass(mainSelector, selectors);
|
|
762
931
|
}
|
|
763
|
-
return
|
|
932
|
+
return new strategyClass(
|
|
933
|
+
mainSelector,
|
|
934
|
+
selectors,
|
|
935
|
+
actionTimeoutMs,
|
|
936
|
+
assertionTimeoutMs
|
|
937
|
+
);
|
|
764
938
|
}
|
|
765
939
|
};
|
|
766
940
|
}
|
|
@@ -1265,17 +1439,42 @@ var contractTestRunnerPlaywright_exports = {};
|
|
|
1265
1439
|
__export(contractTestRunnerPlaywright_exports, {
|
|
1266
1440
|
runContractTestsPlaywright: () => runContractTestsPlaywright
|
|
1267
1441
|
});
|
|
1268
|
-
async function runContractTestsPlaywright(componentName, url, strictness) {
|
|
1269
|
-
const
|
|
1442
|
+
async function runContractTestsPlaywright(componentName, url, strictness, config, configBaseDir) {
|
|
1443
|
+
const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
|
|
1444
|
+
const isCustomContract = !!componentConfig?.path;
|
|
1445
|
+
const reporter = new ContractReporter(true, isCustomContract);
|
|
1270
1446
|
const actionTimeoutMs = 400;
|
|
1271
1447
|
const assertionTimeoutMs = 400;
|
|
1272
1448
|
const strictnessMode = normalizeStrictness(strictness);
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1449
|
+
let contractPath = componentConfig?.path;
|
|
1450
|
+
if (!contractPath) {
|
|
1451
|
+
const contractTyped = contract_default;
|
|
1452
|
+
contractPath = contractTyped[componentName]?.path;
|
|
1453
|
+
}
|
|
1454
|
+
if (!contractPath) {
|
|
1455
|
+
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
1456
|
+
}
|
|
1457
|
+
const resolvedPath = (() => {
|
|
1458
|
+
if (path3__default.default.isAbsolute(contractPath)) return contractPath;
|
|
1459
|
+
if (configBaseDir) {
|
|
1460
|
+
const configResolved = path3__default.default.resolve(configBaseDir, contractPath);
|
|
1461
|
+
try {
|
|
1462
|
+
fs.readFileSync(configResolved, "utf-8");
|
|
1463
|
+
return configResolved;
|
|
1464
|
+
} catch {
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
const cwdResolved = path3__default.default.resolve(process.cwd(), contractPath);
|
|
1468
|
+
try {
|
|
1469
|
+
fs.readFileSync(cwdResolved, "utf-8");
|
|
1470
|
+
return cwdResolved;
|
|
1471
|
+
} catch {
|
|
1472
|
+
return new URL(contractPath, (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).pathname;
|
|
1473
|
+
}
|
|
1474
|
+
})();
|
|
1276
1475
|
const contractData = fs.readFileSync(resolvedPath, "utf-8");
|
|
1277
1476
|
const componentContract = JSON.parse(contractData);
|
|
1278
|
-
const totalTests = componentContract.static[0]
|
|
1477
|
+
const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
|
|
1279
1478
|
const apgUrl = componentContract.meta?.source?.apg;
|
|
1280
1479
|
const failures = [];
|
|
1281
1480
|
const warnings = [];
|
|
@@ -1312,7 +1511,7 @@ async function runContractTestsPlaywright(componentName, url, strictness) {
|
|
|
1312
1511
|
}
|
|
1313
1512
|
await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
|
|
1314
1513
|
}
|
|
1315
|
-
const strategy = ComponentDetector.detect(componentName, actionTimeoutMs, assertionTimeoutMs);
|
|
1514
|
+
const strategy = await ComponentDetector.detect(componentName, componentConfig, actionTimeoutMs, assertionTimeoutMs, configBaseDir);
|
|
1316
1515
|
if (!strategy) {
|
|
1317
1516
|
throw new Error(`Unsupported component: ${componentName}`);
|
|
1318
1517
|
}
|
|
@@ -1345,6 +1544,105 @@ This usually means:
|
|
|
1345
1544
|
let staticPassed = 0;
|
|
1346
1545
|
let staticFailed = 0;
|
|
1347
1546
|
let staticWarnings = 0;
|
|
1547
|
+
for (const rel of componentContract.relationships || []) {
|
|
1548
|
+
const relationshipLevel = normalizeLevel(rel.level);
|
|
1549
|
+
if (rel.type === "aria-reference") {
|
|
1550
|
+
const relDescription = `${rel.from}.${rel.attribute} references ${rel.to}`;
|
|
1551
|
+
const fromSelector = componentContract.selectors[rel.from];
|
|
1552
|
+
const toSelector = componentContract.selectors[rel.to];
|
|
1553
|
+
if (!fromSelector || !toSelector) {
|
|
1554
|
+
const outcome = classifyFailure(
|
|
1555
|
+
`Relationship selector missing: from="${rel.from}" or to="${rel.to}" not found in selectors.`,
|
|
1556
|
+
rel.level
|
|
1557
|
+
);
|
|
1558
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1559
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1560
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1563
|
+
const fromTarget = page.locator(fromSelector).first();
|
|
1564
|
+
const toTarget = page.locator(toSelector).first();
|
|
1565
|
+
const fromExists = await fromTarget.count() > 0;
|
|
1566
|
+
const toExists = await toTarget.count() > 0;
|
|
1567
|
+
if (!fromExists || !toExists) {
|
|
1568
|
+
const outcome = classifyFailure(
|
|
1569
|
+
`Relationship target not found: ${!fromExists ? rel.from : rel.to}.`,
|
|
1570
|
+
rel.level
|
|
1571
|
+
);
|
|
1572
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1573
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1574
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
const attrValue = await fromTarget.getAttribute(rel.attribute);
|
|
1578
|
+
const toId = await toTarget.getAttribute("id");
|
|
1579
|
+
if (!toId) {
|
|
1580
|
+
const outcome = classifyFailure(
|
|
1581
|
+
`Relationship target "${rel.to}" must have an id for ${rel.attribute} validation.`,
|
|
1582
|
+
rel.level
|
|
1583
|
+
);
|
|
1584
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1585
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1586
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1589
|
+
const references = (attrValue || "").split(/\s+/).filter(Boolean);
|
|
1590
|
+
const matches = references.includes(toId);
|
|
1591
|
+
if (!matches) {
|
|
1592
|
+
const outcome = classifyFailure(
|
|
1593
|
+
`Expected ${rel.from} ${rel.attribute} to reference id "${toId}", found "${attrValue || ""}".`,
|
|
1594
|
+
rel.level
|
|
1595
|
+
);
|
|
1596
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1597
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1598
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
passes.push(`Relationship valid: ${rel.from}.${rel.attribute} -> ${rel.to} (id=${toId}).`);
|
|
1602
|
+
staticPassed += 1;
|
|
1603
|
+
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
if (rel.type === "contains") {
|
|
1607
|
+
const relDescription = `${rel.parent} contains ${rel.child}`;
|
|
1608
|
+
const parentSelector = componentContract.selectors[rel.parent];
|
|
1609
|
+
const childSelector = componentContract.selectors[rel.child];
|
|
1610
|
+
if (!parentSelector || !childSelector) {
|
|
1611
|
+
const outcome = classifyFailure(
|
|
1612
|
+
`Relationship selector missing: parent="${rel.parent}" or child="${rel.child}" not found in selectors.`,
|
|
1613
|
+
rel.level
|
|
1614
|
+
);
|
|
1615
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1616
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1617
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
const parent = page.locator(parentSelector).first();
|
|
1621
|
+
const parentExists = await parent.count() > 0;
|
|
1622
|
+
if (!parentExists) {
|
|
1623
|
+
const outcome = classifyFailure(`Relationship parent target not found: ${rel.parent}.`, rel.level);
|
|
1624
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1625
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1626
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
const descendants = parent.locator(childSelector);
|
|
1630
|
+
const descendantCount = await descendants.count();
|
|
1631
|
+
if (descendantCount < 1) {
|
|
1632
|
+
const outcome = classifyFailure(
|
|
1633
|
+
`Expected ${rel.parent} to contain descendant matching selector for ${rel.child}.`,
|
|
1634
|
+
rel.level
|
|
1635
|
+
);
|
|
1636
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1637
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1638
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
passes.push(`Relationship valid: ${rel.parent} contains ${rel.child}.`);
|
|
1642
|
+
staticPassed += 1;
|
|
1643
|
+
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1348
1646
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
1349
1647
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
1350
1648
|
if (test.target === "relative") continue;
|
|
@@ -1611,7 +1909,7 @@ function displayBadgeInfo(badgeType) {
|
|
|
1611
1909
|
console.log(chalk__default.default.dim("\n This helps others discover accessibility tools and shows you care!\n"));
|
|
1612
1910
|
}
|
|
1613
1911
|
async function promptAddBadge(badgeType, cwd = process.cwd()) {
|
|
1614
|
-
const readmePath =
|
|
1912
|
+
const readmePath = path3__default.default.join(cwd, "README.md");
|
|
1615
1913
|
const readmeExists = await fs3__default.default.pathExists(readmePath);
|
|
1616
1914
|
if (!readmeExists) {
|
|
1617
1915
|
console.log(chalk__default.default.yellow(" \u2139\uFE0F No README.md found in current directory"));
|
|
@@ -1714,7 +2012,7 @@ async function runContractTests(componentName, component, strictness) {
|
|
|
1714
2012
|
const resolvedPath = new URL(contractPath, (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).pathname;
|
|
1715
2013
|
const contractData = await fs__default.default.readFile(resolvedPath, "utf-8");
|
|
1716
2014
|
const componentContract = JSON.parse(contractData);
|
|
1717
|
-
const totalTests = componentContract.static[0]
|
|
2015
|
+
const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
|
|
1718
2016
|
reporter.start(componentName, totalTests);
|
|
1719
2017
|
const failures = [];
|
|
1720
2018
|
const passes = [];
|
|
@@ -1738,6 +2036,82 @@ async function runContractTests(componentName, component, strictness) {
|
|
|
1738
2036
|
let staticPassed = 0;
|
|
1739
2037
|
let staticFailed = 0;
|
|
1740
2038
|
let staticWarnings = 0;
|
|
2039
|
+
for (const rel of componentContract.relationships || []) {
|
|
2040
|
+
const relationshipLevel = normalizeLevel(rel.level);
|
|
2041
|
+
if (rel.type === "aria-reference") {
|
|
2042
|
+
const fromSelector = componentContract.selectors[rel.from];
|
|
2043
|
+
const toSelector = componentContract.selectors[rel.to];
|
|
2044
|
+
const relDescription = `${rel.from}.${rel.attribute} references ${rel.to}`;
|
|
2045
|
+
if (!fromSelector || !toSelector) {
|
|
2046
|
+
const outcome = classifyFailure(`Relationship selector missing: from="${rel.from}" or to="${rel.to}" not found in selectors.`, rel.level);
|
|
2047
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
2048
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
2049
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2050
|
+
continue;
|
|
2051
|
+
}
|
|
2052
|
+
const fromTarget = component.querySelector(fromSelector);
|
|
2053
|
+
const toTarget = component.querySelector(toSelector);
|
|
2054
|
+
if (!fromTarget || !toTarget) {
|
|
2055
|
+
const outcome = classifyFailure(`Relationship target not found: ${!fromTarget ? rel.from : rel.to}.`, rel.level);
|
|
2056
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
2057
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
2058
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2059
|
+
continue;
|
|
2060
|
+
}
|
|
2061
|
+
const toId = toTarget.getAttribute("id");
|
|
2062
|
+
const attrValue = fromTarget.getAttribute(rel.attribute) || "";
|
|
2063
|
+
if (!toId) {
|
|
2064
|
+
const outcome = classifyFailure(`Relationship target "${rel.to}" must have an id for ${rel.attribute} validation.`, rel.level);
|
|
2065
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
2066
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
2067
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
const references = attrValue.split(/\s+/).filter(Boolean);
|
|
2071
|
+
if (!references.includes(toId)) {
|
|
2072
|
+
const outcome = classifyFailure(`Expected ${rel.from} ${rel.attribute} to reference id "${toId}", found "${attrValue}".`, rel.level);
|
|
2073
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
2074
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
2075
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2076
|
+
continue;
|
|
2077
|
+
}
|
|
2078
|
+
passes.push(`Relationship valid: ${rel.from}.${rel.attribute} -> ${rel.to} (id=${toId}).`);
|
|
2079
|
+
staticPassed += 1;
|
|
2080
|
+
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
if (rel.type === "contains") {
|
|
2084
|
+
const parentSelector = componentContract.selectors[rel.parent];
|
|
2085
|
+
const childSelector = componentContract.selectors[rel.child];
|
|
2086
|
+
const relDescription = `${rel.parent} contains ${rel.child}`;
|
|
2087
|
+
if (!parentSelector || !childSelector) {
|
|
2088
|
+
const outcome = classifyFailure(`Relationship selector missing: parent="${rel.parent}" or child="${rel.child}" not found in selectors.`, rel.level);
|
|
2089
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
2090
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
2091
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2092
|
+
continue;
|
|
2093
|
+
}
|
|
2094
|
+
const parentTarget = component.querySelector(parentSelector);
|
|
2095
|
+
if (!parentTarget) {
|
|
2096
|
+
const outcome = classifyFailure(`Relationship parent target not found: ${rel.parent}.`, rel.level);
|
|
2097
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
2098
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
2099
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2100
|
+
continue;
|
|
2101
|
+
}
|
|
2102
|
+
const nestedChild = parentTarget.querySelector(childSelector);
|
|
2103
|
+
if (!nestedChild) {
|
|
2104
|
+
const outcome = classifyFailure(`Expected ${rel.parent} to contain descendant matching selector for ${rel.child}.`, rel.level);
|
|
2105
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
2106
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
2107
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2108
|
+
continue;
|
|
2109
|
+
}
|
|
2110
|
+
passes.push(`Relationship valid: ${rel.parent} contains ${rel.child}.`);
|
|
2111
|
+
staticPassed += 1;
|
|
2112
|
+
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
1741
2115
|
for (const test of componentContract.static[0].assertions) {
|
|
1742
2116
|
if (test.target !== "relative") {
|
|
1743
2117
|
const staticLevel = normalizeLevel(test.level);
|
|
@@ -1833,14 +2207,24 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
1833
2207
|
return null;
|
|
1834
2208
|
}
|
|
1835
2209
|
let strictness = normalizeStrictness(options.strictness);
|
|
1836
|
-
|
|
2210
|
+
let config = {};
|
|
2211
|
+
let configBaseDir = typeof process !== "undefined" ? process.cwd() : "";
|
|
2212
|
+
if (typeof process !== "undefined" && typeof process.cwd === "function") {
|
|
1837
2213
|
try {
|
|
1838
2214
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_configLoader(), configLoader_exports));
|
|
1839
|
-
const
|
|
1840
|
-
|
|
1841
|
-
|
|
2215
|
+
const result2 = await loadConfig2(process.cwd());
|
|
2216
|
+
config = result2.config;
|
|
2217
|
+
if (result2.configPath) {
|
|
2218
|
+
configBaseDir = path3__default.default.dirname(result2.configPath);
|
|
2219
|
+
}
|
|
2220
|
+
if (options.strictness === void 0) {
|
|
2221
|
+
const componentStrictness = config.test?.components?.find((comp) => comp?.name === componentName)?.strictness;
|
|
2222
|
+
strictness = normalizeStrictness(componentStrictness ?? config.test?.strictness);
|
|
2223
|
+
}
|
|
1842
2224
|
} catch {
|
|
1843
|
-
strictness
|
|
2225
|
+
if (options.strictness === void 0) {
|
|
2226
|
+
strictness = "balanced";
|
|
2227
|
+
}
|
|
1844
2228
|
}
|
|
1845
2229
|
}
|
|
1846
2230
|
let contract;
|
|
@@ -1850,7 +2234,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
1850
2234
|
if (devServerUrl) {
|
|
1851
2235
|
console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
|
|
1852
2236
|
const { runContractTestsPlaywright: runContractTestsPlaywright2 } = await Promise.resolve().then(() => (init_contractTestRunnerPlaywright(), contractTestRunnerPlaywright_exports));
|
|
1853
|
-
contract = await runContractTestsPlaywright2(componentName, devServerUrl, strictness);
|
|
2237
|
+
contract = await runContractTestsPlaywright2(componentName, devServerUrl, strictness, config, configBaseDir);
|
|
1854
2238
|
} else {
|
|
1855
2239
|
throw new Error(
|
|
1856
2240
|
`\u274C Dev server not running at ${url}
|