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
|
@@ -16,7 +16,7 @@ function resolveSetup(setup, ctx) {
|
|
|
16
16
|
);
|
|
17
17
|
}
|
|
18
18
|
var COMBOBOX_STATES = {
|
|
19
|
-
"
|
|
19
|
+
"popup.open": {
|
|
20
20
|
setup: [
|
|
21
21
|
{
|
|
22
22
|
when: ["keyboard", "textInput"],
|
|
@@ -33,7 +33,7 @@ var COMBOBOX_STATES = {
|
|
|
33
33
|
],
|
|
34
34
|
assertion: isComboboxOpen
|
|
35
35
|
},
|
|
36
|
-
"
|
|
36
|
+
"popup.closed": {
|
|
37
37
|
setup: [
|
|
38
38
|
{
|
|
39
39
|
when: ["keyboard"],
|
|
@@ -48,18 +48,18 @@ var COMBOBOX_STATES = {
|
|
|
48
48
|
]
|
|
49
49
|
}
|
|
50
50
|
],
|
|
51
|
-
assertion: isComboboxClosed
|
|
51
|
+
assertion: [...isComboboxClosed(), ...isActiveDescendantEmpty()]
|
|
52
52
|
},
|
|
53
|
-
"
|
|
53
|
+
"main.focused": {
|
|
54
54
|
setup: [
|
|
55
55
|
{
|
|
56
56
|
when: ["keyboard"],
|
|
57
57
|
steps: () => [
|
|
58
|
-
{ type: "focus", target: "
|
|
58
|
+
{ type: "focus", target: "main" }
|
|
59
59
|
]
|
|
60
60
|
}
|
|
61
61
|
],
|
|
62
|
-
assertion:
|
|
62
|
+
assertion: isMainFocused
|
|
63
63
|
},
|
|
64
64
|
"input.filled": {
|
|
65
65
|
setup: [
|
|
@@ -72,8 +72,19 @@ var COMBOBOX_STATES = {
|
|
|
72
72
|
],
|
|
73
73
|
assertion: isInputFilled
|
|
74
74
|
},
|
|
75
|
+
"input.notFilled": {
|
|
76
|
+
setup: [
|
|
77
|
+
{
|
|
78
|
+
when: ["keyboard", "textInput"],
|
|
79
|
+
steps: () => [
|
|
80
|
+
{ type: "type", target: "input", value: "" }
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
assertion: isInputNotFilled
|
|
85
|
+
},
|
|
75
86
|
"activeOption.first": {
|
|
76
|
-
requires: ["
|
|
87
|
+
requires: ["popup.open"],
|
|
77
88
|
setup: [
|
|
78
89
|
{
|
|
79
90
|
when: ["keyboard"],
|
|
@@ -82,7 +93,7 @@ var COMBOBOX_STATES = {
|
|
|
82
93
|
]
|
|
83
94
|
}
|
|
84
95
|
],
|
|
85
|
-
assertion:
|
|
96
|
+
assertion: isActiveDescendantFirst
|
|
86
97
|
},
|
|
87
98
|
"activeOption.last": {
|
|
88
99
|
requires: ["activeOption.first"],
|
|
@@ -94,10 +105,30 @@ var COMBOBOX_STATES = {
|
|
|
94
105
|
]
|
|
95
106
|
}
|
|
96
107
|
],
|
|
108
|
+
assertion: isActiveDescendantLast
|
|
109
|
+
},
|
|
110
|
+
"activeDescendant.notEmpty": {
|
|
111
|
+
requires: [],
|
|
112
|
+
setup: [
|
|
113
|
+
{
|
|
114
|
+
when: ["keyboard"],
|
|
115
|
+
steps: () => []
|
|
116
|
+
}
|
|
117
|
+
],
|
|
97
118
|
assertion: isActiveDescendantNotEmpty
|
|
98
119
|
},
|
|
120
|
+
"activeDescendant.Empty": {
|
|
121
|
+
requires: [],
|
|
122
|
+
setup: [
|
|
123
|
+
{
|
|
124
|
+
when: ["keyboard"],
|
|
125
|
+
steps: () => []
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
assertion: isActiveDescendantEmpty
|
|
129
|
+
},
|
|
99
130
|
"selectedOption.first": {
|
|
100
|
-
requires: ["
|
|
131
|
+
requires: ["popup.open"],
|
|
101
132
|
setup: [
|
|
102
133
|
{
|
|
103
134
|
when: ["pointer"],
|
|
@@ -109,7 +140,7 @@ var COMBOBOX_STATES = {
|
|
|
109
140
|
assertion: () => isAriaSelected("first")
|
|
110
141
|
},
|
|
111
142
|
"selectedOption.last": {
|
|
112
|
-
requires: ["
|
|
143
|
+
requires: ["popup.open"],
|
|
113
144
|
setup: [
|
|
114
145
|
{
|
|
115
146
|
when: ["pointer"],
|
|
@@ -124,43 +155,76 @@ var COMBOBOX_STATES = {
|
|
|
124
155
|
function isComboboxOpen() {
|
|
125
156
|
return [
|
|
126
157
|
{
|
|
127
|
-
target: "
|
|
158
|
+
target: "popup",
|
|
128
159
|
assertion: "toBeVisible",
|
|
129
|
-
failureMessage: "Expected
|
|
160
|
+
failureMessage: "Expected popup to be visible"
|
|
130
161
|
},
|
|
131
162
|
{
|
|
132
|
-
target: "
|
|
163
|
+
target: "main",
|
|
133
164
|
assertion: "toHaveAttribute",
|
|
134
165
|
attribute: "aria-expanded",
|
|
135
166
|
expectedValue: "true",
|
|
136
|
-
failureMessage: "Expect combobox
|
|
167
|
+
failureMessage: "Expect combobox main to have aria-expanded='true'."
|
|
137
168
|
}
|
|
138
169
|
];
|
|
139
170
|
}
|
|
140
171
|
function isComboboxClosed() {
|
|
141
172
|
return [
|
|
142
173
|
{
|
|
143
|
-
target: "
|
|
174
|
+
target: "popup",
|
|
144
175
|
assertion: "notToBeVisible",
|
|
145
|
-
failureMessage: "Expected
|
|
176
|
+
failureMessage: "Expected popup to be closed"
|
|
146
177
|
},
|
|
147
178
|
{
|
|
148
|
-
target: "
|
|
179
|
+
target: "main",
|
|
149
180
|
assertion: "toHaveAttribute",
|
|
150
181
|
attribute: "aria-expanded",
|
|
151
182
|
expectedValue: "false",
|
|
152
|
-
failureMessage: "Expect combobox
|
|
183
|
+
failureMessage: "Expect combobox main to have aria-expanded='false'."
|
|
184
|
+
}
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
function isActiveDescendantFirst() {
|
|
188
|
+
return [
|
|
189
|
+
{
|
|
190
|
+
target: "main",
|
|
191
|
+
assertion: "toHaveAttribute",
|
|
192
|
+
attribute: "aria-activedescendant",
|
|
193
|
+
expectedValue: { ref: "relative", relativeTarget: "first", property: "id" },
|
|
194
|
+
failureMessage: "Expected aria-activedescendant on main to match the id of the first option."
|
|
195
|
+
}
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
function isActiveDescendantLast() {
|
|
199
|
+
return [
|
|
200
|
+
{
|
|
201
|
+
target: "main",
|
|
202
|
+
assertion: "toHaveAttribute",
|
|
203
|
+
attribute: "aria-activedescendant",
|
|
204
|
+
expectedValue: { ref: "relative", relativeTarget: "last", property: "id" },
|
|
205
|
+
failureMessage: "Expected aria-activedescendant on main to match the id of the last option."
|
|
153
206
|
}
|
|
154
207
|
];
|
|
155
208
|
}
|
|
156
209
|
function isActiveDescendantNotEmpty() {
|
|
157
210
|
return [
|
|
158
211
|
{
|
|
159
|
-
target: "
|
|
212
|
+
target: "main",
|
|
160
213
|
assertion: "toHaveAttribute",
|
|
161
214
|
attribute: "aria-activedescendant",
|
|
162
215
|
expectedValue: "!empty",
|
|
163
|
-
failureMessage: "Expected aria-activedescendant to not be empty"
|
|
216
|
+
failureMessage: "Expected aria-activedescendant on main to not be empty."
|
|
217
|
+
}
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
function isActiveDescendantEmpty() {
|
|
221
|
+
return [
|
|
222
|
+
{
|
|
223
|
+
target: "main",
|
|
224
|
+
assertion: "toHaveAttribute",
|
|
225
|
+
attribute: "aria-activedescendant",
|
|
226
|
+
expectedValue: "",
|
|
227
|
+
failureMessage: "Expected aria-activedescendant on main to be empty."
|
|
164
228
|
}
|
|
165
229
|
];
|
|
166
230
|
}
|
|
@@ -172,16 +236,16 @@ function isAriaSelected(index) {
|
|
|
172
236
|
assertion: "toHaveAttribute",
|
|
173
237
|
attribute: "aria-selected",
|
|
174
238
|
expectedValue: "true",
|
|
175
|
-
failureMessage: `Expected ${index} option to have aria-selected='true'
|
|
239
|
+
failureMessage: `Expected ${index} option to have aria-selected='true'.`
|
|
176
240
|
}
|
|
177
241
|
];
|
|
178
242
|
}
|
|
179
|
-
function
|
|
243
|
+
function isMainFocused() {
|
|
180
244
|
return [
|
|
181
245
|
{
|
|
182
|
-
target: "
|
|
246
|
+
target: "main",
|
|
183
247
|
assertion: "toHaveFocus",
|
|
184
|
-
failureMessage: "Expected
|
|
248
|
+
failureMessage: "Expected main to be focused."
|
|
185
249
|
}
|
|
186
250
|
];
|
|
187
251
|
}
|
|
@@ -191,14 +255,24 @@ function isInputFilled() {
|
|
|
191
255
|
target: "input",
|
|
192
256
|
assertion: "toHaveValue",
|
|
193
257
|
expectedValue: "test",
|
|
194
|
-
failureMessage: "Expected input to have the value 'test'"
|
|
258
|
+
failureMessage: "Expected input to have the value 'test'."
|
|
259
|
+
}
|
|
260
|
+
];
|
|
261
|
+
}
|
|
262
|
+
function isInputNotFilled() {
|
|
263
|
+
return [
|
|
264
|
+
{
|
|
265
|
+
target: "input",
|
|
266
|
+
assertion: "toHaveValue",
|
|
267
|
+
expectedValue: "",
|
|
268
|
+
failureMessage: "Expected input to have the value ''."
|
|
195
269
|
}
|
|
196
270
|
];
|
|
197
271
|
}
|
|
198
272
|
|
|
199
273
|
// src/utils/test/dsl/src/contractBuilder.ts
|
|
200
274
|
var STATE_PACKS = {
|
|
201
|
-
"combobox
|
|
275
|
+
"combobox": COMBOBOX_STATES
|
|
202
276
|
// Add more mappings as needed
|
|
203
277
|
};
|
|
204
278
|
var FluentContract = class {
|
|
@@ -232,11 +306,13 @@ var ContractBuilder = class {
|
|
|
232
306
|
const api = {
|
|
233
307
|
ariaReference: (from, attribute, to) => ({
|
|
234
308
|
required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required" }),
|
|
235
|
-
optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" })
|
|
309
|
+
optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" }),
|
|
310
|
+
recommended: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "recommended" })
|
|
236
311
|
}),
|
|
237
312
|
contains: (parent, child) => ({
|
|
238
313
|
required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required" }),
|
|
239
|
-
optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" })
|
|
314
|
+
optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" }),
|
|
315
|
+
recommended: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "recommended" })
|
|
240
316
|
})
|
|
241
317
|
};
|
|
242
318
|
fn(api);
|
|
@@ -247,7 +323,8 @@ var ContractBuilder = class {
|
|
|
247
323
|
target: (target) => ({
|
|
248
324
|
has: (attribute, expectedValue) => ({
|
|
249
325
|
required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required" }),
|
|
250
|
-
optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" })
|
|
326
|
+
optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" }),
|
|
327
|
+
recommended: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "recommended" })
|
|
251
328
|
})
|
|
252
329
|
})
|
|
253
330
|
};
|
|
@@ -30,31 +30,6 @@ var __export = (target, all) => {
|
|
|
30
30
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
// src/utils/test/contract/contract.json
|
|
34
|
-
var contract_default;
|
|
35
|
-
var init_contract = __esm({
|
|
36
|
-
"src/utils/test/contract/contract.json"() {
|
|
37
|
-
contract_default = {
|
|
38
|
-
menu: {
|
|
39
|
-
path: "./aria-contracts/menu/menu.contract.json",
|
|
40
|
-
component: "menu"
|
|
41
|
-
},
|
|
42
|
-
"combobox.listbox": {
|
|
43
|
-
path: "./aria-contracts/combobox/combobox.listbox.contract.json",
|
|
44
|
-
component: "combobox.listbox"
|
|
45
|
-
},
|
|
46
|
-
accordion: {
|
|
47
|
-
path: "./aria-contracts/accordion/accordion.contract.json",
|
|
48
|
-
component: "accordion"
|
|
49
|
-
},
|
|
50
|
-
tabs: {
|
|
51
|
-
path: "./aria-contracts/tabs/tabs.contract.json",
|
|
52
|
-
component: "tabs"
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
33
|
// src/utils/test/src/ContractReporter.ts
|
|
59
34
|
var ContractReporter;
|
|
60
35
|
var init_ContractReporter = __esm({
|
|
@@ -371,9 +346,7 @@ async function getOrCreateContext() {
|
|
|
371
346
|
if (!sharedContext) {
|
|
372
347
|
const browser = await getOrCreateBrowser();
|
|
373
348
|
sharedContext = await browser.newContext({
|
|
374
|
-
// Isolated context - no permissions, no geolocation, etc.
|
|
375
349
|
permissions: [],
|
|
376
|
-
// Ignore HTTPS errors for local dev servers
|
|
377
350
|
ignoreHTTPSErrors: true
|
|
378
351
|
});
|
|
379
352
|
}
|
|
@@ -473,8 +446,8 @@ function validateConfig(config) {
|
|
|
473
446
|
if (typeof comp.name !== "string") {
|
|
474
447
|
errors.push(`test.components[${idx}].name must be a string`);
|
|
475
448
|
}
|
|
476
|
-
if (comp.
|
|
477
|
-
errors.push(`test.components[${idx}].
|
|
449
|
+
if (comp.contractPath !== void 0 && typeof comp.contractPath !== "string") {
|
|
450
|
+
errors.push(`test.components[${idx}].contractPath must be a string when provided`);
|
|
478
451
|
}
|
|
479
452
|
if (comp.strategyPath !== void 0 && typeof comp.strategyPath !== "string") {
|
|
480
453
|
errors.push(`test.components[${idx}].strategyPath must be a string when provided`);
|
|
@@ -734,7 +707,7 @@ var init_ComboboxComponentStrategy = __esm({
|
|
|
734
707
|
const popupElement = page.locator(popupSelector).first();
|
|
735
708
|
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
736
709
|
if (!isPopupVisible) return;
|
|
737
|
-
let
|
|
710
|
+
let popupClosed = false;
|
|
738
711
|
let closeSelector = this.selectors.input;
|
|
739
712
|
if (!closeSelector && this.selectors.focusable) {
|
|
740
713
|
closeSelector = this.selectors.focusable;
|
|
@@ -745,18 +718,18 @@ var init_ComboboxComponentStrategy = __esm({
|
|
|
745
718
|
const closeElement = page.locator(closeSelector).first();
|
|
746
719
|
await closeElement.focus();
|
|
747
720
|
await page.keyboard.press("Escape");
|
|
748
|
-
|
|
721
|
+
popupClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
749
722
|
}
|
|
750
|
-
if (!
|
|
723
|
+
if (!popupClosed && this.selectors.button) {
|
|
751
724
|
const buttonElement = page.locator(this.selectors.button).first();
|
|
752
725
|
await buttonElement.click({ timeout: this.actionTimeoutMs });
|
|
753
|
-
|
|
726
|
+
popupClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
754
727
|
}
|
|
755
|
-
if (!
|
|
728
|
+
if (!popupClosed) {
|
|
756
729
|
await page.mouse.click(10, 10);
|
|
757
|
-
|
|
730
|
+
popupClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
758
731
|
}
|
|
759
|
-
if (!
|
|
732
|
+
if (!popupClosed) {
|
|
760
733
|
throw new Error(
|
|
761
734
|
`\u274C FATAL: Cannot close combobox popup between tests. Popup remains visible after trying:
|
|
762
735
|
1. Escape key
|
|
@@ -848,12 +821,6 @@ var init_StrategyRegistry = __esm({
|
|
|
848
821
|
(m) => m.TabsComponentStrategy
|
|
849
822
|
)
|
|
850
823
|
);
|
|
851
|
-
this.builtInStrategies.set(
|
|
852
|
-
"combobox.listbox",
|
|
853
|
-
() => Promise.resolve().then(() => (init_ComboboxComponentStrategy(), ComboboxComponentStrategy_exports)).then(
|
|
854
|
-
(m) => m.ComboboxComponentStrategy
|
|
855
|
-
)
|
|
856
|
-
);
|
|
857
824
|
}
|
|
858
825
|
/**
|
|
859
826
|
* Load a strategy - either from custom path or built-in registry
|
|
@@ -901,7 +868,6 @@ var init_StrategyRegistry = __esm({
|
|
|
901
868
|
var ComponentDetector;
|
|
902
869
|
var init_ComponentDetector = __esm({
|
|
903
870
|
"src/utils/test/src/ComponentDetector.ts"() {
|
|
904
|
-
init_contract();
|
|
905
871
|
init_StrategyRegistry();
|
|
906
872
|
ComponentDetector = class {
|
|
907
873
|
static strategyRegistry = new StrategyRegistry();
|
|
@@ -922,11 +888,7 @@ var init_ComponentDetector = __esm({
|
|
|
922
888
|
*/
|
|
923
889
|
static async detect(componentName, componentConfig, actionTimeoutMs = 400, assertionTimeoutMs = 400, configBaseDir) {
|
|
924
890
|
const typedComponentConfig = this.isComponentConfig(componentConfig) ? componentConfig : void 0;
|
|
925
|
-
|
|
926
|
-
if (!contractPath) {
|
|
927
|
-
const contractTyped = contract_default;
|
|
928
|
-
contractPath = contractTyped[componentName]?.path;
|
|
929
|
-
}
|
|
891
|
+
const contractPath = typedComponentConfig?.contractPath;
|
|
930
892
|
if (!contractPath) {
|
|
931
893
|
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
932
894
|
}
|
|
@@ -959,7 +921,7 @@ var init_ComponentDetector = __esm({
|
|
|
959
921
|
if (!strategyClass) {
|
|
960
922
|
return null;
|
|
961
923
|
}
|
|
962
|
-
const mainSelector = selectors.
|
|
924
|
+
const mainSelector = selectors.main;
|
|
963
925
|
if (componentName === "tabs") {
|
|
964
926
|
return new strategyClass(mainSelector, selectors);
|
|
965
927
|
}
|
|
@@ -975,6 +937,10 @@ var init_ComponentDetector = __esm({
|
|
|
975
937
|
});
|
|
976
938
|
|
|
977
939
|
// src/utils/test/src/RelativeTargetResolver.ts
|
|
940
|
+
var RelativeTargetResolver_exports = {};
|
|
941
|
+
__export(RelativeTargetResolver_exports, {
|
|
942
|
+
RelativeTargetResolver: () => RelativeTargetResolver
|
|
943
|
+
});
|
|
978
944
|
var RelativeTargetResolver;
|
|
979
945
|
var init_RelativeTargetResolver = __esm({
|
|
980
946
|
"src/utils/test/src/RelativeTargetResolver.ts"() {
|
|
@@ -1048,16 +1014,16 @@ var init_ActionExecutor = __esm({
|
|
|
1048
1014
|
async focus(target, relativeTarget, virtualId) {
|
|
1049
1015
|
try {
|
|
1050
1016
|
if (target === "virtual" && virtualId) {
|
|
1051
|
-
const
|
|
1052
|
-
if (!
|
|
1053
|
-
return { success: false, error: `
|
|
1017
|
+
const mainSelector = this.selectors.main;
|
|
1018
|
+
if (!mainSelector) {
|
|
1019
|
+
return { success: false, error: `Main selector not defined for virtual focus.` };
|
|
1054
1020
|
}
|
|
1055
|
-
const
|
|
1056
|
-
const exists = await
|
|
1021
|
+
const main = this.page.locator(mainSelector).first();
|
|
1022
|
+
const exists = await main.count();
|
|
1057
1023
|
if (!exists) {
|
|
1058
|
-
return { success: false, error: `
|
|
1024
|
+
return { success: false, error: `Main element not found for virtual focus.` };
|
|
1059
1025
|
}
|
|
1060
|
-
await
|
|
1026
|
+
await main.evaluate((el, id) => {
|
|
1061
1027
|
el.setAttribute("aria-activedescendant", id);
|
|
1062
1028
|
}, virtualId);
|
|
1063
1029
|
return { success: true };
|
|
@@ -1355,6 +1321,10 @@ var init_AssertionRunner = __esm({
|
|
|
1355
1321
|
};
|
|
1356
1322
|
}
|
|
1357
1323
|
}
|
|
1324
|
+
if (typeof expectedValue !== "string") {
|
|
1325
|
+
console.error("[AssertionRunner] expectedValue is not a string:", expectedValue);
|
|
1326
|
+
throw new Error(`AssertionRunner: expectedValue for attribute assertion must be a string, but got: ${JSON.stringify(expectedValue)}`);
|
|
1327
|
+
}
|
|
1358
1328
|
const expectedValues = expectedValue.split(" | ").map((v) => v.trim());
|
|
1359
1329
|
const attributeValue = await target.getAttribute(attribute);
|
|
1360
1330
|
if (attributeValue !== null && expectedValues.includes(attributeValue)) {
|
|
@@ -1528,7 +1498,7 @@ __export(contractTestRunnerPlaywright_exports, {
|
|
|
1528
1498
|
});
|
|
1529
1499
|
async function runContractTestsPlaywright(componentName, url, strictness, config, configBaseDir) {
|
|
1530
1500
|
const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
|
|
1531
|
-
const isCustomContract = !!componentConfig?.
|
|
1501
|
+
const isCustomContract = !!componentConfig?.contractPath;
|
|
1532
1502
|
const reporter = new ContractReporter(true, isCustomContract);
|
|
1533
1503
|
const defaultTimeouts = {
|
|
1534
1504
|
actionTimeoutMs: 400,
|
|
@@ -1568,11 +1538,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
1568
1538
|
defaultTimeouts.componentReadyTimeoutMs
|
|
1569
1539
|
);
|
|
1570
1540
|
const strictnessMode = normalizeStrictness(strictness);
|
|
1571
|
-
|
|
1572
|
-
if (!contractPath) {
|
|
1573
|
-
const contractTyped = contract_default;
|
|
1574
|
-
contractPath = contractTyped[componentName]?.path;
|
|
1575
|
-
}
|
|
1541
|
+
const contractPath = componentConfig?.contractPath;
|
|
1576
1542
|
if (!contractPath) {
|
|
1577
1543
|
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
1578
1544
|
}
|
|
@@ -1791,6 +1757,52 @@ This usually means:
|
|
|
1791
1757
|
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
1792
1758
|
}
|
|
1793
1759
|
}
|
|
1760
|
+
async function resolveExpectedValue(expectedValue, selectors, page2, context = {}) {
|
|
1761
|
+
if (!expectedValue || typeof expectedValue !== "object" || !("ref" in expectedValue)) return expectedValue;
|
|
1762
|
+
let refSelector;
|
|
1763
|
+
if (expectedValue.ref === "relative") {
|
|
1764
|
+
if (!expectedValue.relativeTarget || !context.relativeBaseSelector) return void 0;
|
|
1765
|
+
const baseLocator = page2.locator(context.relativeBaseSelector);
|
|
1766
|
+
const count = await baseLocator.count();
|
|
1767
|
+
let idx = 0;
|
|
1768
|
+
if (expectedValue.relativeTarget === "first") idx = 0;
|
|
1769
|
+
else if (expectedValue.relativeTarget === "second") idx = 1;
|
|
1770
|
+
else if (expectedValue.relativeTarget === "last") idx = count - 1;
|
|
1771
|
+
else if (!isNaN(Number(expectedValue.relativeTarget))) idx = Number(expectedValue.relativeTarget);
|
|
1772
|
+
else idx = 0;
|
|
1773
|
+
if (idx < 0 || idx >= count) return void 0;
|
|
1774
|
+
const relElem = baseLocator.nth(idx);
|
|
1775
|
+
return await getPropertyFromLocator(relElem, expectedValue.property || expectedValue.attribute);
|
|
1776
|
+
} else {
|
|
1777
|
+
refSelector = selectors[expectedValue.ref];
|
|
1778
|
+
if (!refSelector) throw new Error(`Selector for ref '${expectedValue.ref}' not found in contract selectors.`);
|
|
1779
|
+
const refLocator = page2.locator(refSelector).first();
|
|
1780
|
+
return await getPropertyFromLocator(refLocator, expectedValue.property || expectedValue.attribute);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
async function getPropertyFromLocator(locator, property) {
|
|
1784
|
+
if (!locator) return void 0;
|
|
1785
|
+
if (!property || property === "id") {
|
|
1786
|
+
return await locator.getAttribute("id") ?? void 0;
|
|
1787
|
+
} else if (property === "class") {
|
|
1788
|
+
return await locator.getAttribute("class") ?? void 0;
|
|
1789
|
+
} else if (property === "textContent") {
|
|
1790
|
+
return await locator.evaluate((el) => el.textContent ?? void 0);
|
|
1791
|
+
} else if (property.startsWith("aria-")) {
|
|
1792
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
1793
|
+
} else if (property.endsWith("*")) {
|
|
1794
|
+
const attrs = await locator.evaluate((el) => {
|
|
1795
|
+
const out = [];
|
|
1796
|
+
for (const attr of Array.from(el.attributes)) {
|
|
1797
|
+
if (attr.name.startsWith("aria-")) out.push(`${attr.name}=${attr.value}`);
|
|
1798
|
+
}
|
|
1799
|
+
return out.join(";");
|
|
1800
|
+
});
|
|
1801
|
+
return attrs;
|
|
1802
|
+
} else {
|
|
1803
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1794
1806
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
1795
1807
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
1796
1808
|
if (test.target === "relative") continue;
|
|
@@ -1833,6 +1845,22 @@ This usually means:
|
|
|
1833
1845
|
}
|
|
1834
1846
|
return false;
|
|
1835
1847
|
};
|
|
1848
|
+
let expectedValue = test.expectedValue;
|
|
1849
|
+
if (test.expectedValue && typeof test.expectedValue === "object" && "ref" in test.expectedValue) {
|
|
1850
|
+
const context = {};
|
|
1851
|
+
const relTarget = test.relativeTarget;
|
|
1852
|
+
if (test.expectedValue.ref === "relative" && test.target === "relative" && relTarget) {
|
|
1853
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
1854
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
1855
|
+
context.relativeBaseSelector = baseSel;
|
|
1856
|
+
} else if (test.expectedValue.ref === "relative" && relTarget) {
|
|
1857
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
1858
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
1859
|
+
context.relativeBaseSelector = baseSel;
|
|
1860
|
+
}
|
|
1861
|
+
expectedValue = await resolveExpectedValue(test.expectedValue, componentContract.selectors, page, context);
|
|
1862
|
+
console.log("Expected value in static check", expectedValue);
|
|
1863
|
+
}
|
|
1836
1864
|
if (!test.expectedValue) {
|
|
1837
1865
|
const attributes = test.attribute.split(" | ");
|
|
1838
1866
|
let hasAny = false;
|
|
@@ -1866,16 +1894,17 @@ This usually means:
|
|
|
1866
1894
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1867
1895
|
}
|
|
1868
1896
|
} else {
|
|
1869
|
-
if (isRedundantCheck(targetSelector, test.attribute,
|
|
1870
|
-
passes.push(`${test.attribute}="${
|
|
1897
|
+
if (isRedundantCheck(targetSelector, test.attribute, typeof expectedValue === "string" ? expectedValue : void 0)) {
|
|
1898
|
+
passes.push(`${test.attribute}="${expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
|
|
1871
1899
|
staticPassed += 1;
|
|
1872
1900
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1873
1901
|
} else {
|
|
1902
|
+
const valueToCheck = expectedValue ?? "";
|
|
1874
1903
|
const result = await staticAssertionRunner.validateAttribute(
|
|
1875
1904
|
target,
|
|
1876
1905
|
test.target,
|
|
1877
1906
|
test.attribute,
|
|
1878
|
-
|
|
1907
|
+
valueToCheck,
|
|
1879
1908
|
test.failureMessage,
|
|
1880
1909
|
"Static ARIA Test"
|
|
1881
1910
|
);
|
|
@@ -1993,7 +2022,33 @@ This usually means:
|
|
|
1993
2022
|
continue;
|
|
1994
2023
|
}
|
|
1995
2024
|
for (const assertion of assertions) {
|
|
1996
|
-
|
|
2025
|
+
let expectedValue;
|
|
2026
|
+
if (assertion.expectedValue && typeof assertion.expectedValue === "object" && "ref" in assertion.expectedValue) {
|
|
2027
|
+
if (assertion.expectedValue.ref === "relative") {
|
|
2028
|
+
const { RelativeTargetResolver: RelativeTargetResolver2 } = await Promise.resolve().then(() => (init_RelativeTargetResolver(), RelativeTargetResolver_exports));
|
|
2029
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
2030
|
+
if (!relativeSelector) throw new Error("Relative selector not defined in contract selectors.");
|
|
2031
|
+
const relTarget = assertion.relativeTarget || "first";
|
|
2032
|
+
const relElem = await RelativeTargetResolver2.resolve(page, relativeSelector, relTarget);
|
|
2033
|
+
if (!relElem) throw new Error(`Could not resolve relative target '${relTarget}' for expectedValue.`);
|
|
2034
|
+
const prop = assertion.expectedValue.property || assertion.expectedValue.attribute || "id";
|
|
2035
|
+
if (prop === "textContent") {
|
|
2036
|
+
expectedValue = await relElem.evaluate((el) => el.textContent ?? void 0);
|
|
2037
|
+
} else {
|
|
2038
|
+
const attr = await relElem.getAttribute(prop);
|
|
2039
|
+
expectedValue = attr === null ? void 0 : attr;
|
|
2040
|
+
}
|
|
2041
|
+
} else {
|
|
2042
|
+
expectedValue = await resolveExpectedValue(assertion.expectedValue, componentContract.selectors, page, {});
|
|
2043
|
+
}
|
|
2044
|
+
} else if (typeof assertion.expectedValue === "string" || typeof assertion.expectedValue === "undefined") {
|
|
2045
|
+
expectedValue = assertion.expectedValue;
|
|
2046
|
+
} else {
|
|
2047
|
+
expectedValue = "";
|
|
2048
|
+
}
|
|
2049
|
+
const assertionToRun = { ...assertion, expectedValue };
|
|
2050
|
+
const valueToCheck = expectedValue ?? "";
|
|
2051
|
+
const result = await assertionRunner.validate({ ...assertionToRun, expectedValue: valueToCheck }, dynamicTest.description);
|
|
1997
2052
|
if (result.success && result.passMessage) {
|
|
1998
2053
|
passes.push(result.passMessage);
|
|
1999
2054
|
} else if (!result.success && result.failMessage) {
|
|
@@ -2065,7 +2120,6 @@ This usually means:
|
|
|
2065
2120
|
}
|
|
2066
2121
|
var init_contractTestRunnerPlaywright = __esm({
|
|
2067
2122
|
"src/utils/test/src/contractTestRunnerPlaywright.ts"() {
|
|
2068
|
-
init_contract();
|
|
2069
2123
|
init_playwrightTestHarness();
|
|
2070
2124
|
init_ComponentDetector();
|
|
2071
2125
|
init_ContractReporter();
|
|
@@ -2185,19 +2239,15 @@ var init_badgeHelper = __esm({
|
|
|
2185
2239
|
});
|
|
2186
2240
|
|
|
2187
2241
|
// src/utils/test/src/contractTestRunner.ts
|
|
2188
|
-
init_contract();
|
|
2189
2242
|
init_ContractReporter();
|
|
2190
2243
|
init_strictness();
|
|
2191
|
-
async function runContractTests(componentName, component, strictness) {
|
|
2244
|
+
async function runContractTests(contractPath, componentName, component, strictness) {
|
|
2192
2245
|
const reporter = new ContractReporter(false);
|
|
2193
2246
|
const strictnessMode = normalizeStrictness(strictness);
|
|
2194
|
-
const contractTyped = contract_default;
|
|
2195
|
-
const contractPath = contractTyped[componentName]?.path;
|
|
2196
2247
|
if (!contractPath) {
|
|
2197
|
-
throw new Error(`No contract
|
|
2248
|
+
throw new Error(`No contract path provided for component: ${componentName}`);
|
|
2198
2249
|
}
|
|
2199
|
-
const
|
|
2200
|
-
const contractData = await fs__default.default.readFile(resolvedPath, "utf-8");
|
|
2250
|
+
const contractData = await fs__default.default.readFile(contractPath, "utf-8");
|
|
2201
2251
|
const componentContract = JSON.parse(contractData);
|
|
2202
2252
|
const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
|
|
2203
2253
|
reporter.start(componentName, totalTests);
|
|
@@ -2332,7 +2382,7 @@ async function runContractTests(componentName, component, strictness) {
|
|
|
2332
2382
|
staticPassed += 1;
|
|
2333
2383
|
reporter.reportStaticTest(`${test.target} has ${test.attribute}`, "pass", void 0, staticLevel);
|
|
2334
2384
|
}
|
|
2335
|
-
} else if (!attributeValue || !test.expectedValue.split(" | ").includes(attributeValue)) {
|
|
2385
|
+
} else if (!attributeValue || typeof test.expectedValue === "string" && !test.expectedValue.split(" | ").includes(attributeValue)) {
|
|
2336
2386
|
const outcome = classifyFailure(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`, test.level);
|
|
2337
2387
|
if (outcome.status === "fail") staticFailed += 1;
|
|
2338
2388
|
if (outcome.status === "warn") staticWarnings += 1;
|
|
@@ -2430,7 +2480,16 @@ Please start your dev server and try again.`
|
|
|
2430
2480
|
}
|
|
2431
2481
|
} else if (component) {
|
|
2432
2482
|
console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
|
|
2433
|
-
|
|
2483
|
+
const contractPath = config.test?.components?.find((comp) => comp?.name === componentName)?.contractPath;
|
|
2484
|
+
if (!contractPath) {
|
|
2485
|
+
throw new Error(`\u274C No contract path found for component: ${componentName}`);
|
|
2486
|
+
}
|
|
2487
|
+
contract = await runContractTests(
|
|
2488
|
+
path3__default.default.resolve(configBaseDir, contractPath),
|
|
2489
|
+
componentName,
|
|
2490
|
+
component,
|
|
2491
|
+
strictness
|
|
2492
|
+
);
|
|
2434
2493
|
} else {
|
|
2435
2494
|
throw new Error("\u274C Either component or URL must be provided");
|
|
2436
2495
|
}
|