aria-ease 6.6.0 → 6.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -15
- package/bin/{chunk-LKN5PRYD.js → chunk-2TOYEY5L.js} +87 -35
- package/bin/chunk-VPBHLMAS.js +127 -0
- package/bin/cli.cjs +380 -231
- package/bin/cli.js +8 -123
- package/bin/configLoader-XRF6VM4J.js +7 -0
- package/{dist/contractTestRunnerPlaywright-PC6JOYYV.js → bin/contractTestRunnerPlaywright-UAOFNS7Z.js} +98 -59
- package/bin/{test-LP723IXM.js → test-WRIJHN6H.js} +65 -24
- package/dist/{chunk-LKN5PRYD.js → chunk-2TOYEY5L.js} +87 -35
- package/dist/configLoader-IT4PWCJB.js +128 -0
- package/{bin/contractTestRunnerPlaywright-PC6JOYYV.js → dist/contractTestRunnerPlaywright-UAOFNS7Z.js} +98 -59
- package/dist/index.cjs +404 -125
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +83 -29
- package/dist/src/menu/index.cjs +18 -5
- package/dist/src/menu/index.js +18 -5
- package/dist/src/utils/test/aria-contracts/accordion/accordion.contract.json +8 -8
- package/dist/src/utils/test/aria-contracts/combobox/combobox.listbox.contract.json +4 -4
- package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +44 -19
- package/dist/src/utils/test/aria-contracts/tabs/tabs.contract.json +3 -3
- package/dist/src/utils/test/{chunk-LKN5PRYD.js → chunk-2TOYEY5L.js} +85 -36
- package/dist/src/utils/test/configLoader-LD4RV2WQ.js +126 -0
- package/dist/src/utils/test/{contractTestRunnerPlaywright-RGKMGXND.js → contractTestRunnerPlaywright-IRJOAEMT.js} +94 -58
- package/dist/src/utils/test/index.cjs +380 -119
- package/dist/src/utils/test/index.d.cts +7 -1
- package/dist/src/utils/test/index.d.ts +7 -1
- package/dist/src/utils/test/index.js +61 -23
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -66,10 +66,11 @@ var init_ContractReporter = __esm({
|
|
|
66
66
|
componentName = "";
|
|
67
67
|
staticPasses = 0;
|
|
68
68
|
staticFailures = 0;
|
|
69
|
+
staticWarnings = 0;
|
|
69
70
|
dynamicResults = [];
|
|
70
71
|
totalTests = 0;
|
|
71
72
|
skipped = 0;
|
|
72
|
-
|
|
73
|
+
warnings = 0;
|
|
73
74
|
isPlaywright = false;
|
|
74
75
|
apgUrl = "https://www.w3.org/WAI/ARIA/apg/";
|
|
75
76
|
hasPrintedStaticSection = false;
|
|
@@ -96,23 +97,27 @@ ${"\u2550".repeat(60)}`);
|
|
|
96
97
|
this.log(`${"\u2550".repeat(60)}
|
|
97
98
|
`);
|
|
98
99
|
}
|
|
99
|
-
reportStatic(passes, failures) {
|
|
100
|
+
reportStatic(passes, failures, warnings = 0) {
|
|
100
101
|
this.staticPasses = passes;
|
|
101
102
|
this.staticFailures = failures;
|
|
103
|
+
this.staticWarnings = warnings;
|
|
102
104
|
}
|
|
103
105
|
/**
|
|
104
106
|
* Report individual static test pass
|
|
105
107
|
*/
|
|
106
|
-
reportStaticTest(description,
|
|
108
|
+
reportStaticTest(description, status, failureMessage, level) {
|
|
107
109
|
if (!this.hasPrintedStaticSection) {
|
|
108
110
|
this.log(`${"\u2500".repeat(60)}`);
|
|
109
111
|
this.log(`\u{1F9EA} Static Assertions`);
|
|
110
112
|
this.log(`${"\u2500".repeat(60)}`);
|
|
111
113
|
this.hasPrintedStaticSection = true;
|
|
112
114
|
}
|
|
113
|
-
const icon =
|
|
115
|
+
const icon = status === "pass" ? "\u2713" : status === "warn" ? "\u26A0" : status === "skip" ? "\u25CB" : "\u2717";
|
|
114
116
|
this.log(` ${icon} ${description}`);
|
|
115
|
-
if (
|
|
117
|
+
if (level) {
|
|
118
|
+
this.log(` \u21B3 level=${level}`);
|
|
119
|
+
}
|
|
120
|
+
if ((status === "fail" || status === "warn" || status === "skip") && failureMessage) {
|
|
116
121
|
this.log(` \u21B3 ${failureMessage}`);
|
|
117
122
|
}
|
|
118
123
|
}
|
|
@@ -131,23 +136,26 @@ ${"\u2550".repeat(60)}`);
|
|
|
131
136
|
description: test.description,
|
|
132
137
|
status,
|
|
133
138
|
failureMessage,
|
|
134
|
-
|
|
139
|
+
level: test.level
|
|
135
140
|
};
|
|
136
141
|
if (status === "skip") {
|
|
137
142
|
result.skipReason = "Requires real browser (addEventListener events)";
|
|
138
143
|
}
|
|
139
144
|
this.dynamicResults.push(result);
|
|
140
|
-
const icons = { pass: "\u2713", fail: "\u2717",
|
|
141
|
-
const
|
|
142
|
-
this.log(` ${icons[status]} ${
|
|
145
|
+
const icons = { pass: "\u2713", fail: "\u2717", warn: "\u26A0", skip: "\u25CB" };
|
|
146
|
+
const levelPrefix = test.level ? `[${test.level.toUpperCase()}] ` : "";
|
|
147
|
+
this.log(` ${icons[status]} ${levelPrefix}${test.description}`);
|
|
143
148
|
if (status === "skip" && !this.isPlaywright) {
|
|
144
149
|
this.log(` \u21B3 Skipped in jsdom (runs in Playwright)`);
|
|
145
150
|
}
|
|
146
|
-
if (status === "fail" && failureMessage
|
|
151
|
+
if (status === "fail" && failureMessage) {
|
|
152
|
+
this.log(` \u21B3 ${failureMessage}`);
|
|
153
|
+
}
|
|
154
|
+
if (status === "warn" && failureMessage) {
|
|
147
155
|
this.log(` \u21B3 ${failureMessage}`);
|
|
148
156
|
}
|
|
149
|
-
if (status === "
|
|
150
|
-
this.log(` \u21B3
|
|
157
|
+
if (status === "skip" && failureMessage) {
|
|
158
|
+
this.log(` \u21B3 ${failureMessage}`);
|
|
151
159
|
}
|
|
152
160
|
}
|
|
153
161
|
/**
|
|
@@ -171,29 +179,29 @@ ${"\u2500".repeat(60)}`);
|
|
|
171
179
|
this.log("");
|
|
172
180
|
});
|
|
173
181
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
reportOptionalSuggestions() {
|
|
178
|
-
const suggestions = this.dynamicResults.filter((r) => r.status === "optional-fail");
|
|
179
|
-
if (suggestions.length === 0) return;
|
|
182
|
+
reportWarnings() {
|
|
183
|
+
const warnings = this.dynamicResults.filter((r) => r.status === "warn");
|
|
184
|
+
if (warnings.length === 0 && this.staticWarnings === 0) return;
|
|
180
185
|
this.log(`
|
|
181
186
|
${"\u2500".repeat(60)}`);
|
|
182
|
-
this.log(`\
|
|
187
|
+
this.log(`\u26A0\uFE0F Warnings (${this.staticWarnings + warnings.length}):
|
|
183
188
|
`);
|
|
184
|
-
this.log(`These
|
|
185
|
-
this.log(`for improved user experience and keyboard interaction:
|
|
189
|
+
this.log(`These checks are failing but treated as warnings under the active strictness mode.
|
|
186
190
|
`);
|
|
187
|
-
|
|
191
|
+
warnings.forEach((test, index) => {
|
|
188
192
|
this.log(`${index + 1}. ${test.description}`);
|
|
189
193
|
if (test.failureMessage) {
|
|
190
194
|
this.log(` \u21B3 ${test.failureMessage}`);
|
|
191
195
|
}
|
|
196
|
+
if (test.level) {
|
|
197
|
+
this.log(` \u21B3 level=${test.level}`);
|
|
198
|
+
}
|
|
192
199
|
});
|
|
193
|
-
this.
|
|
194
|
-
|
|
195
|
-
|
|
200
|
+
if (this.apgUrl) {
|
|
201
|
+
this.log(`
|
|
202
|
+
Reference: ${this.apgUrl}
|
|
196
203
|
`);
|
|
204
|
+
}
|
|
197
205
|
}
|
|
198
206
|
/**
|
|
199
207
|
* Report skipped tests with helpful context
|
|
@@ -223,41 +231,42 @@ ${"\u2500".repeat(60)}`);
|
|
|
223
231
|
const duration = Date.now() - this.startTime;
|
|
224
232
|
const dynamicPasses = this.dynamicResults.filter((r) => r.status === "pass").length;
|
|
225
233
|
const dynamicFailures = this.dynamicResults.filter((r) => r.status === "fail").length;
|
|
234
|
+
const dynamicWarnings = this.dynamicResults.filter((r) => r.status === "warn").length;
|
|
226
235
|
this.skipped = this.dynamicResults.filter((r) => r.status === "skip").length;
|
|
227
|
-
this.
|
|
236
|
+
this.warnings = this.staticWarnings + dynamicWarnings;
|
|
228
237
|
const totalPasses = this.staticPasses + dynamicPasses;
|
|
229
238
|
const totalFailures = this.staticFailures + dynamicFailures;
|
|
230
|
-
const totalRun = totalPasses + totalFailures;
|
|
239
|
+
const totalRun = totalPasses + totalFailures + this.warnings;
|
|
231
240
|
if (failures.length > 0) {
|
|
232
241
|
this.reportFailures(failures);
|
|
233
242
|
}
|
|
234
|
-
this.
|
|
243
|
+
this.reportWarnings();
|
|
235
244
|
this.reportSkipped();
|
|
236
245
|
this.log(`
|
|
237
246
|
${"\u2550".repeat(60)}`);
|
|
238
247
|
this.log(`\u{1F4CA} Summary
|
|
239
248
|
`);
|
|
240
|
-
if (totalFailures === 0 && this.skipped === 0 && this.
|
|
249
|
+
if (totalFailures === 0 && this.skipped === 0 && this.warnings === 0) {
|
|
241
250
|
this.log(`\u2705 All ${totalRun} tests passed!`);
|
|
242
251
|
this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
|
|
243
252
|
} else if (totalFailures === 0) {
|
|
244
|
-
this.log(`\u2705 ${totalPasses}/${totalRun}
|
|
253
|
+
this.log(`\u2705 ${totalPasses}/${totalRun} tests passed`);
|
|
245
254
|
if (this.skipped > 0) {
|
|
246
255
|
this.log(`\u25CB ${this.skipped} tests skipped`);
|
|
247
256
|
}
|
|
248
|
-
if (this.
|
|
249
|
-
this.log(`\
|
|
257
|
+
if (this.warnings > 0) {
|
|
258
|
+
this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings > 1 ? "s" : ""}`);
|
|
250
259
|
}
|
|
251
260
|
this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
|
|
252
261
|
} else {
|
|
253
262
|
this.log(`\u274C ${totalFailures} test${totalFailures > 1 ? "s" : ""} failed`);
|
|
254
263
|
this.log(`\u2705 ${totalPasses} test${totalPasses > 1 ? "s" : ""} passed`);
|
|
264
|
+
if (this.warnings > 0) {
|
|
265
|
+
this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings > 1 ? "s" : ""}`);
|
|
266
|
+
}
|
|
255
267
|
if (this.skipped > 0) {
|
|
256
268
|
this.log(`\u25CB ${this.skipped} test${this.skipped > 1 ? "s" : ""} skipped`);
|
|
257
269
|
}
|
|
258
|
-
if (this.optionalSuggestions > 0) {
|
|
259
|
-
this.log(`\u{1F4A1} ${this.optionalSuggestions} optional enhancement${this.optionalSuggestions > 1 ? "s" : ""} suggested`);
|
|
260
|
-
}
|
|
261
270
|
}
|
|
262
271
|
this.log(`\u23F1\uFE0F Duration: ${duration}ms`);
|
|
263
272
|
this.log(`${"\u2550".repeat(60)}
|
|
@@ -294,6 +303,52 @@ ${"\u2550".repeat(60)}`);
|
|
|
294
303
|
}
|
|
295
304
|
});
|
|
296
305
|
|
|
306
|
+
// src/utils/test/src/strictness.ts
|
|
307
|
+
function normalizeLevel(level) {
|
|
308
|
+
if (level === "required" || level === "recommended" || level === "optional") {
|
|
309
|
+
return level;
|
|
310
|
+
}
|
|
311
|
+
return FALLBACK_LEVEL;
|
|
312
|
+
}
|
|
313
|
+
function normalizeStrictness(strictness) {
|
|
314
|
+
if (strictness === "minimal" || strictness === "balanced" || strictness === "strict" || strictness === "paranoid") {
|
|
315
|
+
return strictness;
|
|
316
|
+
}
|
|
317
|
+
return "balanced";
|
|
318
|
+
}
|
|
319
|
+
function resolveEnforcement(level, strictness) {
|
|
320
|
+
const matrix = {
|
|
321
|
+
minimal: {
|
|
322
|
+
required: "error",
|
|
323
|
+
recommended: "ignore",
|
|
324
|
+
optional: "ignore"
|
|
325
|
+
},
|
|
326
|
+
balanced: {
|
|
327
|
+
required: "error",
|
|
328
|
+
recommended: "warning",
|
|
329
|
+
optional: "ignore"
|
|
330
|
+
},
|
|
331
|
+
strict: {
|
|
332
|
+
required: "error",
|
|
333
|
+
recommended: "error",
|
|
334
|
+
optional: "warning"
|
|
335
|
+
},
|
|
336
|
+
paranoid: {
|
|
337
|
+
required: "error",
|
|
338
|
+
recommended: "error",
|
|
339
|
+
optional: "error"
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
return matrix[strictness][level];
|
|
343
|
+
}
|
|
344
|
+
var FALLBACK_LEVEL;
|
|
345
|
+
var init_strictness = __esm({
|
|
346
|
+
"src/utils/test/src/strictness.ts"() {
|
|
347
|
+
"use strict";
|
|
348
|
+
FALLBACK_LEVEL = "required";
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
297
352
|
// src/utils/test/src/playwrightTestHarness.ts
|
|
298
353
|
async function getOrCreateBrowser() {
|
|
299
354
|
if (!sharedBrowser) {
|
|
@@ -344,6 +399,140 @@ var init_playwrightTestHarness = __esm({
|
|
|
344
399
|
}
|
|
345
400
|
});
|
|
346
401
|
|
|
402
|
+
// src/utils/cli/configLoader.ts
|
|
403
|
+
var configLoader_exports = {};
|
|
404
|
+
__export(configLoader_exports, {
|
|
405
|
+
loadConfig: () => loadConfig
|
|
406
|
+
});
|
|
407
|
+
function validateConfig(config) {
|
|
408
|
+
const errors = [];
|
|
409
|
+
if (!config || typeof config !== "object") {
|
|
410
|
+
errors.push("Config must be an object");
|
|
411
|
+
return { valid: false, errors };
|
|
412
|
+
}
|
|
413
|
+
const cfg = config;
|
|
414
|
+
if (cfg.audit !== void 0) {
|
|
415
|
+
if (typeof cfg.audit !== "object" || cfg.audit === null) {
|
|
416
|
+
errors.push("audit must be an object");
|
|
417
|
+
} else {
|
|
418
|
+
if (cfg.audit.urls !== void 0) {
|
|
419
|
+
if (!Array.isArray(cfg.audit.urls)) {
|
|
420
|
+
errors.push("audit.urls must be an array");
|
|
421
|
+
} else if (cfg.audit.urls.some((url) => typeof url !== "string")) {
|
|
422
|
+
errors.push("audit.urls must contain only strings");
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (cfg.audit.output !== void 0) {
|
|
426
|
+
if (typeof cfg.audit.output !== "object") {
|
|
427
|
+
errors.push("audit.output must be an object");
|
|
428
|
+
} else {
|
|
429
|
+
const output = cfg.audit.output;
|
|
430
|
+
if (output.format !== void 0) {
|
|
431
|
+
if (!["json", "csv", "html", "all"].includes(output.format)) {
|
|
432
|
+
errors.push("audit.output.format must be one of: json, csv, html, all");
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (output.out !== void 0 && typeof output.out !== "string") {
|
|
436
|
+
errors.push("audit.output.out must be a string");
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (cfg.test !== void 0) {
|
|
443
|
+
if (typeof cfg.test !== "object" || cfg.test === null) {
|
|
444
|
+
errors.push("test must be an object");
|
|
445
|
+
} else {
|
|
446
|
+
if (cfg.test.components !== void 0) {
|
|
447
|
+
if (!Array.isArray(cfg.test.components)) {
|
|
448
|
+
errors.push("test.components must be an array");
|
|
449
|
+
} else {
|
|
450
|
+
cfg.test.components.forEach((comp, idx) => {
|
|
451
|
+
if (typeof comp !== "object" || comp === null) {
|
|
452
|
+
errors.push(`test.components[${idx}] must be an object`);
|
|
453
|
+
} else {
|
|
454
|
+
if (typeof comp.name !== "string") {
|
|
455
|
+
errors.push(`test.components[${idx}].name must be a string`);
|
|
456
|
+
}
|
|
457
|
+
if (comp.path !== void 0 && typeof comp.path !== "string") {
|
|
458
|
+
errors.push(`test.components[${idx}].path must be a string when provided`);
|
|
459
|
+
}
|
|
460
|
+
if (comp.strictness !== void 0 && !["minimal", "balanced", "strict", "paranoid"].includes(comp.strictness)) {
|
|
461
|
+
errors.push(`test.components[${idx}].strictness must be one of: minimal, balanced, strict, paranoid`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (cfg.test.strictness !== void 0) {
|
|
468
|
+
if (!["minimal", "balanced", "strict", "paranoid"].includes(cfg.test.strictness)) {
|
|
469
|
+
errors.push("test.strictness must be one of: minimal, balanced, strict, paranoid");
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return { valid: errors.length === 0, errors };
|
|
475
|
+
}
|
|
476
|
+
async function loadConfigFile(filePath) {
|
|
477
|
+
try {
|
|
478
|
+
const ext = import_path.default.extname(filePath);
|
|
479
|
+
if (ext === ".json") {
|
|
480
|
+
const content = await import_fs_extra.default.readFile(filePath, "utf-8");
|
|
481
|
+
return JSON.parse(content);
|
|
482
|
+
} else if ([".js", ".mjs", ".cjs", ".ts"].includes(ext)) {
|
|
483
|
+
const imported = await import(filePath);
|
|
484
|
+
return imported.default || imported;
|
|
485
|
+
}
|
|
486
|
+
return null;
|
|
487
|
+
} catch {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async function loadConfig(cwd = process.cwd()) {
|
|
492
|
+
const configNames = [
|
|
493
|
+
"ariaease.config.js",
|
|
494
|
+
"ariaease.config.mjs",
|
|
495
|
+
"ariaease.config.cjs",
|
|
496
|
+
"ariaease.config.json",
|
|
497
|
+
"ariaease.config.ts"
|
|
498
|
+
];
|
|
499
|
+
let loadedConfig = null;
|
|
500
|
+
let foundPath = null;
|
|
501
|
+
const errors = [];
|
|
502
|
+
for (const name of configNames) {
|
|
503
|
+
const configPath = import_path.default.resolve(cwd, name);
|
|
504
|
+
if (await import_fs_extra.default.pathExists(configPath)) {
|
|
505
|
+
foundPath = configPath;
|
|
506
|
+
loadedConfig = await loadConfigFile(configPath);
|
|
507
|
+
if (loadedConfig === null) {
|
|
508
|
+
errors.push(`Found config at ${name} but failed to load it. Check for syntax errors.`);
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const validation = validateConfig(loadedConfig);
|
|
512
|
+
if (!validation.valid) {
|
|
513
|
+
errors.push(`Config validation failed in ${name}:`);
|
|
514
|
+
errors.push(...validation.errors.map((err) => ` - ${err}`));
|
|
515
|
+
loadedConfig = null;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
config: loadedConfig || {},
|
|
523
|
+
configPath: loadedConfig ? foundPath : null,
|
|
524
|
+
errors
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
var import_path, import_fs_extra;
|
|
528
|
+
var init_configLoader = __esm({
|
|
529
|
+
"src/utils/cli/configLoader.ts"() {
|
|
530
|
+
"use strict";
|
|
531
|
+
import_path = __toESM(require("path"), 1);
|
|
532
|
+
import_fs_extra = __toESM(require("fs-extra"), 1);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
|
|
347
536
|
// node_modules/@playwright/test/index.mjs
|
|
348
537
|
var test_exports = {};
|
|
349
538
|
__export(test_exports, {
|
|
@@ -679,9 +868,6 @@ var init_ActionExecutor = __esm({
|
|
|
679
868
|
this.selectors = selectors;
|
|
680
869
|
this.timeoutMs = timeoutMs;
|
|
681
870
|
}
|
|
682
|
-
isOptionalMenuTarget(target) {
|
|
683
|
-
return ["submenu", "submenuTrigger", "submenuItems"].includes(target);
|
|
684
|
-
}
|
|
685
871
|
/**
|
|
686
872
|
* Check if error is due to browser/page being closed
|
|
687
873
|
*/
|
|
@@ -813,10 +999,9 @@ var init_ActionExecutor = __esm({
|
|
|
813
999
|
const locator = this.page.locator(selector).first();
|
|
814
1000
|
const elementCount = await locator.count();
|
|
815
1001
|
if (elementCount === 0) {
|
|
816
|
-
const optionalMenuTarget = this.isOptionalMenuTarget(target);
|
|
817
1002
|
return {
|
|
818
1003
|
success: false,
|
|
819
|
-
error:
|
|
1004
|
+
error: `${target} element not found.`,
|
|
820
1005
|
shouldBreak: true
|
|
821
1006
|
// Signal to skip this test
|
|
822
1007
|
};
|
|
@@ -1130,10 +1315,11 @@ var contractTestRunnerPlaywright_exports = {};
|
|
|
1130
1315
|
__export(contractTestRunnerPlaywright_exports, {
|
|
1131
1316
|
runContractTestsPlaywright: () => runContractTestsPlaywright
|
|
1132
1317
|
});
|
|
1133
|
-
async function runContractTestsPlaywright(componentName, url) {
|
|
1318
|
+
async function runContractTestsPlaywright(componentName, url, strictness) {
|
|
1134
1319
|
const reporter = new ContractReporter(true);
|
|
1135
1320
|
const actionTimeoutMs = 400;
|
|
1136
1321
|
const assertionTimeoutMs = 400;
|
|
1322
|
+
const strictnessMode = normalizeStrictness(strictness);
|
|
1137
1323
|
const contractTyped = contract_default;
|
|
1138
1324
|
const contractPath = contractTyped[componentName]?.path;
|
|
1139
1325
|
const resolvedPath = new URL(contractPath, import_meta3.url).pathname;
|
|
@@ -1142,9 +1328,25 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
1142
1328
|
const totalTests = componentContract.static[0].assertions.length + componentContract.dynamic.length;
|
|
1143
1329
|
const apgUrl = componentContract.meta?.source?.apg;
|
|
1144
1330
|
const failures = [];
|
|
1331
|
+
const warnings = [];
|
|
1145
1332
|
const passes = [];
|
|
1146
1333
|
const skipped = [];
|
|
1147
1334
|
let page = null;
|
|
1335
|
+
const classifyFailure = (message, levelRaw) => {
|
|
1336
|
+
const level = normalizeLevel(levelRaw);
|
|
1337
|
+
const enforcement = resolveEnforcement(level, strictnessMode);
|
|
1338
|
+
if (enforcement === "error") {
|
|
1339
|
+
failures.push(message);
|
|
1340
|
+
return { status: "fail", level, detail: message };
|
|
1341
|
+
}
|
|
1342
|
+
if (enforcement === "warning") {
|
|
1343
|
+
warnings.push(message);
|
|
1344
|
+
return { status: "warn", level, detail: message };
|
|
1345
|
+
}
|
|
1346
|
+
const ignoredMessage = `${message} (ignored by strictness=${strictnessMode}, level=${level})`;
|
|
1347
|
+
skipped.push(ignoredMessage);
|
|
1348
|
+
return { status: "skip", level, detail: ignoredMessage };
|
|
1349
|
+
};
|
|
1148
1350
|
try {
|
|
1149
1351
|
page = await createTestPage();
|
|
1150
1352
|
if (url) {
|
|
@@ -1190,35 +1392,37 @@ This usually means:
|
|
|
1190
1392
|
});
|
|
1191
1393
|
}
|
|
1192
1394
|
const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
|
|
1395
|
+
let staticPassed = 0;
|
|
1193
1396
|
let staticFailed = 0;
|
|
1397
|
+
let staticWarnings = 0;
|
|
1194
1398
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
1195
1399
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
1196
1400
|
if (test.target === "relative") continue;
|
|
1197
1401
|
const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
|
|
1402
|
+
const staticLevel = normalizeLevel(test.level);
|
|
1198
1403
|
if (componentName === "menu" && test.target === "submenuTrigger" && !hasSubmenuCapability) {
|
|
1199
|
-
|
|
1200
|
-
|
|
1404
|
+
const skipMessage = `Skipping submenu static assertion for ${test.target}: no submenu capability detected in rendered component.`;
|
|
1405
|
+
skipped.push(skipMessage);
|
|
1406
|
+
reporter.reportStaticTest(staticDescription, "skip", skipMessage, staticLevel);
|
|
1201
1407
|
continue;
|
|
1202
1408
|
}
|
|
1203
1409
|
const targetSelector = componentContract.selectors[test.target];
|
|
1204
1410
|
if (!targetSelector) {
|
|
1205
1411
|
const failure = `Selector for target ${test.target} not found.`;
|
|
1206
|
-
|
|
1207
|
-
staticFailed += 1;
|
|
1208
|
-
|
|
1412
|
+
const outcome = classifyFailure(failure, test.level);
|
|
1413
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1414
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1415
|
+
reporter.reportStaticTest(staticDescription, outcome.status, outcome.detail, outcome.level);
|
|
1209
1416
|
continue;
|
|
1210
1417
|
}
|
|
1211
1418
|
const target = page.locator(targetSelector).first();
|
|
1212
1419
|
const exists = await target.count() > 0;
|
|
1213
1420
|
if (!exists) {
|
|
1214
|
-
if (test.isOptional === true) {
|
|
1215
|
-
reporter.reportStaticTest(staticDescription, true);
|
|
1216
|
-
continue;
|
|
1217
|
-
}
|
|
1218
1421
|
const failure = `Target ${test.target} not found.`;
|
|
1219
|
-
|
|
1220
|
-
staticFailed += 1;
|
|
1221
|
-
|
|
1422
|
+
const outcome = classifyFailure(failure, test.level);
|
|
1423
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1424
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1425
|
+
reporter.reportStaticTest(staticDescription, outcome.status, outcome.detail, outcome.level);
|
|
1222
1426
|
continue;
|
|
1223
1427
|
}
|
|
1224
1428
|
const isRedundantCheck = (selector, attrName, expectedVal) => {
|
|
@@ -1253,19 +1457,23 @@ This usually means:
|
|
|
1253
1457
|
}
|
|
1254
1458
|
if (!hasAny && !allRedundant) {
|
|
1255
1459
|
const failure = test.failureMessage + ` None of the attributes "${test.attribute}" are present.`;
|
|
1256
|
-
|
|
1257
|
-
staticFailed += 1;
|
|
1258
|
-
|
|
1460
|
+
const outcome = classifyFailure(failure, test.level);
|
|
1461
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1462
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1463
|
+
reporter.reportStaticTest(staticDescription, outcome.status, outcome.detail, outcome.level);
|
|
1259
1464
|
} else if (!allRedundant && hasAny) {
|
|
1260
1465
|
passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
|
|
1261
|
-
|
|
1466
|
+
staticPassed += 1;
|
|
1467
|
+
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1262
1468
|
} else {
|
|
1263
|
-
|
|
1469
|
+
staticPassed += 1;
|
|
1470
|
+
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1264
1471
|
}
|
|
1265
1472
|
} else {
|
|
1266
1473
|
if (isRedundantCheck(targetSelector, test.attribute, test.expectedValue)) {
|
|
1267
1474
|
passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
|
|
1268
|
-
|
|
1475
|
+
staticPassed += 1;
|
|
1476
|
+
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1269
1477
|
} else {
|
|
1270
1478
|
const result = await staticAssertionRunner.validateAttribute(
|
|
1271
1479
|
target,
|
|
@@ -1277,11 +1485,13 @@ This usually means:
|
|
|
1277
1485
|
);
|
|
1278
1486
|
if (result.success && result.passMessage) {
|
|
1279
1487
|
passes.push(result.passMessage);
|
|
1280
|
-
|
|
1488
|
+
staticPassed += 1;
|
|
1489
|
+
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1281
1490
|
} else if (!result.success && result.failMessage) {
|
|
1282
|
-
|
|
1283
|
-
staticFailed += 1;
|
|
1284
|
-
|
|
1491
|
+
const outcome = classifyFailure(result.failMessage, test.level);
|
|
1492
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1493
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1494
|
+
reporter.reportStaticTest(staticDescription, outcome.status, outcome.detail, outcome.level);
|
|
1285
1495
|
}
|
|
1286
1496
|
}
|
|
1287
1497
|
}
|
|
@@ -1296,6 +1506,9 @@ This usually means:
|
|
|
1296
1506
|
}
|
|
1297
1507
|
const { action, assertions } = dynamicTest;
|
|
1298
1508
|
const failuresBeforeTest = failures.length;
|
|
1509
|
+
const warningsBeforeTest = warnings.length;
|
|
1510
|
+
const skippedBeforeTest = skipped.length;
|
|
1511
|
+
const dynamicLevel = normalizeLevel(dynamicTest.level);
|
|
1299
1512
|
try {
|
|
1300
1513
|
await strategy.resetState(page);
|
|
1301
1514
|
} catch (error) {
|
|
@@ -1305,13 +1518,15 @@ This usually means:
|
|
|
1305
1518
|
}
|
|
1306
1519
|
const shouldSkipTest = await strategy.shouldSkipTest(dynamicTest, page);
|
|
1307
1520
|
if (shouldSkipTest) {
|
|
1308
|
-
|
|
1521
|
+
const skipMessage = `Skipping test - component-specific conditions not met`;
|
|
1522
|
+
skipped.push(skipMessage);
|
|
1523
|
+
reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, "skip", skipMessage);
|
|
1309
1524
|
continue;
|
|
1310
1525
|
}
|
|
1311
1526
|
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
1312
1527
|
const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
1313
|
-
let shouldSkipCurrentTest = false;
|
|
1314
1528
|
let shouldAbortCurrentTest = false;
|
|
1529
|
+
let actionOutcome = null;
|
|
1315
1530
|
for (const act of action) {
|
|
1316
1531
|
if (!page || page.isClosed()) {
|
|
1317
1532
|
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
@@ -1333,27 +1548,20 @@ This usually means:
|
|
|
1333
1548
|
continue;
|
|
1334
1549
|
}
|
|
1335
1550
|
if (!result.success) {
|
|
1336
|
-
if (result.shouldBreak) {
|
|
1337
|
-
if (result.error?.includes("optional submenu test")) {
|
|
1338
|
-
reporter.reportTest(dynamicTest, "skip", result.error);
|
|
1339
|
-
shouldSkipCurrentTest = true;
|
|
1340
|
-
} else if (result.error) {
|
|
1341
|
-
failures.push(result.error);
|
|
1342
|
-
shouldAbortCurrentTest = true;
|
|
1343
|
-
}
|
|
1344
|
-
break;
|
|
1345
|
-
}
|
|
1346
1551
|
if (result.error) {
|
|
1347
|
-
|
|
1552
|
+
const outcome = classifyFailure(result.error, dynamicTest.level);
|
|
1553
|
+
actionOutcome = { status: outcome.status, detail: outcome.detail };
|
|
1348
1554
|
}
|
|
1349
|
-
|
|
1555
|
+
shouldAbortCurrentTest = true;
|
|
1556
|
+
break;
|
|
1350
1557
|
}
|
|
1351
1558
|
}
|
|
1352
|
-
if (shouldSkipCurrentTest) {
|
|
1353
|
-
continue;
|
|
1354
|
-
}
|
|
1355
1559
|
if (shouldAbortCurrentTest) {
|
|
1356
|
-
reporter.reportTest(
|
|
1560
|
+
reporter.reportTest(
|
|
1561
|
+
{ description: dynamicTest.description, level: dynamicLevel },
|
|
1562
|
+
actionOutcome?.status || "fail",
|
|
1563
|
+
actionOutcome?.detail || failures[failures.length - 1]
|
|
1564
|
+
);
|
|
1357
1565
|
continue;
|
|
1358
1566
|
}
|
|
1359
1567
|
for (const assertion of assertions) {
|
|
@@ -1361,22 +1569,39 @@ This usually means:
|
|
|
1361
1569
|
if (result.success && result.passMessage) {
|
|
1362
1570
|
passes.push(result.passMessage);
|
|
1363
1571
|
} else if (!result.success && result.failMessage) {
|
|
1364
|
-
|
|
1572
|
+
const assertionLevel = normalizeLevel(assertion.level || dynamicTest.level);
|
|
1573
|
+
const outcome = classifyFailure(result.failMessage, assertionLevel);
|
|
1574
|
+
if (outcome.status === "skip") {
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1365
1577
|
}
|
|
1366
1578
|
}
|
|
1367
1579
|
const failuresAfterTest = failures.length;
|
|
1368
|
-
const
|
|
1369
|
-
const
|
|
1370
|
-
if (
|
|
1371
|
-
|
|
1372
|
-
|
|
1580
|
+
const warningsAfterTest = warnings.length;
|
|
1581
|
+
const skippedAfterTest = skipped.length;
|
|
1582
|
+
if (failuresAfterTest > failuresBeforeTest) {
|
|
1583
|
+
reporter.reportTest(
|
|
1584
|
+
{ description: dynamicTest.description, level: dynamicLevel },
|
|
1585
|
+
"fail",
|
|
1586
|
+
failures[failures.length - 1]
|
|
1587
|
+
);
|
|
1588
|
+
} else if (warningsAfterTest > warningsBeforeTest) {
|
|
1589
|
+
reporter.reportTest(
|
|
1590
|
+
{ description: dynamicTest.description, level: dynamicLevel },
|
|
1591
|
+
"warn",
|
|
1592
|
+
warnings[warnings.length - 1]
|
|
1593
|
+
);
|
|
1594
|
+
} else if (skippedAfterTest > skippedBeforeTest) {
|
|
1595
|
+
reporter.reportTest(
|
|
1596
|
+
{ description: dynamicTest.description, level: dynamicLevel },
|
|
1597
|
+
"skip",
|
|
1598
|
+
skipped[skipped.length - 1]
|
|
1599
|
+
);
|
|
1373
1600
|
} else {
|
|
1374
|
-
reporter.reportTest(dynamicTest,
|
|
1601
|
+
reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, "pass");
|
|
1375
1602
|
}
|
|
1376
1603
|
}
|
|
1377
|
-
|
|
1378
|
-
const staticPassed = Math.max(0, staticTotal - staticFailed);
|
|
1379
|
-
reporter.reportStatic(staticPassed, staticFailed);
|
|
1604
|
+
reporter.reportStatic(staticPassed, staticFailed, staticWarnings);
|
|
1380
1605
|
reporter.summary(failures);
|
|
1381
1606
|
} catch (error) {
|
|
1382
1607
|
if (error instanceof Error) {
|
|
@@ -1401,7 +1626,7 @@ Make sure your dev server is running at ${url}`);
|
|
|
1401
1626
|
} finally {
|
|
1402
1627
|
if (page) await page.close();
|
|
1403
1628
|
}
|
|
1404
|
-
return { passes, failures, skipped };
|
|
1629
|
+
return { passes, failures, skipped, warnings };
|
|
1405
1630
|
}
|
|
1406
1631
|
var import_fs2, import_meta3;
|
|
1407
1632
|
var init_contractTestRunnerPlaywright = __esm({
|
|
@@ -1414,6 +1639,7 @@ var init_contractTestRunnerPlaywright = __esm({
|
|
|
1414
1639
|
init_ContractReporter();
|
|
1415
1640
|
init_ActionExecutor();
|
|
1416
1641
|
init_AssertionRunner();
|
|
1642
|
+
init_strictness();
|
|
1417
1643
|
import_meta3 = {};
|
|
1418
1644
|
}
|
|
1419
1645
|
});
|
|
@@ -1439,13 +1665,13 @@ function displayBadgeInfo(badgeType) {
|
|
|
1439
1665
|
console.log(import_chalk.default.dim("\n This helps others discover accessibility tools and shows you care!\n"));
|
|
1440
1666
|
}
|
|
1441
1667
|
async function promptAddBadge(badgeType, cwd = process.cwd()) {
|
|
1442
|
-
const readmePath =
|
|
1443
|
-
const readmeExists = await
|
|
1668
|
+
const readmePath = import_path2.default.join(cwd, "README.md");
|
|
1669
|
+
const readmeExists = await import_fs_extra2.default.pathExists(readmePath);
|
|
1444
1670
|
if (!readmeExists) {
|
|
1445
1671
|
console.log(import_chalk.default.yellow(" \u2139\uFE0F No README.md found in current directory"));
|
|
1446
1672
|
return;
|
|
1447
1673
|
}
|
|
1448
|
-
const readmeContent = await
|
|
1674
|
+
const readmeContent = await import_fs_extra2.default.readFile(readmePath, "utf-8");
|
|
1449
1675
|
const markdown = getBadgeMarkdown(badgeType);
|
|
1450
1676
|
if (readmeContent.includes(markdown) || readmeContent.includes(BADGE_CONFIGS[badgeType].fileName)) {
|
|
1451
1677
|
console.log(import_chalk.default.gray(" \u2713 Badge already in README.md"));
|
|
@@ -1489,7 +1715,7 @@ async function addBadgeToReadme(readmePath, content, badge) {
|
|
|
1489
1715
|
insertIndex = 1;
|
|
1490
1716
|
}
|
|
1491
1717
|
lines.splice(insertIndex, 0, badge);
|
|
1492
|
-
await
|
|
1718
|
+
await import_fs_extra2.default.writeFile(readmePath, lines.join("\n"), "utf-8");
|
|
1493
1719
|
}
|
|
1494
1720
|
function displayAllBadges() {
|
|
1495
1721
|
console.log(import_chalk.default.cyan("\n\u{1F4CD} Available badges:"));
|
|
@@ -1501,12 +1727,12 @@ function displayAllBadges() {
|
|
|
1501
1727
|
console.log(import_chalk.default.green(" " + getBadgeMarkdown("verified")));
|
|
1502
1728
|
console.log("");
|
|
1503
1729
|
}
|
|
1504
|
-
var
|
|
1730
|
+
var import_fs_extra2, import_path2, import_chalk, import_readline, BADGE_CONFIGS;
|
|
1505
1731
|
var init_badgeHelper = __esm({
|
|
1506
1732
|
"src/utils/cli/badgeHelper.ts"() {
|
|
1507
1733
|
"use strict";
|
|
1508
|
-
|
|
1509
|
-
|
|
1734
|
+
import_fs_extra2 = __toESM(require("fs-extra"), 1);
|
|
1735
|
+
import_path2 = __toESM(require("path"), 1);
|
|
1510
1736
|
import_chalk = __toESM(require("chalk"), 1);
|
|
1511
1737
|
import_readline = __toESM(require("readline"), 1);
|
|
1512
1738
|
BADGE_CONFIGS = {
|
|
@@ -2062,6 +2288,20 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
2062
2288
|
function hasSubmenu(menuItem) {
|
|
2063
2289
|
return menuItem.hasAttribute("aria-controls") && menuItem.hasAttribute("aria-haspopup") && menuItem.getAttribute("role") === "menuitem";
|
|
2064
2290
|
}
|
|
2291
|
+
function closeAncestorMenusFromTrigger(triggerEl) {
|
|
2292
|
+
let currentTrigger = triggerEl;
|
|
2293
|
+
while (currentTrigger && currentTrigger.getAttribute("role") === "menuitem") {
|
|
2294
|
+
const parentMenu = currentTrigger.closest('[role="menu"]');
|
|
2295
|
+
if (!parentMenu) break;
|
|
2296
|
+
parentMenu.style.display = "none";
|
|
2297
|
+
currentTrigger.setAttribute("aria-expanded", "false");
|
|
2298
|
+
const parentTriggerId = parentMenu.getAttribute("aria-labelledby");
|
|
2299
|
+
if (!parentTriggerId) break;
|
|
2300
|
+
const nextTrigger = document.getElementById(parentTriggerId);
|
|
2301
|
+
if (!nextTrigger) break;
|
|
2302
|
+
currentTrigger = nextTrigger;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2065
2305
|
intializeMenuItems();
|
|
2066
2306
|
function handleItemsKeydown(event, menuItem, menuItemIndex) {
|
|
2067
2307
|
switch (event.key) {
|
|
@@ -2132,11 +2372,10 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
2132
2372
|
break;
|
|
2133
2373
|
}
|
|
2134
2374
|
case "Tab": {
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
}
|
|
2375
|
+
closeMenu();
|
|
2376
|
+
closeAncestorMenusFromTrigger(triggerButton);
|
|
2377
|
+
if (onOpenChange) {
|
|
2378
|
+
onOpenChange(false);
|
|
2140
2379
|
}
|
|
2141
2380
|
break;
|
|
2142
2381
|
}
|
|
@@ -3028,9 +3267,11 @@ var import_jest_axe = require("jest-axe");
|
|
|
3028
3267
|
init_contract();
|
|
3029
3268
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
3030
3269
|
init_ContractReporter();
|
|
3270
|
+
init_strictness();
|
|
3031
3271
|
var import_meta = {};
|
|
3032
|
-
async function runContractTests(componentName, component) {
|
|
3272
|
+
async function runContractTests(componentName, component, strictness) {
|
|
3033
3273
|
const reporter = new ContractReporter(false);
|
|
3274
|
+
const strictnessMode = normalizeStrictness(strictness);
|
|
3034
3275
|
const contractTyped = contract_default;
|
|
3035
3276
|
const contractPath = contractTyped[componentName]?.path;
|
|
3036
3277
|
if (!contractPath) {
|
|
@@ -3044,19 +3285,42 @@ async function runContractTests(componentName, component) {
|
|
|
3044
3285
|
const failures = [];
|
|
3045
3286
|
const passes = [];
|
|
3046
3287
|
const skipped = [];
|
|
3047
|
-
const
|
|
3288
|
+
const warnings = [];
|
|
3289
|
+
const classifyFailure = (message, levelRaw) => {
|
|
3290
|
+
const level = normalizeLevel(levelRaw);
|
|
3291
|
+
const enforcement = resolveEnforcement(level, strictnessMode);
|
|
3292
|
+
if (enforcement === "error") {
|
|
3293
|
+
failures.push(message);
|
|
3294
|
+
return { status: "fail", level, detail: message };
|
|
3295
|
+
}
|
|
3296
|
+
if (enforcement === "warning") {
|
|
3297
|
+
warnings.push(message);
|
|
3298
|
+
return { status: "warn", level, detail: message };
|
|
3299
|
+
}
|
|
3300
|
+
const ignoredMessage = `${message} (ignored by strictness=${strictnessMode}, level=${level})`;
|
|
3301
|
+
skipped.push(ignoredMessage);
|
|
3302
|
+
return { status: "skip", level, detail: ignoredMessage };
|
|
3303
|
+
};
|
|
3304
|
+
let staticPassed = 0;
|
|
3305
|
+
let staticFailed = 0;
|
|
3306
|
+
let staticWarnings = 0;
|
|
3048
3307
|
for (const test of componentContract.static[0].assertions) {
|
|
3049
3308
|
if (test.target !== "relative") {
|
|
3309
|
+
const staticLevel = normalizeLevel(test.level);
|
|
3050
3310
|
const selector = componentContract.selectors[test.target];
|
|
3051
3311
|
if (!selector) {
|
|
3052
|
-
|
|
3053
|
-
|
|
3312
|
+
const outcome = classifyFailure(`Selector for target ${test.target} not found.`, test.level);
|
|
3313
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
3314
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
3315
|
+
reporter.reportStaticTest(`${test.target} has required ARIA attributes`, outcome.status, outcome.detail, outcome.level);
|
|
3054
3316
|
continue;
|
|
3055
3317
|
}
|
|
3056
3318
|
const target = component.querySelector(selector);
|
|
3057
3319
|
if (!target) {
|
|
3058
|
-
|
|
3059
|
-
|
|
3320
|
+
const outcome = classifyFailure(`Target ${test.target} not found.`, test.level);
|
|
3321
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
3322
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
3323
|
+
reporter.reportStaticTest(`${test.target} has required ARIA attributes`, outcome.status, outcome.detail, outcome.level);
|
|
3060
3324
|
continue;
|
|
3061
3325
|
}
|
|
3062
3326
|
const attributeValue = target.getAttribute(test.attribute);
|
|
@@ -3064,36 +3328,40 @@ async function runContractTests(componentName, component) {
|
|
|
3064
3328
|
const attributes = test.attribute.split(" | ");
|
|
3065
3329
|
const hasAnyAttribute = attributes.some((attr) => target.hasAttribute(attr));
|
|
3066
3330
|
if (!hasAnyAttribute) {
|
|
3067
|
-
|
|
3068
|
-
|
|
3331
|
+
const outcome = classifyFailure(test.failureMessage + ` None of the attributes "${test.attribute}" are present.`, test.level);
|
|
3332
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
3333
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
3334
|
+
reporter.reportStaticTest(`${test.target} has ${test.attribute}`, outcome.status, outcome.detail, outcome.level);
|
|
3069
3335
|
} else {
|
|
3070
3336
|
passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
|
|
3071
|
-
|
|
3337
|
+
staticPassed += 1;
|
|
3338
|
+
reporter.reportStaticTest(`${test.target} has ${test.attribute}`, "pass", void 0, staticLevel);
|
|
3072
3339
|
}
|
|
3073
3340
|
} else if (!attributeValue || !test.expectedValue.split(" | ").includes(attributeValue)) {
|
|
3074
|
-
|
|
3075
|
-
|
|
3341
|
+
const outcome = classifyFailure(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`, test.level);
|
|
3342
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
3343
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
3344
|
+
reporter.reportStaticTest(`${test.target} has ${test.attribute}="${test.expectedValue}"`, outcome.status, outcome.detail, outcome.level);
|
|
3076
3345
|
} else {
|
|
3077
3346
|
passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
|
|
3078
|
-
|
|
3347
|
+
staticPassed += 1;
|
|
3348
|
+
reporter.reportStaticTest(`${test.target} has ${test.attribute}="${attributeValue}"`, "pass", void 0, staticLevel);
|
|
3079
3349
|
}
|
|
3080
3350
|
}
|
|
3081
3351
|
}
|
|
3082
3352
|
for (const dynamicTest of componentContract.dynamic) {
|
|
3083
3353
|
skipped.push(dynamicTest.description);
|
|
3084
|
-
reporter.reportTest(dynamicTest, "skip");
|
|
3354
|
+
reporter.reportTest({ description: dynamicTest.description, level: dynamicTest.level }, "skip");
|
|
3085
3355
|
}
|
|
3086
|
-
|
|
3087
|
-
const staticFailed = failures.length - failuresBeforeStatic;
|
|
3088
|
-
const staticPassed = Math.max(0, staticTotal - staticFailed);
|
|
3089
|
-
reporter.reportStatic(staticPassed, staticFailed);
|
|
3356
|
+
reporter.reportStatic(staticPassed, staticFailed, staticWarnings);
|
|
3090
3357
|
reporter.summary(failures);
|
|
3091
|
-
return { passes, failures, skipped };
|
|
3358
|
+
return { passes, failures, skipped, warnings };
|
|
3092
3359
|
}
|
|
3093
3360
|
|
|
3094
3361
|
// src/utils/test/src/test.ts
|
|
3095
3362
|
init_playwrightTestHarness();
|
|
3096
|
-
|
|
3363
|
+
init_strictness();
|
|
3364
|
+
async function testUiComponent(componentName, component, url, options = {}) {
|
|
3097
3365
|
if (!componentName || typeof componentName !== "string") {
|
|
3098
3366
|
throw new Error("\u274C testUiComponent requires a valid componentName (string)");
|
|
3099
3367
|
}
|
|
@@ -3130,6 +3398,17 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
3130
3398
|
}
|
|
3131
3399
|
return null;
|
|
3132
3400
|
}
|
|
3401
|
+
let strictness = normalizeStrictness(options.strictness);
|
|
3402
|
+
if (options.strictness === void 0 && typeof window === "undefined") {
|
|
3403
|
+
try {
|
|
3404
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_configLoader(), configLoader_exports));
|
|
3405
|
+
const { config } = await loadConfig2(process.cwd());
|
|
3406
|
+
const componentStrictness = config.test?.components?.find((comp) => comp?.name === componentName)?.strictness;
|
|
3407
|
+
strictness = normalizeStrictness(componentStrictness ?? config.test?.strictness);
|
|
3408
|
+
} catch {
|
|
3409
|
+
strictness = "balanced";
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3133
3412
|
let contract;
|
|
3134
3413
|
try {
|
|
3135
3414
|
if (url) {
|
|
@@ -3137,7 +3416,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
3137
3416
|
if (devServerUrl) {
|
|
3138
3417
|
console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
|
|
3139
3418
|
const { runContractTestsPlaywright: runContractTestsPlaywright2 } = await Promise.resolve().then(() => (init_contractTestRunnerPlaywright(), contractTestRunnerPlaywright_exports));
|
|
3140
|
-
contract = await runContractTestsPlaywright2(componentName, devServerUrl);
|
|
3419
|
+
contract = await runContractTestsPlaywright2(componentName, devServerUrl, strictness);
|
|
3141
3420
|
} else {
|
|
3142
3421
|
throw new Error(
|
|
3143
3422
|
`\u274C Dev server not running at ${url}
|
|
@@ -3146,7 +3425,7 @@ Please start your dev server and try again.`
|
|
|
3146
3425
|
}
|
|
3147
3426
|
} else if (component) {
|
|
3148
3427
|
console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
|
|
3149
|
-
contract = await runContractTests(componentName, component);
|
|
3428
|
+
contract = await runContractTests(componentName, component, strictness);
|
|
3150
3429
|
} else {
|
|
3151
3430
|
throw new Error("\u274C Either component or URL must be provided");
|
|
3152
3431
|
}
|