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