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