aria-ease 2.8.0 → 2.8.2

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.
Files changed (82) hide show
  1. package/bin/cli.js +3 -4
  2. package/dist/chunk-PCORWVIQ.js +213 -0
  3. package/dist/{chunk-4F6O5RKZ.js → chunk-SSBW5VAA.js} +0 -1
  4. package/dist/{contractTestRunnerPlaywright-FM6MK6DY.js → contractTestRunnerPlaywright-SE6TPWZZ.js} +1 -2
  5. package/{bin/contractTestRunnerPlaywright-2LQHVMXT.js → dist/contractTestRunnerPlaywright-YNHMLHQ2.js} +6 -178
  6. package/dist/contractTestRunnerPlaywright-ZY2T4UTV.js +254 -0
  7. package/dist/index.cjs +23 -8
  8. package/dist/index.d.cts +11 -14
  9. package/dist/index.d.ts +11 -14
  10. package/dist/index.js +24 -9
  11. package/dist/src/{Types.d-BbztRe-S.d.cts → Types.d-w1KLKLcA.d.cts} +8 -1
  12. package/dist/src/{Types.d-BbztRe-S.d.ts → Types.d-w1KLKLcA.d.ts} +8 -1
  13. package/dist/src/accordion/index.cjs +0 -2
  14. package/dist/src/accordion/index.d.cts +1 -1
  15. package/dist/src/accordion/index.d.ts +1 -1
  16. package/dist/src/accordion/index.js +0 -2
  17. package/dist/src/block/index.cjs +13 -5
  18. package/dist/src/block/index.d.cts +4 -3
  19. package/dist/src/block/index.d.ts +4 -3
  20. package/dist/src/block/index.js +14 -6
  21. package/dist/src/checkbox/index.cjs +0 -2
  22. package/dist/src/checkbox/index.d.cts +1 -1
  23. package/dist/src/checkbox/index.d.ts +1 -1
  24. package/dist/src/checkbox/index.js +0 -2
  25. package/dist/src/{chunk-DF4OR64G.js → chunk-TBJ6MIC7.js} +0 -2
  26. package/dist/src/menu/index.cjs +9 -4
  27. package/dist/src/menu/index.d.cts +4 -11
  28. package/dist/src/menu/index.d.ts +4 -11
  29. package/dist/src/menu/index.js +10 -5
  30. package/dist/src/radio/index.cjs +0 -2
  31. package/dist/src/radio/index.d.cts +1 -1
  32. package/dist/src/radio/index.d.ts +1 -1
  33. package/dist/src/radio/index.js +0 -2
  34. package/dist/src/toggle/index.cjs +0 -2
  35. package/dist/src/toggle/index.d.cts +1 -1
  36. package/dist/src/toggle/index.d.ts +1 -1
  37. package/dist/src/toggle/index.js +0 -2
  38. package/dist/src/utils/test/{chunk-UAS6V5MH.js → chunk-SSBW5VAA.js} +0 -2
  39. package/dist/src/utils/test/{contractTestRunnerPlaywright-IBC4FHWK.js → contractTestRunnerPlaywright-I36Y2NHA.js} +1 -3
  40. package/dist/src/utils/test/contractTestRunnerPlaywright-YNHMLHQ2.js +249 -0
  41. package/dist/src/utils/test/contractTestRunnerPlaywright-ZY2T4UTV.js +249 -0
  42. package/dist/src/utils/test/contracts/MenuContract.json +52 -8
  43. package/dist/src/utils/test/index.cjs +1 -3
  44. package/dist/src/utils/test/index.js +2 -4
  45. package/package.json +14 -13
  46. package/bin/cli.cjs +0 -475
  47. package/bin/cli.cjs.map +0 -1
  48. package/bin/cli.d.cts +0 -1
  49. package/bin/cli.d.ts +0 -1
  50. package/bin/cli.d.ts.map +0 -1
  51. package/bin/cli.js.map +0 -1
  52. package/bin/cli.ts +0 -122
  53. package/bin/configLoader.d.ts +0 -19
  54. package/bin/configLoader.d.ts.map +0 -1
  55. package/bin/configLoader.js +0 -155
  56. package/bin/configLoader.ts +0 -170
  57. package/bin/contractTestRunnerPlaywright-2LQHVMXT.js.map +0 -1
  58. package/dist/chunk-4F6O5RKZ.js.map +0 -1
  59. package/dist/contractTestRunnerPlaywright-FM6MK6DY.js.map +0 -1
  60. package/dist/index.cjs.map +0 -1
  61. package/dist/index.js.map +0 -1
  62. package/dist/src/accordion/index.cjs.map +0 -1
  63. package/dist/src/accordion/index.js.map +0 -1
  64. package/dist/src/block/index.cjs.map +0 -1
  65. package/dist/src/block/index.js.map +0 -1
  66. package/dist/src/checkbox/index.cjs.map +0 -1
  67. package/dist/src/checkbox/index.js.map +0 -1
  68. package/dist/src/chunk-CGC24XEF.js +0 -127
  69. package/dist/src/chunk-CGC24XEF.js.map +0 -1
  70. package/dist/src/chunk-DF4OR64G.js.map +0 -1
  71. package/dist/src/chunk-MNMWQWXH.js +0 -117
  72. package/dist/src/chunk-MNMWQWXH.js.map +0 -1
  73. package/dist/src/menu/index.cjs.map +0 -1
  74. package/dist/src/menu/index.js.map +0 -1
  75. package/dist/src/radio/index.cjs.map +0 -1
  76. package/dist/src/radio/index.js.map +0 -1
  77. package/dist/src/toggle/index.cjs.map +0 -1
  78. package/dist/src/toggle/index.js.map +0 -1
  79. package/dist/src/utils/test/chunk-UAS6V5MH.js.map +0 -1
  80. package/dist/src/utils/test/contractTestRunnerPlaywright-IBC4FHWK.js.map +0 -1
  81. package/dist/src/utils/test/index.cjs.map +0 -1
  82. package/dist/src/utils/test/index.js.map +0 -1
