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
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ContractReporter, normalizeStrictness, createTestPage, normalizeLevel, resolveEnforcement } from './chunk-4DU5Z5BR.js';
|
|
2
|
+
import { RelativeTargetResolver } from './chunk-GLT43UVH.js';
|
|
2
3
|
import { readFileSync } from 'fs';
|
|
3
4
|
import path2 from 'path';
|
|
4
5
|
import { pathToFileURL } from 'url';
|
|
@@ -27,7 +28,7 @@ var StrategyRegistry = class {
|
|
|
27
28
|
);
|
|
28
29
|
this.builtInStrategies.set(
|
|
29
30
|
"combobox",
|
|
30
|
-
() => import('./ComboboxComponentStrategy-
|
|
31
|
+
() => import('./ComboboxComponentStrategy-XKQ72RFD.js').then(
|
|
31
32
|
(m) => m.ComboboxComponentStrategy
|
|
32
33
|
)
|
|
33
34
|
);
|
|
@@ -37,12 +38,6 @@ var StrategyRegistry = class {
|
|
|
37
38
|
(m) => m.TabsComponentStrategy
|
|
38
39
|
)
|
|
39
40
|
);
|
|
40
|
-
this.builtInStrategies.set(
|
|
41
|
-
"combobox.listbox",
|
|
42
|
-
() => import('./ComboboxComponentStrategy-5AECQSRN.js').then(
|
|
43
|
-
(m) => m.ComboboxComponentStrategy
|
|
44
|
-
)
|
|
45
|
-
);
|
|
46
41
|
}
|
|
47
42
|
/**
|
|
48
43
|
* Load a strategy - either from custom path or built-in registry
|
|
@@ -106,11 +101,7 @@ var ComponentDetector = class {
|
|
|
106
101
|
*/
|
|
107
102
|
static async detect(componentName, componentConfig, actionTimeoutMs = 400, assertionTimeoutMs = 400, configBaseDir) {
|
|
108
103
|
const typedComponentConfig = this.isComponentConfig(componentConfig) ? componentConfig : void 0;
|
|
109
|
-
|
|
110
|
-
if (!contractPath) {
|
|
111
|
-
const contractTyped = contract_default;
|
|
112
|
-
contractPath = contractTyped[componentName]?.path;
|
|
113
|
-
}
|
|
104
|
+
const contractPath = typedComponentConfig?.contractPath;
|
|
114
105
|
if (!contractPath) {
|
|
115
106
|
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
116
107
|
}
|
|
@@ -143,7 +134,7 @@ var ComponentDetector = class {
|
|
|
143
134
|
if (!strategyClass) {
|
|
144
135
|
return null;
|
|
145
136
|
}
|
|
146
|
-
const mainSelector = selectors.
|
|
137
|
+
const mainSelector = selectors.main;
|
|
147
138
|
if (componentName === "tabs") {
|
|
148
139
|
return new strategyClass(mainSelector, selectors);
|
|
149
140
|
}
|
|
@@ -156,46 +147,6 @@ var ComponentDetector = class {
|
|
|
156
147
|
}
|
|
157
148
|
};
|
|
158
149
|
|
|
159
|
-
// src/utils/test/src/RelativeTargetResolver.ts
|
|
160
|
-
var RelativeTargetResolver = class {
|
|
161
|
-
/**
|
|
162
|
-
* Resolve a relative target like "first", "second", "last", "next", "previous"
|
|
163
|
-
* @param page Playwright page instance
|
|
164
|
-
* @param selector Base selector to find elements
|
|
165
|
-
* @param relative Relative position (first, second, last, next, previous)
|
|
166
|
-
* @returns The resolved Locator or null if not found
|
|
167
|
-
*/
|
|
168
|
-
static async resolve(page, selector, relative) {
|
|
169
|
-
const items = await page.locator(selector).all();
|
|
170
|
-
switch (relative) {
|
|
171
|
-
case "first":
|
|
172
|
-
return items[0];
|
|
173
|
-
case "second":
|
|
174
|
-
return items[1];
|
|
175
|
-
case "last":
|
|
176
|
-
return items[items.length - 1];
|
|
177
|
-
case "next": {
|
|
178
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
179
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
180
|
-
return items2.indexOf(document.activeElement);
|
|
181
|
-
}, [selector]);
|
|
182
|
-
const nextIndex = (currentIndex + 1) % items.length;
|
|
183
|
-
return items[nextIndex];
|
|
184
|
-
}
|
|
185
|
-
case "previous": {
|
|
186
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
187
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
188
|
-
return items2.indexOf(document.activeElement);
|
|
189
|
-
}, [selector]);
|
|
190
|
-
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
191
|
-
return items[prevIndex];
|
|
192
|
-
}
|
|
193
|
-
default:
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
150
|
// src/utils/test/src/ActionExecutor.ts
|
|
200
151
|
var ActionExecutor = class {
|
|
201
152
|
constructor(page, selectors, timeoutMs = 400) {
|
|
@@ -221,16 +172,16 @@ var ActionExecutor = class {
|
|
|
221
172
|
async focus(target, relativeTarget, virtualId) {
|
|
222
173
|
try {
|
|
223
174
|
if (target === "virtual" && virtualId) {
|
|
224
|
-
const
|
|
225
|
-
if (!
|
|
226
|
-
return { success: false, error: `
|
|
175
|
+
const mainSelector = this.selectors.main;
|
|
176
|
+
if (!mainSelector) {
|
|
177
|
+
return { success: false, error: `Main selector not defined for virtual focus.` };
|
|
227
178
|
}
|
|
228
|
-
const
|
|
229
|
-
const exists = await
|
|
179
|
+
const main = this.page.locator(mainSelector).first();
|
|
180
|
+
const exists = await main.count();
|
|
230
181
|
if (!exists) {
|
|
231
|
-
return { success: false, error: `
|
|
182
|
+
return { success: false, error: `Main element not found for virtual focus.` };
|
|
232
183
|
}
|
|
233
|
-
await
|
|
184
|
+
await main.evaluate((el, id) => {
|
|
234
185
|
el.setAttribute("aria-activedescendant", id);
|
|
235
186
|
}, virtualId);
|
|
236
187
|
return { success: true };
|
|
@@ -522,6 +473,10 @@ var AssertionRunner = class {
|
|
|
522
473
|
};
|
|
523
474
|
}
|
|
524
475
|
}
|
|
476
|
+
if (typeof expectedValue !== "string") {
|
|
477
|
+
console.error("[AssertionRunner] expectedValue is not a string:", expectedValue);
|
|
478
|
+
throw new Error(`AssertionRunner: expectedValue for attribute assertion must be a string, but got: ${JSON.stringify(expectedValue)}`);
|
|
479
|
+
}
|
|
525
480
|
const expectedValues = expectedValue.split(" | ").map((v) => v.trim());
|
|
526
481
|
const attributeValue = await target.getAttribute(attribute);
|
|
527
482
|
if (attributeValue !== null && expectedValues.includes(attributeValue)) {
|
|
@@ -689,7 +644,7 @@ var AssertionRunner = class {
|
|
|
689
644
|
// src/utils/test/src/contractTestRunnerPlaywright.ts
|
|
690
645
|
async function runContractTestsPlaywright(componentName, url, strictness, config, configBaseDir) {
|
|
691
646
|
const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
|
|
692
|
-
const isCustomContract = !!componentConfig?.
|
|
647
|
+
const isCustomContract = !!componentConfig?.contractPath;
|
|
693
648
|
const reporter = new ContractReporter(true, isCustomContract);
|
|
694
649
|
const defaultTimeouts = {
|
|
695
650
|
actionTimeoutMs: 400,
|
|
@@ -729,11 +684,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
729
684
|
defaultTimeouts.componentReadyTimeoutMs
|
|
730
685
|
);
|
|
731
686
|
const strictnessMode = normalizeStrictness(strictness);
|
|
732
|
-
|
|
733
|
-
if (!contractPath) {
|
|
734
|
-
const contractTyped = contract_default;
|
|
735
|
-
contractPath = contractTyped[componentName]?.path;
|
|
736
|
-
}
|
|
687
|
+
const contractPath = componentConfig?.contractPath;
|
|
737
688
|
if (!contractPath) {
|
|
738
689
|
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
739
690
|
}
|
|
@@ -952,6 +903,52 @@ This usually means:
|
|
|
952
903
|
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
953
904
|
}
|
|
954
905
|
}
|
|
906
|
+
async function resolveExpectedValue(expectedValue, selectors, page2, context = {}) {
|
|
907
|
+
if (!expectedValue || typeof expectedValue !== "object" || !("ref" in expectedValue)) return expectedValue;
|
|
908
|
+
let refSelector;
|
|
909
|
+
if (expectedValue.ref === "relative") {
|
|
910
|
+
if (!expectedValue.relativeTarget || !context.relativeBaseSelector) return void 0;
|
|
911
|
+
const baseLocator = page2.locator(context.relativeBaseSelector);
|
|
912
|
+
const count = await baseLocator.count();
|
|
913
|
+
let idx = 0;
|
|
914
|
+
if (expectedValue.relativeTarget === "first") idx = 0;
|
|
915
|
+
else if (expectedValue.relativeTarget === "second") idx = 1;
|
|
916
|
+
else if (expectedValue.relativeTarget === "last") idx = count - 1;
|
|
917
|
+
else if (!isNaN(Number(expectedValue.relativeTarget))) idx = Number(expectedValue.relativeTarget);
|
|
918
|
+
else idx = 0;
|
|
919
|
+
if (idx < 0 || idx >= count) return void 0;
|
|
920
|
+
const relElem = baseLocator.nth(idx);
|
|
921
|
+
return await getPropertyFromLocator(relElem, expectedValue.property || expectedValue.attribute);
|
|
922
|
+
} else {
|
|
923
|
+
refSelector = selectors[expectedValue.ref];
|
|
924
|
+
if (!refSelector) throw new Error(`Selector for ref '${expectedValue.ref}' not found in contract selectors.`);
|
|
925
|
+
const refLocator = page2.locator(refSelector).first();
|
|
926
|
+
return await getPropertyFromLocator(refLocator, expectedValue.property || expectedValue.attribute);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
async function getPropertyFromLocator(locator, property) {
|
|
930
|
+
if (!locator) return void 0;
|
|
931
|
+
if (!property || property === "id") {
|
|
932
|
+
return await locator.getAttribute("id") ?? void 0;
|
|
933
|
+
} else if (property === "class") {
|
|
934
|
+
return await locator.getAttribute("class") ?? void 0;
|
|
935
|
+
} else if (property === "textContent") {
|
|
936
|
+
return await locator.evaluate((el) => el.textContent ?? void 0);
|
|
937
|
+
} else if (property.startsWith("aria-")) {
|
|
938
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
939
|
+
} else if (property.endsWith("*")) {
|
|
940
|
+
const attrs = await locator.evaluate((el) => {
|
|
941
|
+
const out = [];
|
|
942
|
+
for (const attr of Array.from(el.attributes)) {
|
|
943
|
+
if (attr.name.startsWith("aria-")) out.push(`${attr.name}=${attr.value}`);
|
|
944
|
+
}
|
|
945
|
+
return out.join(";");
|
|
946
|
+
});
|
|
947
|
+
return attrs;
|
|
948
|
+
} else {
|
|
949
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
955
952
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
956
953
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
957
954
|
if (test.target === "relative") continue;
|
|
@@ -994,6 +991,22 @@ This usually means:
|
|
|
994
991
|
}
|
|
995
992
|
return false;
|
|
996
993
|
};
|
|
994
|
+
let expectedValue = test.expectedValue;
|
|
995
|
+
if (test.expectedValue && typeof test.expectedValue === "object" && "ref" in test.expectedValue) {
|
|
996
|
+
const context = {};
|
|
997
|
+
const relTarget = test.relativeTarget;
|
|
998
|
+
if (test.expectedValue.ref === "relative" && test.target === "relative" && relTarget) {
|
|
999
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
1000
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
1001
|
+
context.relativeBaseSelector = baseSel;
|
|
1002
|
+
} else if (test.expectedValue.ref === "relative" && relTarget) {
|
|
1003
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
1004
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
1005
|
+
context.relativeBaseSelector = baseSel;
|
|
1006
|
+
}
|
|
1007
|
+
expectedValue = await resolveExpectedValue(test.expectedValue, componentContract.selectors, page, context);
|
|
1008
|
+
console.log("Expected value in static check", expectedValue);
|
|
1009
|
+
}
|
|
997
1010
|
if (!test.expectedValue) {
|
|
998
1011
|
const attributes = test.attribute.split(" | ");
|
|
999
1012
|
let hasAny = false;
|
|
@@ -1027,16 +1040,17 @@ This usually means:
|
|
|
1027
1040
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1028
1041
|
}
|
|
1029
1042
|
} else {
|
|
1030
|
-
if (isRedundantCheck(targetSelector, test.attribute,
|
|
1031
|
-
passes.push(`${test.attribute}="${
|
|
1043
|
+
if (isRedundantCheck(targetSelector, test.attribute, typeof expectedValue === "string" ? expectedValue : void 0)) {
|
|
1044
|
+
passes.push(`${test.attribute}="${expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
|
|
1032
1045
|
staticPassed += 1;
|
|
1033
1046
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1034
1047
|
} else {
|
|
1048
|
+
const valueToCheck = expectedValue ?? "";
|
|
1035
1049
|
const result = await staticAssertionRunner.validateAttribute(
|
|
1036
1050
|
target,
|
|
1037
1051
|
test.target,
|
|
1038
1052
|
test.attribute,
|
|
1039
|
-
|
|
1053
|
+
valueToCheck,
|
|
1040
1054
|
test.failureMessage,
|
|
1041
1055
|
"Static ARIA Test"
|
|
1042
1056
|
);
|
|
@@ -1154,7 +1168,33 @@ This usually means:
|
|
|
1154
1168
|
continue;
|
|
1155
1169
|
}
|
|
1156
1170
|
for (const assertion of assertions) {
|
|
1157
|
-
|
|
1171
|
+
let expectedValue;
|
|
1172
|
+
if (assertion.expectedValue && typeof assertion.expectedValue === "object" && "ref" in assertion.expectedValue) {
|
|
1173
|
+
if (assertion.expectedValue.ref === "relative") {
|
|
1174
|
+
const { RelativeTargetResolver: RelativeTargetResolver2 } = await import('./RelativeTargetResolver-G2XDN2VV.js');
|
|
1175
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
1176
|
+
if (!relativeSelector) throw new Error("Relative selector not defined in contract selectors.");
|
|
1177
|
+
const relTarget = assertion.relativeTarget || "first";
|
|
1178
|
+
const relElem = await RelativeTargetResolver2.resolve(page, relativeSelector, relTarget);
|
|
1179
|
+
if (!relElem) throw new Error(`Could not resolve relative target '${relTarget}' for expectedValue.`);
|
|
1180
|
+
const prop = assertion.expectedValue.property || assertion.expectedValue.attribute || "id";
|
|
1181
|
+
if (prop === "textContent") {
|
|
1182
|
+
expectedValue = await relElem.evaluate((el) => el.textContent ?? void 0);
|
|
1183
|
+
} else {
|
|
1184
|
+
const attr = await relElem.getAttribute(prop);
|
|
1185
|
+
expectedValue = attr === null ? void 0 : attr;
|
|
1186
|
+
}
|
|
1187
|
+
} else {
|
|
1188
|
+
expectedValue = await resolveExpectedValue(assertion.expectedValue, componentContract.selectors, page, {});
|
|
1189
|
+
}
|
|
1190
|
+
} else if (typeof assertion.expectedValue === "string" || typeof assertion.expectedValue === "undefined") {
|
|
1191
|
+
expectedValue = assertion.expectedValue;
|
|
1192
|
+
} else {
|
|
1193
|
+
expectedValue = "";
|
|
1194
|
+
}
|
|
1195
|
+
const assertionToRun = { ...assertion, expectedValue };
|
|
1196
|
+
const valueToCheck = expectedValue ?? "";
|
|
1197
|
+
const result = await assertionRunner.validate({ ...assertionToRun, expectedValue: valueToCheck }, dynamicTest.description);
|
|
1158
1198
|
if (result.success && result.passMessage) {
|
|
1159
1199
|
passes.push(result.passMessage);
|
|
1160
1200
|
} else if (!result.success && result.failMessage) {
|
|
@@ -18,7 +18,7 @@ function resolveSetup(setup, ctx) {
|
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
20
|
var COMBOBOX_STATES = {
|
|
21
|
-
"
|
|
21
|
+
"popup.open": {
|
|
22
22
|
setup: [
|
|
23
23
|
{
|
|
24
24
|
when: ["keyboard", "textInput"],
|
|
@@ -35,7 +35,7 @@ var COMBOBOX_STATES = {
|
|
|
35
35
|
],
|
|
36
36
|
assertion: isComboboxOpen
|
|
37
37
|
},
|
|
38
|
-
"
|
|
38
|
+
"popup.closed": {
|
|
39
39
|
setup: [
|
|
40
40
|
{
|
|
41
41
|
when: ["keyboard"],
|
|
@@ -50,18 +50,18 @@ var COMBOBOX_STATES = {
|
|
|
50
50
|
]
|
|
51
51
|
}
|
|
52
52
|
],
|
|
53
|
-
assertion: isComboboxClosed
|
|
53
|
+
assertion: [...isComboboxClosed(), ...isActiveDescendantEmpty()]
|
|
54
54
|
},
|
|
55
|
-
"
|
|
55
|
+
"main.focused": {
|
|
56
56
|
setup: [
|
|
57
57
|
{
|
|
58
58
|
when: ["keyboard"],
|
|
59
59
|
steps: () => [
|
|
60
|
-
{ type: "focus", target: "
|
|
60
|
+
{ type: "focus", target: "main" }
|
|
61
61
|
]
|
|
62
62
|
}
|
|
63
63
|
],
|
|
64
|
-
assertion:
|
|
64
|
+
assertion: isMainFocused
|
|
65
65
|
},
|
|
66
66
|
"input.filled": {
|
|
67
67
|
setup: [
|
|
@@ -74,8 +74,19 @@ var COMBOBOX_STATES = {
|
|
|
74
74
|
],
|
|
75
75
|
assertion: isInputFilled
|
|
76
76
|
},
|
|
77
|
+
"input.notFilled": {
|
|
78
|
+
setup: [
|
|
79
|
+
{
|
|
80
|
+
when: ["keyboard", "textInput"],
|
|
81
|
+
steps: () => [
|
|
82
|
+
{ type: "type", target: "input", value: "" }
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
assertion: isInputNotFilled
|
|
87
|
+
},
|
|
77
88
|
"activeOption.first": {
|
|
78
|
-
requires: ["
|
|
89
|
+
requires: ["popup.open"],
|
|
79
90
|
setup: [
|
|
80
91
|
{
|
|
81
92
|
when: ["keyboard"],
|
|
@@ -84,7 +95,7 @@ var COMBOBOX_STATES = {
|
|
|
84
95
|
]
|
|
85
96
|
}
|
|
86
97
|
],
|
|
87
|
-
assertion:
|
|
98
|
+
assertion: isActiveDescendantFirst
|
|
88
99
|
},
|
|
89
100
|
"activeOption.last": {
|
|
90
101
|
requires: ["activeOption.first"],
|
|
@@ -96,10 +107,30 @@ var COMBOBOX_STATES = {
|
|
|
96
107
|
]
|
|
97
108
|
}
|
|
98
109
|
],
|
|
110
|
+
assertion: isActiveDescendantLast
|
|
111
|
+
},
|
|
112
|
+
"activeDescendant.notEmpty": {
|
|
113
|
+
requires: [],
|
|
114
|
+
setup: [
|
|
115
|
+
{
|
|
116
|
+
when: ["keyboard"],
|
|
117
|
+
steps: () => []
|
|
118
|
+
}
|
|
119
|
+
],
|
|
99
120
|
assertion: isActiveDescendantNotEmpty
|
|
100
121
|
},
|
|
122
|
+
"activeDescendant.Empty": {
|
|
123
|
+
requires: [],
|
|
124
|
+
setup: [
|
|
125
|
+
{
|
|
126
|
+
when: ["keyboard"],
|
|
127
|
+
steps: () => []
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
assertion: isActiveDescendantEmpty
|
|
131
|
+
},
|
|
101
132
|
"selectedOption.first": {
|
|
102
|
-
requires: ["
|
|
133
|
+
requires: ["popup.open"],
|
|
103
134
|
setup: [
|
|
104
135
|
{
|
|
105
136
|
when: ["pointer"],
|
|
@@ -111,7 +142,7 @@ var COMBOBOX_STATES = {
|
|
|
111
142
|
assertion: () => isAriaSelected("first")
|
|
112
143
|
},
|
|
113
144
|
"selectedOption.last": {
|
|
114
|
-
requires: ["
|
|
145
|
+
requires: ["popup.open"],
|
|
115
146
|
setup: [
|
|
116
147
|
{
|
|
117
148
|
when: ["pointer"],
|
|
@@ -126,43 +157,76 @@ var COMBOBOX_STATES = {
|
|
|
126
157
|
function isComboboxOpen() {
|
|
127
158
|
return [
|
|
128
159
|
{
|
|
129
|
-
target: "
|
|
160
|
+
target: "popup",
|
|
130
161
|
assertion: "toBeVisible",
|
|
131
|
-
failureMessage: "Expected
|
|
162
|
+
failureMessage: "Expected popup to be visible"
|
|
132
163
|
},
|
|
133
164
|
{
|
|
134
|
-
target: "
|
|
165
|
+
target: "main",
|
|
135
166
|
assertion: "toHaveAttribute",
|
|
136
167
|
attribute: "aria-expanded",
|
|
137
168
|
expectedValue: "true",
|
|
138
|
-
failureMessage: "Expect combobox
|
|
169
|
+
failureMessage: "Expect combobox main to have aria-expanded='true'."
|
|
139
170
|
}
|
|
140
171
|
];
|
|
141
172
|
}
|
|
142
173
|
function isComboboxClosed() {
|
|
143
174
|
return [
|
|
144
175
|
{
|
|
145
|
-
target: "
|
|
176
|
+
target: "popup",
|
|
146
177
|
assertion: "notToBeVisible",
|
|
147
|
-
failureMessage: "Expected
|
|
178
|
+
failureMessage: "Expected popup to be closed"
|
|
148
179
|
},
|
|
149
180
|
{
|
|
150
|
-
target: "
|
|
181
|
+
target: "main",
|
|
151
182
|
assertion: "toHaveAttribute",
|
|
152
183
|
attribute: "aria-expanded",
|
|
153
184
|
expectedValue: "false",
|
|
154
|
-
failureMessage: "Expect combobox
|
|
185
|
+
failureMessage: "Expect combobox main to have aria-expanded='false'."
|
|
186
|
+
}
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
function isActiveDescendantFirst() {
|
|
190
|
+
return [
|
|
191
|
+
{
|
|
192
|
+
target: "main",
|
|
193
|
+
assertion: "toHaveAttribute",
|
|
194
|
+
attribute: "aria-activedescendant",
|
|
195
|
+
expectedValue: { ref: "relative", relativeTarget: "first", property: "id" },
|
|
196
|
+
failureMessage: "Expected aria-activedescendant on main to match the id of the first option."
|
|
197
|
+
}
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
function isActiveDescendantLast() {
|
|
201
|
+
return [
|
|
202
|
+
{
|
|
203
|
+
target: "main",
|
|
204
|
+
assertion: "toHaveAttribute",
|
|
205
|
+
attribute: "aria-activedescendant",
|
|
206
|
+
expectedValue: { ref: "relative", relativeTarget: "last", property: "id" },
|
|
207
|
+
failureMessage: "Expected aria-activedescendant on main to match the id of the last option."
|
|
155
208
|
}
|
|
156
209
|
];
|
|
157
210
|
}
|
|
158
211
|
function isActiveDescendantNotEmpty() {
|
|
159
212
|
return [
|
|
160
213
|
{
|
|
161
|
-
target: "
|
|
214
|
+
target: "main",
|
|
162
215
|
assertion: "toHaveAttribute",
|
|
163
216
|
attribute: "aria-activedescendant",
|
|
164
217
|
expectedValue: "!empty",
|
|
165
|
-
failureMessage: "Expected aria-activedescendant to not be empty"
|
|
218
|
+
failureMessage: "Expected aria-activedescendant on main to not be empty."
|
|
219
|
+
}
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
function isActiveDescendantEmpty() {
|
|
223
|
+
return [
|
|
224
|
+
{
|
|
225
|
+
target: "main",
|
|
226
|
+
assertion: "toHaveAttribute",
|
|
227
|
+
attribute: "aria-activedescendant",
|
|
228
|
+
expectedValue: "",
|
|
229
|
+
failureMessage: "Expected aria-activedescendant on main to be empty."
|
|
166
230
|
}
|
|
167
231
|
];
|
|
168
232
|
}
|
|
@@ -174,16 +238,16 @@ function isAriaSelected(index) {
|
|
|
174
238
|
assertion: "toHaveAttribute",
|
|
175
239
|
attribute: "aria-selected",
|
|
176
240
|
expectedValue: "true",
|
|
177
|
-
failureMessage: `Expected ${index} option to have aria-selected='true'
|
|
241
|
+
failureMessage: `Expected ${index} option to have aria-selected='true'.`
|
|
178
242
|
}
|
|
179
243
|
];
|
|
180
244
|
}
|
|
181
|
-
function
|
|
245
|
+
function isMainFocused() {
|
|
182
246
|
return [
|
|
183
247
|
{
|
|
184
|
-
target: "
|
|
248
|
+
target: "main",
|
|
185
249
|
assertion: "toHaveFocus",
|
|
186
|
-
failureMessage: "Expected
|
|
250
|
+
failureMessage: "Expected main to be focused."
|
|
187
251
|
}
|
|
188
252
|
];
|
|
189
253
|
}
|
|
@@ -193,14 +257,24 @@ function isInputFilled() {
|
|
|
193
257
|
target: "input",
|
|
194
258
|
assertion: "toHaveValue",
|
|
195
259
|
expectedValue: "test",
|
|
196
|
-
failureMessage: "Expected input to have the value 'test'"
|
|
260
|
+
failureMessage: "Expected input to have the value 'test'."
|
|
261
|
+
}
|
|
262
|
+
];
|
|
263
|
+
}
|
|
264
|
+
function isInputNotFilled() {
|
|
265
|
+
return [
|
|
266
|
+
{
|
|
267
|
+
target: "input",
|
|
268
|
+
assertion: "toHaveValue",
|
|
269
|
+
expectedValue: "",
|
|
270
|
+
failureMessage: "Expected input to have the value ''."
|
|
197
271
|
}
|
|
198
272
|
];
|
|
199
273
|
}
|
|
200
274
|
|
|
201
275
|
// src/utils/test/dsl/src/contractBuilder.ts
|
|
202
276
|
var STATE_PACKS = {
|
|
203
|
-
"combobox
|
|
277
|
+
"combobox": COMBOBOX_STATES
|
|
204
278
|
// Add more mappings as needed
|
|
205
279
|
};
|
|
206
280
|
var FluentContract = class {
|
|
@@ -234,11 +308,13 @@ var ContractBuilder = class {
|
|
|
234
308
|
const api = {
|
|
235
309
|
ariaReference: (from, attribute, to) => ({
|
|
236
310
|
required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required" }),
|
|
237
|
-
optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" })
|
|
311
|
+
optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" }),
|
|
312
|
+
recommended: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "recommended" })
|
|
238
313
|
}),
|
|
239
314
|
contains: (parent, child) => ({
|
|
240
315
|
required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required" }),
|
|
241
|
-
optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" })
|
|
316
|
+
optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" }),
|
|
317
|
+
recommended: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "recommended" })
|
|
242
318
|
})
|
|
243
319
|
};
|
|
244
320
|
fn(api);
|
|
@@ -249,7 +325,8 @@ var ContractBuilder = class {
|
|
|
249
325
|
target: (target) => ({
|
|
250
326
|
has: (attribute, expectedValue) => ({
|
|
251
327
|
required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required" }),
|
|
252
|
-
optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" })
|
|
328
|
+
optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" }),
|
|
329
|
+
recommended: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "recommended" })
|
|
253
330
|
})
|
|
254
331
|
})
|
|
255
332
|
};
|
|
@@ -92,10 +92,12 @@ declare class ContractBuilder {
|
|
|
92
92
|
ariaReference: (from: string, attribute: string, to: string) => {
|
|
93
93
|
required: () => void;
|
|
94
94
|
optional: () => void;
|
|
95
|
+
recommended: () => void;
|
|
95
96
|
};
|
|
96
97
|
contains: (parent: string, child: string) => {
|
|
97
98
|
required: () => void;
|
|
98
99
|
optional: () => void;
|
|
100
|
+
recommended: () => void;
|
|
99
101
|
};
|
|
100
102
|
}) => void): this;
|
|
101
103
|
static(fn: (s: {
|
|
@@ -103,6 +105,7 @@ declare class ContractBuilder {
|
|
|
103
105
|
has: (attribute: string, expectedValue: string) => {
|
|
104
106
|
required: () => void;
|
|
105
107
|
optional: () => void;
|
|
108
|
+
recommended: () => void;
|
|
106
109
|
};
|
|
107
110
|
};
|
|
108
111
|
}) => void): this;
|
|
@@ -92,10 +92,12 @@ declare class ContractBuilder {
|
|
|
92
92
|
ariaReference: (from: string, attribute: string, to: string) => {
|
|
93
93
|
required: () => void;
|
|
94
94
|
optional: () => void;
|
|
95
|
+
recommended: () => void;
|
|
95
96
|
};
|
|
96
97
|
contains: (parent: string, child: string) => {
|
|
97
98
|
required: () => void;
|
|
98
99
|
optional: () => void;
|
|
100
|
+
recommended: () => void;
|
|
99
101
|
};
|
|
100
102
|
}) => void): this;
|
|
101
103
|
static(fn: (s: {
|
|
@@ -103,6 +105,7 @@ declare class ContractBuilder {
|
|
|
103
105
|
has: (attribute: string, expectedValue: string) => {
|
|
104
106
|
required: () => void;
|
|
105
107
|
optional: () => void;
|
|
108
|
+
recommended: () => void;
|
|
106
109
|
};
|
|
107
110
|
};
|
|
108
111
|
}) => void): this;
|