aria-ease 5.0.1 → 5.0.3
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 +2 -2
- package/bin/{audit-6A7OW5NJ.js → audit-5JJ3XK46.js} +1 -1
- package/bin/{chunk-2P3A4VVY.js → chunk-I2KLQ2HA.js} +1 -16
- package/bin/{chunk-5Z7PR3VC.js → chunk-TUWQNVQJ.js} +68 -9
- package/bin/cli.cjs +283 -13023
- package/bin/cli.js +4 -4
- package/bin/{contractTestRunnerPlaywright-YPBTKJP7.js → contractTestRunnerPlaywright-O22AQ4RK.js} +27 -75
- package/bin/{formatters-FCDHFTPI.js → formatters-32KQIIYS.js} +1 -1
- package/bin/test-JNQFZBJA.js +195 -0
- package/dist/{chunk-IPJYOFRK.js → chunk-KJ33RDSC.js} +68 -24
- package/dist/{contractTestRunnerPlaywright-45CFWUOD.js → contractTestRunnerPlaywright-7ZOM7ZMG.js} +26 -74
- package/dist/index.cjs +1669 -14340
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1063 -13744
- package/dist/src/accordion/index.cjs +2 -2
- package/dist/src/accordion/index.d.cts +3 -3
- package/dist/src/accordion/index.d.ts +3 -3
- package/dist/src/accordion/index.js +2 -2
- package/dist/src/utils/test/{chunk-IPJYOFRK.js → chunk-TUWQNVQJ.js} +69 -41
- package/dist/src/utils/test/{contractTestRunnerPlaywright-45CFWUOD.js → contractTestRunnerPlaywright-P5QZAIDR.js} +31 -86
- package/dist/src/utils/test/contracts/AccordionContract.json +232 -6
- package/dist/src/utils/test/contracts/ComboboxContract.json +2 -0
- package/dist/src/utils/test/index.cjs +635 -13173
- package/dist/src/utils/test/index.js +40 -12533
- package/package.json +2 -2
- package/bin/test-TAH4VGZV.js +0 -12877
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/accordion/src/makeAccordionAccessible/makeAccordionAccessible.ts
|
|
4
|
-
function makeAccordionAccessible({ accordionId, triggersClass, panelsClass,
|
|
4
|
+
function makeAccordionAccessible({ accordionId, triggersClass, panelsClass, allowMultipleOpen = false }) {
|
|
5
5
|
const accordionContainer = document.querySelector(`#${accordionId}`);
|
|
6
6
|
if (!accordionContainer) {
|
|
7
7
|
console.error(`[aria-ease] Element with id="${accordionId}" not found. Make sure the accordion container exists before calling makeAccordionAccessible.`);
|
|
@@ -69,7 +69,7 @@ function makeAccordionAccessible({ accordionId, triggersClass, panelsClass, allo
|
|
|
69
69
|
if (isExpanded) {
|
|
70
70
|
collapseItem(index);
|
|
71
71
|
} else {
|
|
72
|
-
if (!
|
|
72
|
+
if (!allowMultipleOpen) {
|
|
73
73
|
triggers.forEach((_, i) => {
|
|
74
74
|
if (i !== index) {
|
|
75
75
|
collapseItem(i);
|
|
@@ -6,15 +6,15 @@ import { A as AccessibilityInstance } from '../Types.d-COr5IFp5.cjs';
|
|
|
6
6
|
* @param {string} accordionId - The id of the accordion container.
|
|
7
7
|
* @param {string} triggersClass - The shared class of all accordion trigger buttons.
|
|
8
8
|
* @param {string} panelsClass - The shared class of all accordion panels.
|
|
9
|
-
* @param {boolean}
|
|
9
|
+
* @param {boolean} allowMultipleOpen - Whether multiple panels can be open simultaneously (default: false).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
interface AccordionConfig {
|
|
13
13
|
accordionId: string;
|
|
14
14
|
triggersClass: string;
|
|
15
15
|
panelsClass: string;
|
|
16
|
-
|
|
16
|
+
allowMultipleOpen?: boolean;
|
|
17
17
|
}
|
|
18
|
-
declare function makeAccordionAccessible({ accordionId, triggersClass, panelsClass,
|
|
18
|
+
declare function makeAccordionAccessible({ accordionId, triggersClass, panelsClass, allowMultipleOpen }: AccordionConfig): AccessibilityInstance;
|
|
19
19
|
|
|
20
20
|
export { makeAccordionAccessible };
|
|
@@ -6,15 +6,15 @@ import { A as AccessibilityInstance } from '../Types.d-COr5IFp5.js';
|
|
|
6
6
|
* @param {string} accordionId - The id of the accordion container.
|
|
7
7
|
* @param {string} triggersClass - The shared class of all accordion trigger buttons.
|
|
8
8
|
* @param {string} panelsClass - The shared class of all accordion panels.
|
|
9
|
-
* @param {boolean}
|
|
9
|
+
* @param {boolean} allowMultipleOpen - Whether multiple panels can be open simultaneously (default: false).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
interface AccordionConfig {
|
|
13
13
|
accordionId: string;
|
|
14
14
|
triggersClass: string;
|
|
15
15
|
panelsClass: string;
|
|
16
|
-
|
|
16
|
+
allowMultipleOpen?: boolean;
|
|
17
17
|
}
|
|
18
|
-
declare function makeAccordionAccessible({ accordionId, triggersClass, panelsClass,
|
|
18
|
+
declare function makeAccordionAccessible({ accordionId, triggersClass, panelsClass, allowMultipleOpen }: AccordionConfig): AccessibilityInstance;
|
|
19
19
|
|
|
20
20
|
export { makeAccordionAccessible };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/accordion/src/makeAccordionAccessible/makeAccordionAccessible.ts
|
|
2
|
-
function makeAccordionAccessible({ accordionId, triggersClass, panelsClass,
|
|
2
|
+
function makeAccordionAccessible({ accordionId, triggersClass, panelsClass, allowMultipleOpen = false }) {
|
|
3
3
|
const accordionContainer = document.querySelector(`#${accordionId}`);
|
|
4
4
|
if (!accordionContainer) {
|
|
5
5
|
console.error(`[aria-ease] Element with id="${accordionId}" not found. Make sure the accordion container exists before calling makeAccordionAccessible.`);
|
|
@@ -67,7 +67,7 @@ function makeAccordionAccessible({ accordionId, triggersClass, panelsClass, allo
|
|
|
67
67
|
if (isExpanded) {
|
|
68
68
|
collapseItem(index);
|
|
69
69
|
} else {
|
|
70
|
-
if (!
|
|
70
|
+
if (!allowMultipleOpen) {
|
|
71
71
|
triggers.forEach((_, i) => {
|
|
72
72
|
if (i !== index) {
|
|
73
73
|
collapseItem(i);
|
|
@@ -1,34 +1,3 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
9
|
-
};
|
|
10
|
-
var __export = (target, all) => {
|
|
11
|
-
for (var name in all)
|
|
12
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
-
};
|
|
14
|
-
var __copyProps = (to, from, except, desc) => {
|
|
15
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
-
for (let key of __getOwnPropNames(from))
|
|
17
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
-
}
|
|
20
|
-
return to;
|
|
21
|
-
};
|
|
22
|
-
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
-
mod
|
|
30
|
-
));
|
|
31
|
-
|
|
32
1
|
// src/utils/test/contract/contract.json
|
|
33
2
|
var contract_default = {
|
|
34
3
|
menu: {
|
|
@@ -38,6 +7,10 @@ var contract_default = {
|
|
|
38
7
|
combobox: {
|
|
39
8
|
path: "./contracts/ComboboxContract.json",
|
|
40
9
|
component: "combobox"
|
|
10
|
+
},
|
|
11
|
+
accordion: {
|
|
12
|
+
path: "./contracts/AccordionContract.json",
|
|
13
|
+
component: "accordion"
|
|
41
14
|
}
|
|
42
15
|
};
|
|
43
16
|
|
|
@@ -50,17 +23,22 @@ var ContractReporter = class {
|
|
|
50
23
|
dynamicResults = [];
|
|
51
24
|
totalTests = 0;
|
|
52
25
|
skipped = 0;
|
|
26
|
+
optionalSuggestions = 0;
|
|
53
27
|
isPlaywright = false;
|
|
28
|
+
apgUrl = "https://www.w3.org/WAI/ARIA/apg/";
|
|
54
29
|
constructor(isPlaywright = false) {
|
|
55
30
|
this.isPlaywright = isPlaywright;
|
|
56
31
|
}
|
|
57
32
|
log(message) {
|
|
58
33
|
process.stderr.write(message + "\n");
|
|
59
34
|
}
|
|
60
|
-
start(componentName, totalTests) {
|
|
35
|
+
start(componentName, totalTests, apgUrl) {
|
|
61
36
|
this.startTime = Date.now();
|
|
62
37
|
this.componentName = componentName;
|
|
63
38
|
this.totalTests = totalTests;
|
|
39
|
+
if (apgUrl) {
|
|
40
|
+
this.apgUrl = apgUrl;
|
|
41
|
+
}
|
|
64
42
|
const mode = this.isPlaywright ? "Playwright (Real Browser)" : "jsdom (Fast)";
|
|
65
43
|
this.log(`
|
|
66
44
|
${"\u2550".repeat(60)}`);
|
|
@@ -73,10 +51,21 @@ ${"\u2550".repeat(60)}`);
|
|
|
73
51
|
this.staticFailures = failures;
|
|
74
52
|
const icon = failures === 0 ? "\u2705" : "\u274C";
|
|
75
53
|
const status = failures === 0 ? "PASS" : "FAIL";
|
|
54
|
+
this.log("");
|
|
76
55
|
this.log(`${icon} Static ARIA Tests: ${status}`);
|
|
77
56
|
this.log(` ${passes}/${passes + failures} required attributes present
|
|
78
57
|
`);
|
|
79
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Report individual static test pass
|
|
61
|
+
*/
|
|
62
|
+
reportStaticTest(description, passed, failureMessage) {
|
|
63
|
+
const icon = passed ? "\u2713" : "\u2717";
|
|
64
|
+
this.log(` ${icon} ${description}`);
|
|
65
|
+
if (!passed && failureMessage) {
|
|
66
|
+
this.log(` \u21B3 ${failureMessage}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
80
69
|
/**
|
|
81
70
|
* Report individual dynamic test result
|
|
82
71
|
*/
|
|
@@ -84,20 +73,25 @@ ${"\u2550".repeat(60)}`);
|
|
|
84
73
|
const result = {
|
|
85
74
|
description: test.description,
|
|
86
75
|
status,
|
|
87
|
-
failureMessage
|
|
76
|
+
failureMessage,
|
|
77
|
+
isOptional: test.isOptional
|
|
88
78
|
};
|
|
89
79
|
if (status === "skip" && test.requiresBrowser) {
|
|
90
80
|
result.skipReason = "Requires real browser (addEventListener events)";
|
|
91
81
|
}
|
|
92
82
|
this.dynamicResults.push(result);
|
|
93
|
-
const icons = { pass: "\u2713", fail: "\u2717", skip: "\u25CB" };
|
|
94
|
-
|
|
83
|
+
const icons = { pass: "\u2713", fail: "\u2717", skip: "\u25CB", "optional-fail": "\u25CB" };
|
|
84
|
+
const prefix = test.isOptional ? "[OPTIONAL] " : "";
|
|
85
|
+
this.log(` ${icons[status]} ${prefix}${test.description}`);
|
|
95
86
|
if (status === "skip" && !this.isPlaywright) {
|
|
96
87
|
this.log(` \u21B3 Skipped in jsdom (runs in Playwright)`);
|
|
97
88
|
}
|
|
98
|
-
if (status === "fail" && failureMessage) {
|
|
89
|
+
if (status === "fail" && failureMessage && !test.isOptional) {
|
|
99
90
|
this.log(` \u21B3 ${failureMessage}`);
|
|
100
91
|
}
|
|
92
|
+
if (status === "optional-fail") {
|
|
93
|
+
this.log(` \u21B3 Not implemented (recommended for enhanced UX)`);
|
|
94
|
+
}
|
|
101
95
|
}
|
|
102
96
|
/**
|
|
103
97
|
* Report all failures with actionable context
|
|
@@ -120,6 +114,30 @@ ${"\u2500".repeat(60)}`);
|
|
|
120
114
|
this.log("");
|
|
121
115
|
});
|
|
122
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Report optional features that aren't implemented
|
|
119
|
+
*/
|
|
120
|
+
reportOptionalSuggestions() {
|
|
121
|
+
const suggestions = this.dynamicResults.filter((r) => r.status === "optional-fail");
|
|
122
|
+
if (suggestions.length === 0) return;
|
|
123
|
+
this.log(`
|
|
124
|
+
${"\u2500".repeat(60)}`);
|
|
125
|
+
this.log(`\u{1F4A1} Optional Enhancements (${suggestions.length}):
|
|
126
|
+
`);
|
|
127
|
+
this.log(`These features are optional per APG guidelines but recommended`);
|
|
128
|
+
this.log(`for improved user experience and keyboard navigation:
|
|
129
|
+
`);
|
|
130
|
+
suggestions.forEach((test, index) => {
|
|
131
|
+
this.log(`${index + 1}. ${test.description}`);
|
|
132
|
+
if (test.failureMessage) {
|
|
133
|
+
this.log(` \u21B3 ${test.failureMessage}`);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
this.log(`
|
|
137
|
+
\u2728 Consider implementing these for better accessibility`);
|
|
138
|
+
this.log(` Reference: ${this.apgUrl}
|
|
139
|
+
`);
|
|
140
|
+
}
|
|
123
141
|
/**
|
|
124
142
|
* Report skipped tests with helpful context
|
|
125
143
|
*/
|
|
@@ -149,30 +167,40 @@ ${"\u2500".repeat(60)}`);
|
|
|
149
167
|
const dynamicPasses = this.dynamicResults.filter((r) => r.status === "pass").length;
|
|
150
168
|
const dynamicFailures = this.dynamicResults.filter((r) => r.status === "fail").length;
|
|
151
169
|
this.skipped = this.dynamicResults.filter((r) => r.status === "skip").length;
|
|
170
|
+
this.optionalSuggestions = this.dynamicResults.filter((r) => r.status === "optional-fail").length;
|
|
152
171
|
const totalPasses = this.staticPasses + dynamicPasses;
|
|
153
172
|
const totalFailures = this.staticFailures + dynamicFailures;
|
|
154
173
|
const totalRun = totalPasses + totalFailures;
|
|
155
174
|
if (failures.length > 0) {
|
|
156
175
|
this.reportFailures(failures);
|
|
157
176
|
}
|
|
177
|
+
this.reportOptionalSuggestions();
|
|
158
178
|
this.reportSkipped();
|
|
159
179
|
this.log(`
|
|
160
180
|
${"\u2550".repeat(60)}`);
|
|
161
181
|
this.log(`\u{1F4CA} Summary
|
|
162
182
|
`);
|
|
163
|
-
if (totalFailures === 0 && this.skipped === 0) {
|
|
183
|
+
if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
|
|
164
184
|
this.log(`\u2705 All ${totalRun} tests passed!`);
|
|
165
185
|
this.log(` ${this.componentName} component meets APG and WCAG guidelines \u2713`);
|
|
166
186
|
} else if (totalFailures === 0) {
|
|
167
|
-
this.log(`\u2705 ${totalPasses}/${totalRun} tests passed`);
|
|
168
|
-
|
|
169
|
-
|
|
187
|
+
this.log(`\u2705 ${totalPasses}/${totalRun} required tests passed`);
|
|
188
|
+
if (this.skipped > 0) {
|
|
189
|
+
this.log(`\u25CB ${this.skipped} tests skipped (jsdom limitation)`);
|
|
190
|
+
}
|
|
191
|
+
if (this.optionalSuggestions > 0) {
|
|
192
|
+
this.log(`\u{1F4A1} ${this.optionalSuggestions} optional enhancement${this.optionalSuggestions > 1 ? "s" : ""} suggested`);
|
|
193
|
+
}
|
|
194
|
+
this.log(` ${this.componentName} component meets required standards \u2713`);
|
|
170
195
|
} else {
|
|
171
196
|
this.log(`\u274C ${totalFailures} test${totalFailures > 1 ? "s" : ""} failed`);
|
|
172
197
|
this.log(`\u2705 ${totalPasses} test${totalPasses > 1 ? "s" : ""} passed`);
|
|
173
198
|
if (this.skipped > 0) {
|
|
174
199
|
this.log(`\u25CB ${this.skipped} test${this.skipped > 1 ? "s" : ""} skipped`);
|
|
175
200
|
}
|
|
201
|
+
if (this.optionalSuggestions > 0) {
|
|
202
|
+
this.log(`\u{1F4A1} ${this.optionalSuggestions} optional enhancement${this.optionalSuggestions > 1 ? "s" : ""} suggested`);
|
|
203
|
+
}
|
|
176
204
|
}
|
|
177
205
|
this.log(`\u23F1\uFE0F Duration: ${duration}ms`);
|
|
178
206
|
this.log(`${"\u2550".repeat(60)}
|
|
@@ -207,4 +235,4 @@ ${"\u2550".repeat(60)}`);
|
|
|
207
235
|
}
|
|
208
236
|
};
|
|
209
237
|
|
|
210
|
-
export { ContractReporter,
|
|
238
|
+
export { ContractReporter, contract_default };
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ContractReporter, contract_default } from './chunk-TUWQNVQJ.js';
|
|
2
2
|
import { chromium } from 'playwright';
|
|
3
|
-
import
|
|
4
|
-
import default2__default from 'playwright/test';
|
|
3
|
+
import { expect } from '@playwright/test';
|
|
5
4
|
import { readFileSync } from 'fs';
|
|
6
5
|
|
|
7
|
-
// node_modules/@playwright/test/index.mjs
|
|
8
|
-
var test_exports = {};
|
|
9
|
-
__export(test_exports, {
|
|
10
|
-
default: () => default2__default
|
|
11
|
-
});
|
|
12
|
-
__reExport(test_exports, default2);
|
|
13
6
|
async function runContractTestsPlaywright(componentName, url) {
|
|
14
7
|
const reporter = new ContractReporter(true);
|
|
15
8
|
const contractTyped = contract_default;
|
|
@@ -21,7 +14,8 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
21
14
|
const contractData = readFileSync(resolvedPath, "utf-8");
|
|
22
15
|
const componentContract = JSON.parse(contractData);
|
|
23
16
|
const totalTests = componentContract.static[0].assertions.length + componentContract.dynamic.length;
|
|
24
|
-
|
|
17
|
+
const apgUrl = componentContract.meta?.source?.apg;
|
|
18
|
+
reporter.start(componentName, totalTests, apgUrl);
|
|
25
19
|
const failures = [];
|
|
26
20
|
const passes = [];
|
|
27
21
|
const skipped = [];
|
|
@@ -32,25 +26,21 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
32
26
|
const page = await context.newPage();
|
|
33
27
|
await page.goto(url, {
|
|
34
28
|
waitUntil: "domcontentloaded",
|
|
35
|
-
timeout:
|
|
29
|
+
timeout: 9e5
|
|
36
30
|
});
|
|
31
|
+
await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
|
|
37
32
|
const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
|
|
38
33
|
if (!mainSelector) {
|
|
39
34
|
throw new Error(`No main selector (trigger, input, or container) found in contract for ${componentName}`);
|
|
40
35
|
}
|
|
41
|
-
await page.
|
|
36
|
+
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 9e5 });
|
|
42
37
|
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
43
|
-
await page.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
componentContract.selectors.trigger,
|
|
49
|
-
{ timeout: 1e4 }
|
|
50
|
-
).catch(() => {
|
|
51
|
-
console.warn("Menu initialization signal not detected, continuing with tests...");
|
|
38
|
+
await page.locator(componentContract.selectors.trigger).first().waitFor({
|
|
39
|
+
state: "visible",
|
|
40
|
+
timeout: 1e4
|
|
41
|
+
}).catch(() => {
|
|
42
|
+
console.warn("Menu trigger not visible, continuing with tests...");
|
|
52
43
|
});
|
|
53
|
-
await page.waitForTimeout(300);
|
|
54
44
|
}
|
|
55
45
|
async function resolveRelativeTarget(selector, relative) {
|
|
56
46
|
const items = await page.locator(selector).all();
|
|
@@ -126,7 +116,7 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
126
116
|
const popupSelector = componentContract.selectors.listbox || componentContract.selectors.container;
|
|
127
117
|
if (!popupSelector) continue;
|
|
128
118
|
const popupElement = page.locator(popupSelector).first();
|
|
129
|
-
const isPopupVisible = await popupElement.isVisible();
|
|
119
|
+
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
130
120
|
if (isPopupVisible) {
|
|
131
121
|
let menuClosed = false;
|
|
132
122
|
let closeSelector = componentContract.selectors.input;
|
|
@@ -138,44 +128,17 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
138
128
|
if (closeSelector) {
|
|
139
129
|
const closeElement = page.locator(closeSelector).first();
|
|
140
130
|
await closeElement.focus();
|
|
141
|
-
await page.waitForTimeout(200);
|
|
142
131
|
await page.keyboard.press("Escape");
|
|
143
|
-
menuClosed = await
|
|
144
|
-
(selector) => {
|
|
145
|
-
const popup = document.querySelector(selector);
|
|
146
|
-
return popup && getComputedStyle(popup).display === "none";
|
|
147
|
-
},
|
|
148
|
-
popupSelector,
|
|
149
|
-
{ timeout: 3e3 }
|
|
150
|
-
).then(() => true).catch(() => false);
|
|
132
|
+
menuClosed = await expect(popupElement).toBeHidden({ timeout: 3e3 }).then(() => true).catch(() => false);
|
|
151
133
|
}
|
|
152
134
|
if (!menuClosed && componentContract.selectors.trigger) {
|
|
153
135
|
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
154
136
|
await triggerElement.click();
|
|
155
|
-
await
|
|
156
|
-
menuClosed = await page.waitForFunction(
|
|
157
|
-
(selector) => {
|
|
158
|
-
const popup = document.querySelector(selector);
|
|
159
|
-
return popup && getComputedStyle(popup).display === "none";
|
|
160
|
-
},
|
|
161
|
-
popupSelector,
|
|
162
|
-
{ timeout: 3e3 }
|
|
163
|
-
).then(() => true).catch(() => false);
|
|
137
|
+
menuClosed = await expect(popupElement).toBeHidden({ timeout: 3e3 }).then(() => true).catch(() => false);
|
|
164
138
|
}
|
|
165
139
|
if (!menuClosed) {
|
|
166
140
|
await page.mouse.click(10, 10);
|
|
167
|
-
await
|
|
168
|
-
menuClosed = await page.waitForFunction(
|
|
169
|
-
(selector) => {
|
|
170
|
-
const popup = document.querySelector(selector);
|
|
171
|
-
return popup && getComputedStyle(popup).display === "none";
|
|
172
|
-
},
|
|
173
|
-
popupSelector,
|
|
174
|
-
{ timeout: 3e3 }
|
|
175
|
-
).then(() => true).catch(() => false);
|
|
176
|
-
if (menuClosed) {
|
|
177
|
-
console.log("\u{1F3AF} Strategy 3 (Click outside) worked");
|
|
178
|
-
}
|
|
141
|
+
menuClosed = await expect(popupElement).toBeHidden({ timeout: 3e3 }).then(() => true).catch(() => false);
|
|
179
142
|
}
|
|
180
143
|
if (!menuClosed) {
|
|
181
144
|
throw new Error(
|
|
@@ -186,25 +149,12 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
186
149
|
This indicates a problem with the menu component's close functionality.`
|
|
187
150
|
);
|
|
188
151
|
}
|
|
189
|
-
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
190
|
-
await page.waitForFunction(
|
|
191
|
-
(selector) => {
|
|
192
|
-
const trigger = document.querySelector(selector);
|
|
193
|
-
return document.activeElement === trigger;
|
|
194
|
-
},
|
|
195
|
-
componentContract.selectors.trigger,
|
|
196
|
-
{ timeout: 2e3 }
|
|
197
|
-
).catch(async () => {
|
|
198
|
-
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
199
|
-
await triggerElement.focus();
|
|
200
|
-
await page.waitForTimeout(200);
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
await page.waitForTimeout(500);
|
|
204
152
|
if (componentContract.selectors.input) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
153
|
+
await page.locator(componentContract.selectors.input).first().clear();
|
|
154
|
+
}
|
|
155
|
+
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
156
|
+
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
157
|
+
await triggerElement.focus();
|
|
208
158
|
}
|
|
209
159
|
}
|
|
210
160
|
}
|
|
@@ -248,7 +198,6 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
248
198
|
continue;
|
|
249
199
|
}
|
|
250
200
|
await page.locator(focusSelector).first().focus();
|
|
251
|
-
await page.waitForTimeout(100);
|
|
252
201
|
}
|
|
253
202
|
if (act.type === "type" && act.value) {
|
|
254
203
|
const typeSelector = componentContract.selectors[act.target];
|
|
@@ -257,7 +206,6 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
257
206
|
continue;
|
|
258
207
|
}
|
|
259
208
|
await page.locator(typeSelector).first().fill(act.value);
|
|
260
|
-
await page.waitForTimeout(100);
|
|
261
209
|
}
|
|
262
210
|
if (act.type === "click") {
|
|
263
211
|
if (act.target === "document") {
|
|
@@ -274,7 +222,6 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
274
222
|
continue;
|
|
275
223
|
}
|
|
276
224
|
await relativeElement.click();
|
|
277
|
-
await page.waitForTimeout(componentName === "menu" ? 800 : 200);
|
|
278
225
|
} else {
|
|
279
226
|
const actionSelector = componentContract.selectors[act.target];
|
|
280
227
|
if (!actionSelector) {
|
|
@@ -282,7 +229,6 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
282
229
|
continue;
|
|
283
230
|
}
|
|
284
231
|
await page.locator(actionSelector).first().click();
|
|
285
|
-
await page.waitForTimeout(componentName === "menu" ? 800 : 200);
|
|
286
232
|
}
|
|
287
233
|
}
|
|
288
234
|
if (act.type === "keypress" && act.key) {
|
|
@@ -305,9 +251,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
305
251
|
keyValue = keyValue.replace(/ /g, "");
|
|
306
252
|
}
|
|
307
253
|
if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
308
|
-
await page.waitForTimeout(componentName === "menu" ? 200 : 100);
|
|
309
254
|
await page.keyboard.press(keyValue);
|
|
310
|
-
await page.waitForTimeout(componentName === "menu" ? 300 : 100);
|
|
311
255
|
} else {
|
|
312
256
|
const keypressSelector = componentContract.selectors[act.target];
|
|
313
257
|
if (!keypressSelector) {
|
|
@@ -336,7 +280,6 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
336
280
|
continue;
|
|
337
281
|
}
|
|
338
282
|
await relativeElement.hover();
|
|
339
|
-
await page.waitForTimeout(100);
|
|
340
283
|
} else {
|
|
341
284
|
const hoverSelector = componentContract.selectors[act.target];
|
|
342
285
|
if (!hoverSelector) {
|
|
@@ -344,12 +287,9 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
344
287
|
continue;
|
|
345
288
|
}
|
|
346
289
|
await page.locator(hoverSelector).first().hover();
|
|
347
|
-
await page.waitForTimeout(100);
|
|
348
290
|
}
|
|
349
291
|
}
|
|
350
|
-
await page.waitForTimeout(componentName === "menu" ? 200 : 100);
|
|
351
292
|
}
|
|
352
|
-
await page.waitForTimeout(componentName === "menu" ? 300 : 100);
|
|
353
293
|
for (const assertion of assertions) {
|
|
354
294
|
let target;
|
|
355
295
|
if (assertion.target === "relative") {
|
|
@@ -378,7 +318,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
378
318
|
}
|
|
379
319
|
if (assertion.assertion === "toBeVisible") {
|
|
380
320
|
try {
|
|
381
|
-
await
|
|
321
|
+
await expect(target).toBeVisible({ timeout: 3e3 });
|
|
382
322
|
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
383
323
|
} catch {
|
|
384
324
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -392,7 +332,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
392
332
|
}
|
|
393
333
|
if (assertion.assertion === "notToBeVisible") {
|
|
394
334
|
try {
|
|
395
|
-
await
|
|
335
|
+
await expect(target).toBeHidden({ timeout: 5e3 });
|
|
396
336
|
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
397
337
|
} catch {
|
|
398
338
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -414,7 +354,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
414
354
|
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should not be empty, found "${attributeValue}".`);
|
|
415
355
|
}
|
|
416
356
|
} else {
|
|
417
|
-
await
|
|
357
|
+
await expect(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout: 3e3 });
|
|
418
358
|
passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
419
359
|
}
|
|
420
360
|
} catch {
|
|
@@ -444,7 +384,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
444
384
|
}
|
|
445
385
|
if (assertion.assertion === "toHaveFocus") {
|
|
446
386
|
try {
|
|
447
|
-
await
|
|
387
|
+
await expect(target).toBeFocused({ timeout: 5e3 });
|
|
448
388
|
passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
|
|
449
389
|
} catch {
|
|
450
390
|
const actualFocus = await page.evaluate(() => {
|
|
@@ -466,7 +406,12 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
466
406
|
const failuresAfterTest = failures.length;
|
|
467
407
|
const testPassed = failuresAfterTest === failuresBeforeTest;
|
|
468
408
|
const failureMessage = testPassed ? void 0 : failures[failures.length - 1];
|
|
469
|
-
|
|
409
|
+
if (dynamicTest.isOptional === true && !testPassed) {
|
|
410
|
+
failures.pop();
|
|
411
|
+
reporter.reportTest(dynamicTest, "optional-fail", failureMessage);
|
|
412
|
+
} else {
|
|
413
|
+
reporter.reportTest(dynamicTest, testPassed ? "pass" : "fail", failureMessage);
|
|
414
|
+
}
|
|
470
415
|
}
|
|
471
416
|
const staticPassed = componentContract.static[0].assertions.length;
|
|
472
417
|
const staticFailed = 0;
|