@@ -1,7 +1,6 @@
1
- import { handleKeyPress } from '../chunk-DF4OR64G.js';
1
+ import { handleKeyPress } from '../chunk-TBJ6MIC7.js';
2
2
 
3
3
  // src/block/src/makeBlockAccessible/makeBlockAccessible.ts
4
- var eventListenersMap = /* @__PURE__ */ new Map();
5
4
  function makeBlockAccessible(blockId, blockItemsClass) {
6
5
  const blockDiv = document.querySelector(`#${blockId}`);
7
6
  if (!blockDiv) {
@@ -9,12 +8,20 @@ function makeBlockAccessible(blockId, blockItemsClass) {
9
8
  return { cleanup: () => {
10
9
  } };
11
10
  }
12
- const blockItems = blockDiv.querySelectorAll(`.${blockItemsClass}`);
11
+ let cachedItems = null;
12
+ function getItems() {
13
+ if (!cachedItems) {
14
+ cachedItems = blockDiv.querySelectorAll(`.${blockItemsClass}`);
15
+ }
16
+ return cachedItems;
17
+ }
18
+ const blockItems = getItems();
13
19
  if (!blockItems || blockItems.length === 0) {
14
20
  console.error(`[aria-ease] Element with class="${blockItemsClass}" not found. Make sure the block items exist before calling makeBlockAccessible.`);
15
21
  return { cleanup: () => {
16
22
  } };
17
23
  }
24
+ const eventListenersMap = /* @__PURE__ */ new Map();
18
25
  blockItems.forEach((blockItem) => {
19
26
  if (!eventListenersMap.has(blockItem)) {
20
27
  const handler = (event) => {
@@ -35,9 +42,10 @@ function makeBlockAccessible(blockId, blockItemsClass) {
35
42
  }
36
43
  });
37
44
  }
38
- return { cleanup };
45
+ function refresh() {
46
+ cachedItems = null;
47
+ }
48
+ return { cleanup, refresh };
39
49
  }
40
50
 
41
51
  export { makeBlockAccessible };
42
- //# sourceMappingURL=index.js.map
43
- //# sourceMappingURL=index.js.map
@@ -32,5 +32,3 @@ function updateCheckboxAriaAttributes(checkboxId, checkboxesClass, checkboxState
32
32
  }
33
33
 
34
34
  exports.updateCheckboxAriaAttributes = updateCheckboxAriaAttributes;
35
- //# sourceMappingURL=index.cjs.map
36
- //# sourceMappingURL=index.cjs.map
@@ -1,4 +1,4 @@
1
- import { C as CheckboxStates } from '../Types.d-BbztRe-S.cjs';
1
+ import { C as CheckboxStates } from '../Types.d-w1KLKLcA.cjs';
2
2
 
3
3
  /**
4
4
  * Adds screen reader accessibility to multiple checkboxes. Updates the aria attributes of the checkboxes. Checkbox elements must possess the following aria attributes; aria-checked and aria-label.
@@ -1,4 +1,4 @@
1
- import { C as CheckboxStates } from '../Types.d-BbztRe-S.js';
1
+ import { C as CheckboxStates } from '../Types.d-w1KLKLcA.js';
2
2
 
3
3
  /**
4
4
  * Adds screen reader accessibility to multiple checkboxes. Updates the aria attributes of the checkboxes. Checkbox elements must possess the following aria attributes; aria-checked and aria-label.
@@ -30,5 +30,3 @@ function updateCheckboxAriaAttributes(checkboxId, checkboxesClass, checkboxState
30
30
  }
31
31
 
32
32
  export { updateCheckboxAriaAttributes };
33
- //# sourceMappingURL=index.js.map
34
- //# sourceMappingURL=index.js.map
@@ -119,5 +119,3 @@ function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, t
119
119
  }
120
120
 
121
121
  export { handleKeyPress };
122
- //# sourceMappingURL=chunk-DF4OR64G.js.map
123
- //# sourceMappingURL=chunk-DF4OR64G.js.map
@@ -138,7 +138,14 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
138
138
  }, cleanup: () => {
139
139
  } };
140
140
  }
141
- const handlerMap = /* @__PURE__ */ new Map();
141
+ if (!/^[\w-]+$/.test(menuId)) {
142
+ console.error("[aria-ease] Invalid menuId: must be alphanumeric");
143
+ return { openMenu: () => {
144
+ }, closeMenu: () => {
145
+ }, cleanup: () => {
146
+ } };
147
+ }
148
+ const handlerMap = /* @__PURE__ */ new WeakMap();
142
149
  const submenuInstances = /* @__PURE__ */ new Map();
143
150
  let cachedItems = null;
144
151
  let filteredItems = null;
@@ -257,7 +264,7 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
257
264
  setAria(true);
258
265
  const items = getFilteredItems();
259
266
  addListeners();
260
- if (items.length > 0) {
267
+ if (items && items.length > 0) {
261
268
  items[0].focus();
262
269
  }
263
270
  }
@@ -282,5 +289,3 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
282
289
  }
283
290
 
284
291
  exports.makeMenuAccessible = makeMenuAccessible;
285
- //# sourceMappingURL=index.cjs.map
286
- //# sourceMappingURL=index.cjs.map
@@ -1,23 +1,16 @@
1
+ import { a as AccessibilityInstance } from '../Types.d-w1KLKLcA.cjs';
2
+
1
3
  /**
2
4
  * Adds keyboard interaction to toggle menu. The menu traps focus and can be interacted with using the keyboard. The first interactive item of the menu has focus when menu open.
3
5
  * @param {string} menuId - The id of the menu.
4
6
  * @param {string} menuItemsClass - The class of the items that are children of the menu.
5
7
  * @param {string} triggerId - The id of the button that triggers the menu.
6
8
  */
9
+
7
10
  declare function makeMenuAccessible({ menuId, menuItemsClass, triggerId }: {
8
11
  menuId: string;
9
12
  menuItemsClass: string;
10
13
  triggerId: string;
11
- }): {
12
- openMenu: () => void;
13
- closeMenu: () => void;
14
- cleanup: () => void;
15
- refresh?: undefined;
16
- } | {
17
- openMenu: () => void;
18
- closeMenu: () => void;
19
- cleanup: () => void;
20
- refresh: () => void;
21
- };
14
+ }): AccessibilityInstance;
22
15
 
23
16
  export { makeMenuAccessible };
@@ -1,23 +1,16 @@
1
+ import { a as AccessibilityInstance } from '../Types.d-w1KLKLcA.js';
2
+
1
3
  /**
2
4
  * Adds keyboard interaction to toggle menu. The menu traps focus and can be interacted with using the keyboard. The first interactive item of the menu has focus when menu open.
3
5
  * @param {string} menuId - The id of the menu.
4
6
  * @param {string} menuItemsClass - The class of the items that are children of the menu.
5
7
  * @param {string} triggerId - The id of the button that triggers the menu.
6
8
  */
9
+
7
10
  declare function makeMenuAccessible({ menuId, menuItemsClass, triggerId }: {
8
11
  menuId: string;
9
12
  menuItemsClass: string;
10
13
  triggerId: string;
11
- }): {
12
- openMenu: () => void;
13
- closeMenu: () => void;
14
- cleanup: () => void;
15
- refresh?: undefined;
16
- } | {
17
- openMenu: () => void;
18
- closeMenu: () => void;
19
- cleanup: () => void;
20
- refresh: () => void;
21
- };
14
+ }): AccessibilityInstance;
22
15
 
23
16
  export { makeMenuAccessible };
@@ -1,4 +1,4 @@
1
- import { handleKeyPress } from '../chunk-DF4OR64G.js';
1
+ import { handleKeyPress } from '../chunk-TBJ6MIC7.js';
2
2
 
3
3
  // src/menu/src/makeMenuAccessible/makeMenuAccessible.ts
4
4
  function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
@@ -18,7 +18,14 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
18
18
  }, cleanup: () => {
19
19
  } };
20
20
  }
21
- const handlerMap = /* @__PURE__ */ new Map();
21
+ if (!/^[\w-]+$/.test(menuId)) {
22
+ console.error("[aria-ease] Invalid menuId: must be alphanumeric");
23
+ return { openMenu: () => {
24
+ }, closeMenu: () => {
25
+ }, cleanup: () => {
26
+ } };
27
+ }
28
+ const handlerMap = /* @__PURE__ */ new WeakMap();
22
29
  const submenuInstances = /* @__PURE__ */ new Map();
23
30
  let cachedItems = null;
24
31
  let filteredItems = null;
@@ -137,7 +144,7 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
137
144
  setAria(true);
138
145
  const items = getFilteredItems();
139
146
  addListeners();
140
- if (items.length > 0) {
147
+ if (items && items.length > 0) {
141
148
  items[0].focus();
142
149
  }
143
150
  }
@@ -162,5 +169,3 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId }) {
162
169
  }
163
170
 
164
171
  export { makeMenuAccessible };
165
- //# sourceMappingURL=index.js.map
166
- //# sourceMappingURL=index.js.map
@@ -31,5 +31,3 @@ function updateRadioAriaAttributes(radioId, radiosClass, radioStates, currentPre
31
31
  }
32
32
 
33
33
  exports.updateRadioAriaAttributes = updateRadioAriaAttributes;
34
- //# sourceMappingURL=index.cjs.map
35
- //# sourceMappingURL=index.cjs.map
@@ -1,4 +1,4 @@
1
- import { R as RadioStates } from '../Types.d-BbztRe-S.cjs';
1
+ import { R as RadioStates } from '../Types.d-w1KLKLcA.cjs';
2
2
 
3
3
  /**
4
4
  * Adds screen reader accessibility to multiple radio buttons. Updates the aria attributes of the radio buttons. Radio elements must possess the following aria attributes; aria-checked and aria-label.
@@ -1,4 +1,4 @@
1
- import { R as RadioStates } from '../Types.d-BbztRe-S.js';
1
+ import { R as RadioStates } from '../Types.d-w1KLKLcA.js';
2
2
 
3
3
  /**
4
4
  * Adds screen reader accessibility to multiple radio buttons. Updates the aria attributes of the radio buttons. Radio elements must possess the following aria attributes; aria-checked and aria-label.
@@ -29,5 +29,3 @@ function updateRadioAriaAttributes(radioId, radiosClass, radioStates, currentPre
29
29
  }
30
30
 
31
31
  export { updateRadioAriaAttributes };
32
- //# sourceMappingURL=index.js.map
33
- //# sourceMappingURL=index.js.map
@@ -28,5 +28,3 @@ function updateToggleAriaAttribute(toggleId, togglesClass, toggleStates, current
28
28
  }
29
29
 
30
30
  exports.updateToggleAriaAttribute = updateToggleAriaAttribute;
31
- //# sourceMappingURL=index.cjs.map
32
- //# sourceMappingURL=index.cjs.map
@@ -1,4 +1,4 @@
1
- import { T as ToggleStates } from '../Types.d-BbztRe-S.cjs';
1
+ import { T as ToggleStates } from '../Types.d-w1KLKLcA.cjs';
2
2
 
3
3
  /**
4
4
  * Adds screen reader accessibility to toggle buttons. Updates the aria attributes of the toggle buttons. Button must be a semantic button element or a non-semantic element with a role of button, and possess the aria-pressed attribute.
@@ -1,4 +1,4 @@
1
- import { T as ToggleStates } from '../Types.d-BbztRe-S.js';
1
+ import { T as ToggleStates } from '../Types.d-w1KLKLcA.js';
2
2
 
3
3
  /**
4
4
  * Adds screen reader accessibility to toggle buttons. Updates the aria attributes of the toggle buttons. Button must be a semantic button element or a non-semantic element with a role of button, and possess the aria-pressed attribute.
@@ -26,5 +26,3 @@ function updateToggleAriaAttribute(toggleId, togglesClass, toggleStates, current
26
26
  }
27
27
 
28
28
  export { updateToggleAriaAttribute };
29
- //# sourceMappingURL=index.js.map
30
- //# sourceMappingURL=index.js.map
@@ -199,5 +199,3 @@ ${"\u2550".repeat(60)}`);
199
199
  };
200
200
 
201
201
  export { ContractReporter, __commonJS, __toESM, contract_default };
202
- //# sourceMappingURL=chunk-UAS6V5MH.js.map
203
- //# sourceMappingURL=chunk-UAS6V5MH.js.map
@@ -1,4 +1,4 @@
1
- import { ContractReporter, contract_default } from './chunk-UAS6V5MH.js';
1
+ import { ContractReporter, contract_default } from './chunk-SSBW5VAA.js';
2
2
  import { chromium } from 'playwright';
3
3
  import { readFileSync } from 'fs';
4
4
 
@@ -247,5 +247,3 @@ async function runContractTestsPlaywright(componentName, url) {
247
247
  }
248
248
 
249
249
  export { runContractTestsPlaywright };
250
- //# sourceMappingURL=contractTestRunnerPlaywright-IBC4FHWK.js.map
251
- //# sourceMappingURL=contractTestRunnerPlaywright-IBC4FHWK.js.map
@@ -0,0 +1,249 @@
1
+ import { ContractReporter, contract_default } from './chunk-SSBW5VAA.js';
2
+ import { chromium } from 'playwright';
3
+ import { readFileSync } from 'fs';
4
+
5
+ async function runContractTestsPlaywright(componentName, url) {
6
+ const reporter = new ContractReporter(true);
7
+ const contractTyped = contract_default;
8
+ const contractPath = contractTyped[componentName]?.path;
9
+ if (!contractPath) {
10
+ throw new Error(`Contract path not found for component: ${componentName}`);
11
+ }
12
+ const resolvedPath = new URL(contractPath, import.meta.url).pathname;
13
+ const contractData = readFileSync(resolvedPath, "utf-8");
14
+ const componentContract = JSON.parse(contractData);
15
+ const totalTests = componentContract.static[0].assertions.length + componentContract.dynamic.length;
16
+ reporter.start(componentName, totalTests);
17
+ const failures = [];
18
+ const passes = [];
19
+ let browser = null;
20
+ try {
21
+ browser = await chromium.launch({ headless: true });
22
+ const context = await browser.newContext();
23
+ const page = await context.newPage();
24
+ await page.goto(url, { waitUntil: "networkidle" });
25
+ await page.waitForSelector(componentContract.selectors.trigger, { timeout: 3e4 });
26
+ async function resolveRelativeTarget(selector, relative) {
27
+ const items = await page.locator(selector).all();
28
+ switch (relative) {
29
+ case "first":
30
+ return items[0];
31
+ case "second":
32
+ return items[1];
33
+ case "last":
34
+ return items[items.length - 1];
35
+ case "next": {
36
+ const currentIndex = await page.evaluate(([sel]) => {
37
+ const items2 = Array.from(document.querySelectorAll(sel));
38
+ return items2.indexOf(document.activeElement);
39
+ }, [selector]);
40
+ const nextIndex = (currentIndex + 1) % items.length;
41
+ return items[nextIndex];
42
+ }
43
+ case "previous": {
44
+ const currentIndex = await page.evaluate(([sel]) => {
45
+ const items2 = Array.from(document.querySelectorAll(sel));
46
+ return items2.indexOf(document.activeElement);
47
+ }, [selector]);
48
+ const prevIndex = (currentIndex - 1 + items.length) % items.length;
49
+ return items[prevIndex];
50
+ }
51
+ default:
52
+ return null;
53
+ }
54
+ }
55
+ for (const test of componentContract.static[0]?.assertions || []) {
56
+ if (test.target === "relative") continue;
57
+ const targetSelector = componentContract.selectors[test.target];
58
+ if (!targetSelector) {
59
+ failures.push(`Selector for target ${test.target} not found.`);
60
+ continue;
61
+ }
62
+ const target = page.locator(targetSelector).first();
63
+ const exists = await target.count() > 0;
64
+ if (!exists) {
65
+ failures.push(`Target ${test.target} not found.`);
66
+ continue;
67
+ }
68
+ if (!test.expectedValue) {
69
+ const attributes = test.attribute.split(" | ");
70
+ let hasAny = false;
71
+ for (const attr of attributes) {
72
+ const value = await target.getAttribute(attr.trim());
73
+ if (value !== null) {
74
+ hasAny = true;
75
+ break;
76
+ }
77
+ }
78
+ if (!hasAny) {
79
+ failures.push(test.failureMessage + ` None of the attributes "${test.attribute}" are present.`);
80
+ } else {
81
+ passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
82
+ }
83
+ } else {
84
+ const attributeValue = await target.getAttribute(test.attribute);
85
+ const expectedValues = test.expectedValue.split(" | ");
86
+ if (!attributeValue || !expectedValues.includes(attributeValue)) {
87
+ failures.push(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
88
+ } else {
89
+ passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
90
+ }
91
+ }
92
+ }
93
+ for (const dynamicTest of componentContract.dynamic || []) {
94
+ const { action, assertions } = dynamicTest;
95
+ const failuresBeforeTest = failures.length;
96
+ const containerElement = page.locator(componentContract.selectors.container).first();
97
+ const triggerElement = page.locator(componentContract.selectors.trigger).first();
98
+ const isContainerVisible = await containerElement.isVisible();
99
+ if (isContainerVisible) {
100
+ await triggerElement.click();
101
+ await page.waitForTimeout(50);
102
+ }
103
+ for (const act of action) {
104
+ if (act.type === "click") {
105
+ if (act.target === "document") {
106
+ await page.mouse.click(10, 10);
107
+ } else {
108
+ const actionSelector = componentContract.selectors[act.target];
109
+ if (!actionSelector) {
110
+ failures.push(`Selector for action target ${act.target} not found.`);
111
+ continue;
112
+ }
113
+ await page.locator(actionSelector).first().click();
114
+ await page.waitForTimeout(200);
115
+ }
116
+ }
117
+ if (act.type === "keypress" && act.key) {
118
+ const keyMap = {
119
+ "Space": "Space",
120
+ "Enter": "Enter",
121
+ "Escape": "Escape",
122
+ "Arrow Up": "ArrowUp",
123
+ "Arrow Down": "ArrowDown",
124
+ "Arrow Left": "ArrowLeft",
125
+ "Arrow Right": "ArrowRight",
126
+ "Home": "Home",
127
+ "End": "End",
128
+ "Tab": "Tab"
129
+ };
130
+ let keyValue = keyMap[act.key] || act.key;
131
+ if (keyValue === "Space") {
132
+ keyValue = " ";
133
+ } else if (keyValue.includes(" ")) {
134
+ keyValue = keyValue.replace(/ /g, "");
135
+ }
136
+ if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
137
+ await page.waitForTimeout(100);
138
+ await page.keyboard.press(keyValue);
139
+ await page.waitForTimeout(50);
140
+ } else {
141
+ const keypressSelector = componentContract.selectors[act.target];
142
+ if (!keypressSelector) {
143
+ failures.push(`Selector for keypress target ${act.target} not found.`);
144
+ continue;
145
+ }
146
+ const target = page.locator(keypressSelector).first();
147
+ await target.press(keyValue);
148
+ }
149
+ }
150
+ await page.waitForTimeout(100);
151
+ }
152
+ for (const assertion of assertions) {
153
+ let target;
154
+ if (assertion.target === "relative") {
155
+ const relativeSelector = componentContract.selectors.relative;
156
+ if (!relativeSelector) {
157
+ failures.push("Relative selector is not defined in the contract.");
158
+ continue;
159
+ }
160
+ if (!assertion.expectedValue) {
161
+ failures.push("Expected value for relative target is not defined.");
162
+ continue;
163
+ }
164
+ target = await resolveRelativeTarget(relativeSelector, assertion.expectedValue);
165
+ } else {
166
+ const assertionSelector = componentContract.selectors[assertion.target];
167
+ if (!assertionSelector) {
168
+ failures.push(`Selector for assertion target ${assertion.target} not found.`);
169
+ continue;
170
+ }
171
+ target = page.locator(assertionSelector).first();
172
+ }
173
+ if (!target) {
174
+ failures.push(`Target ${assertion.target} not found.`);
175
+ continue;
176
+ }
177
+ if (assertion.assertion === "toBeVisible") {
178
+ const isVisible = await target.isVisible();
179
+ if (isVisible) {
180
+ passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
181
+ } else {
182
+ failures.push(`${assertion.failureMessage}`);
183
+ }
184
+ }
185
+ if (assertion.assertion === "notToBeVisible") {
186
+ const isVisible = await target.isVisible();
187
+ if (!isVisible) {
188
+ passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
189
+ } else {
190
+ failures.push(assertion.failureMessage + ` ${assertion.target} is still visible.`);
191
+ }
192
+ }
193
+ if (assertion.assertion === "toHaveAttribute" && assertion.attribute && assertion.expectedValue) {
194
+ const attributeValue = await target.getAttribute(assertion.attribute);
195
+ if (attributeValue === assertion.expectedValue) {
196
+ passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
197
+ } else {
198
+ failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should be "${assertion.expectedValue}", found "${attributeValue}".`);
199
+ }
200
+ }
201
+ if (assertion.assertion === "toHaveFocus") {
202
+ const hasFocus = await target.evaluate((el) => el === document.activeElement);
203
+ if (hasFocus) {
204
+ passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
205
+ } else {
206
+ failures.push(`${assertion.failureMessage}`);
207
+ }
208
+ }
209
+ if (assertion.assertion === "toHaveRole" && assertion.expectedValue) {
210
+ const roleValue = await target.getAttribute("role");
211
+ if (roleValue === assertion.expectedValue) {
212
+ passes.push(`${assertion.target} has role "${assertion.expectedValue}". Test: "${dynamicTest.description}".`);
213
+ } else {
214
+ failures.push(assertion.failureMessage + ` Expected role "${assertion.expectedValue}", found "${roleValue}".`);
215
+ }
216
+ }
217
+ }
218
+ const failuresAfterTest = failures.length;
219
+ const testPassed = failuresAfterTest === failuresBeforeTest;
220
+ const failureMessage = testPassed ? void 0 : failures[failures.length - 1];
221
+ reporter.reportTest(dynamicTest, testPassed ? "pass" : "fail", failureMessage);
222
+ }
223
+ const staticPassed = componentContract.static[0].assertions.length;
224
+ const staticFailed = 0;
225
+ reporter.reportStatic(staticPassed, staticFailed);
226
+ reporter.summary(failures);
227
+ } catch (error) {
228
+ if (error instanceof Error) {
229
+ if (error.message.includes("Executable doesn't exist")) {
230
+ console.error("\n\u274C Playwright browsers not found!\n");
231
+ console.log("\u{1F4E6} Run: npx playwright install chromium\n");
232
+ failures.push("Playwright browser not installed. Run: npx playwright install chromium");
233
+ } else if (error.message.includes("net::ERR_CONNECTION_REFUSED")) {
234
+ console.error("\n\u274C Cannot connect to dev server!\n");
235
+ console.log(` Make sure your dev server is running at ${url}
236
+ `);
237
+ failures.push(`Dev server not running at ${url}`);
238
+ } else {
239
+ console.error("\u274C Playwright test error:", error.message);
240
+ failures.push(`Test error: ${error.message}`);
241
+ }
242
+ }
243
+ } finally {
244
+ if (browser) await browser.close();
245
+ }
246
+ return { passes, failures };
247
+ }
248
+
249
+ export { runContractTestsPlaywright };