aria-ease 6.2.2 → 6.3.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.
Files changed (47) hide show
  1. package/README.md +91 -12
  2. package/bin/{chunk-7RMRFSJL.js → chunk-XLG3MIPQ.js} +5 -1
  3. package/bin/cli.cjs +52 -11
  4. package/bin/cli.js +1 -1
  5. package/bin/{contractTestRunnerPlaywright-ACAWN34W.js → contractTestRunnerPlaywright-JXQUUKFO.js} +48 -11
  6. package/bin/{test-A3ESFXOR.js → test-XSDP2NX3.js} +2 -2
  7. package/dist/{chunk-PDZQOXUN.js → chunk-RDEAG4KE.js} +5 -1
  8. package/dist/{contractTestRunnerPlaywright-O7FF7GV4.js → contractTestRunnerPlaywright-EUXD6ZZK.js} +48 -11
  9. package/dist/index.cjs +316 -69
  10. package/dist/index.d.cts +34 -4
  11. package/dist/index.d.ts +34 -4
  12. package/dist/index.js +265 -60
  13. package/dist/src/{Types.d-CRjhbrcw.d.cts → Types.d-DYfYR3Vc.d.cts} +18 -1
  14. package/dist/src/{Types.d-CRjhbrcw.d.ts → Types.d-DYfYR3Vc.d.ts} +18 -1
  15. package/dist/src/accordion/index.d.cts +2 -2
  16. package/dist/src/accordion/index.d.ts +2 -2
  17. package/dist/src/block/index.d.cts +1 -1
  18. package/dist/src/block/index.d.ts +1 -1
  19. package/dist/src/checkbox/index.cjs +0 -22
  20. package/dist/src/checkbox/index.d.cts +2 -2
  21. package/dist/src/checkbox/index.d.ts +2 -2
  22. package/dist/src/checkbox/index.js +0 -22
  23. package/dist/src/combobox/index.d.cts +1 -1
  24. package/dist/src/combobox/index.d.ts +1 -1
  25. package/dist/src/menu/index.d.cts +1 -1
  26. package/dist/src/menu/index.d.ts +1 -1
  27. package/dist/src/radio/index.cjs +0 -8
  28. package/dist/src/radio/index.d.cts +2 -2
  29. package/dist/src/radio/index.d.ts +2 -2
  30. package/dist/src/radio/index.js +0 -8
  31. package/dist/src/tabs/index.cjs +265 -0
  32. package/dist/src/tabs/index.d.cts +16 -0
  33. package/dist/src/tabs/index.d.ts +16 -0
  34. package/dist/src/tabs/index.js +263 -0
  35. package/dist/src/toggle/index.cjs +0 -28
  36. package/dist/src/toggle/index.d.cts +1 -1
  37. package/dist/src/toggle/index.d.ts +1 -1
  38. package/dist/src/toggle/index.js +0 -28
  39. package/dist/src/utils/test/{chunk-7RMRFSJL.js → chunk-XLG3MIPQ.js} +5 -1
  40. package/dist/src/utils/test/{contractTestRunnerPlaywright-7BPRTIN4.js → contractTestRunnerPlaywright-N77NEY25.js} +48 -11
  41. package/dist/src/utils/test/contracts/AccordionContract.json +18 -17
  42. package/dist/src/utils/test/contracts/ComboboxContract.json +32 -48
  43. package/dist/src/utils/test/contracts/MenuContract.json +19 -25
  44. package/dist/src/utils/test/contracts/TabsContract.json +348 -0
  45. package/dist/src/utils/test/index.cjs +52 -11
  46. package/dist/src/utils/test/index.js +2 -2
  47. package/package.json +8 -3
@@ -2,7 +2,8 @@
2
2
  "meta": {
3
3
  "id": "aria-ease.menu",
4
4
  "version": "1.0.0",
5
- "lastUpdated": "11-06-2026",
5
+ "created": "11-02-2026",
6
+ "lastUpdated": "28-02-2026",
6
7
  "description": "ARIA Menu interaction contract. Validates the ARIA and interaction contract for a custom menu component following the ARIA Authoring Practices Guide menu with popup pattern.",
7
8
  "source": {
8
9
  "apg": "https://www.w3.org/WAI/ARIA/apg/patterns/menubar/",
@@ -115,7 +116,7 @@
115
116
  ]
116
117
  },
117
118
  {
118
- "description": "Pressing Enter key on trigger opens the menu and focuses first item.",
119
+ "description": "Pressing Enter on trigger opens the menu and focuses first item.",
119
120
  "action": [
120
121
  { "type": "keypress", "target": "trigger", "key": "Enter" }
121
122
  ],
@@ -130,7 +131,7 @@
130
131
  "assertion": "toHaveAttribute",
131
132
  "attribute": "aria-expanded",
132
133
  "expectedValue": "true",
133
- "failureMessage": "Trigger's aria-expanded should be true after pressing Enter."
134
+ "failureMessage": "Trigger's should have aria-expanded=true after pressing Enter."
134
135
  },
135
136
  {
136
137
  "target": "relative",
@@ -141,7 +142,7 @@
141
142
  ]
142
143
  },
143
144
  {
144
- "description": "Pressing Space key on trigger opens the menu and focuses first item.",
145
+ "description": "Pressing Space on trigger opens the menu and focuses first item.",
145
146
  "action": [
146
147
  { "type": "keypress", "target": "trigger", "key": "Space" }
147
148
  ],
@@ -156,7 +157,7 @@
156
157
  "assertion": "toHaveAttribute",
157
158
  "attribute": "aria-expanded",
158
159
  "expectedValue": "true",
159
- "failureMessage": "Trigger's aria-expanded should be true after pressing Space."
160
+ "failureMessage": "Trigger's should have aria-expanded=true after pressing Space."
160
161
  },
161
162
  {
162
163
  "target": "relative",
@@ -167,8 +168,7 @@
167
168
  ]
168
169
  },
169
170
  {
170
- "description": "Arrow Down key moves focus to next menu item.",
171
- "requiresBrowser": true,
171
+ "description": "Down Arrow moves focus to next menu item.",
172
172
  "action": [
173
173
  { "type": "click", "target": "trigger" },
174
174
  { "type": "keypress", "target": "focusable", "key": "ArrowDown" }
@@ -178,13 +178,12 @@
178
178
  "target": "relative",
179
179
  "assertion": "toHaveFocus",
180
180
  "expectedValue": "second",
181
- "failureMessage": "Second menu item should have focus after pressing Arrow Down."
181
+ "failureMessage": "Second menu item should have focus after pressing Down Arrow."
182
182
  }
183
183
  ]
184
184
  },
185
185
  {
186
- "description": "Arrow Up key moves focus to previous menu item.",
187
- "requiresBrowser": true,
186
+ "description": "Up Arrow moves focus to previous menu item.",
188
187
  "action": [
189
188
  { "type": "click", "target": "trigger" },
190
189
  { "type": "keypress", "target": "focusable", "key": "ArrowDown" },
@@ -195,13 +194,12 @@
195
194
  "target": "relative",
196
195
  "assertion": "toHaveFocus",
197
196
  "expectedValue": "first",
198
- "failureMessage": "First menu item should have focus after Arrow Down then Arrow Up."
197
+ "failureMessage": "First menu item should have focus after Down Arrow then Up Arrow."
199
198
  }
200
199
  ]
201
200
  },
202
201
  {
203
- "description": "Arrow Right key moves focus to next menu item.",
204
- "requiresBrowser": true,
202
+ "description": "Right Arrow moves focus to next menu item.",
205
203
  "action": [
206
204
  { "type": "click", "target": "trigger" },
207
205
  { "type": "keypress", "target": "focusable", "key": "ArrowRight" }
@@ -211,13 +209,12 @@
211
209
  "target": "relative",
212
210
  "assertion": "toHaveFocus",
213
211
  "expectedValue": "second",
214
- "failureMessage": "Second menu item should have focus after pressing Arrow Right."
212
+ "failureMessage": "Second menu item should have focus after pressing Right Arrow."
215
213
  }
216
214
  ]
217
215
  },
218
216
  {
219
- "description": "Arrow Left key moves focus to previous menu item.",
220
- "requiresBrowser": true,
217
+ "description": "Left Arrow moves focus to previous menu item.",
221
218
  "action": [
222
219
  { "type": "click", "target": "trigger" },
223
220
  { "type": "keypress", "target": "focusable", "key": "ArrowRight" },
@@ -228,12 +225,12 @@
228
225
  "target": "relative",
229
226
  "assertion": "toHaveFocus",
230
227
  "expectedValue": "first",
231
- "failureMessage": "First menu item should have focus after Arrow Right then Arrow Left."
228
+ "failureMessage": "First menu item should have focus after Right Arrow then Left Arrow."
232
229
  }
233
230
  ]
234
231
  },
235
232
  {
236
- "description": "Escape key closes the menu and returns focus to trigger.",
233
+ "description": "Escape closes the menu and returns focus to trigger.",
237
234
  "action": [
238
235
  { "type": "click", "target": "trigger" },
239
236
  { "type": "keypress", "target": "focusable", "key": "Escape" }
@@ -327,8 +324,7 @@
327
324
  ]
328
325
  },
329
326
  {
330
- "description": "Right arrow on menuitem with submenu opens the submenu",
331
- "requiresBrowser": true,
327
+ "description": "Right Arrow on menuitem with submenu opens the submenu",
332
328
  "action": [
333
329
  { "type": "click", "target": "trigger" },
334
330
  { "type": "keypress", "target": "submenuTrigger", "key": "ArrowRight" }
@@ -337,13 +333,12 @@
337
333
  {
338
334
  "target": "submenu",
339
335
  "assertion": "toBeVisible",
340
- "failureMessage": "Submenu should open when Right arrow pressed on item with submenu"
336
+ "failureMessage": "Submenu should open when Right Arrow pressed on item with submenu"
341
337
  }
342
338
  ]
343
339
  },
344
340
  {
345
341
  "description": "Tab on a menuitem closes the menu and update aria-expanded",
346
- "requiresBrowser": true,
347
342
  "action": [
348
343
  { "type": "click", "target": "trigger" },
349
344
  { "type": "keypress", "target": "focusable", "key": "Tab" }
@@ -359,13 +354,12 @@
359
354
  "assertion": "toHaveAttribute",
360
355
  "attribute": "aria-expanded",
361
356
  "expectedValue": "false",
362
- "failureMessage": "Trigger's aria-expanded should be false after Tab key closes the menu."
357
+ "failureMessage": "Trigger's aria-expanded should be false after Tab closes the menu."
363
358
  }
364
359
  ]
365
360
  },
366
361
  {
367
362
  "description": "Shift+Tab on a menuitem closes the menu and update aria-expanded",
368
- "requiresBrowser": true,
369
363
  "action": [
370
364
  { "type": "click", "target": "trigger" },
371
365
  { "type": "keypress", "target": "focusable", "key": "Shift+Tab" }
@@ -381,7 +375,7 @@
381
375
  "assertion": "toHaveAttribute",
382
376
  "attribute": "aria-expanded",
383
377
  "expectedValue": "false",
384
- "failureMessage": "Trigger's aria-expanded should be false after Shift+Tab key closes the menu."
378
+ "failureMessage": "Trigger's aria-expanded should be false after Shift+Tab closes the menu."
385
379
  }
386
380
  ]
387
381
  }
@@ -0,0 +1,348 @@
1
+ {
2
+ "meta": {
3
+ "id": "aria-ease.tabs",
4
+ "version": "1.0.0",
5
+ "created": "28-02-2026",
6
+ "lastUpdated": "28-02-2026",
7
+ "description": "ARIA tabs interaction contract. Validates the ARIA and interaction contract for a custom tabs component following the ARIA Authoring Practices Guide pattern.",
8
+ "source": {
9
+ "apg": "https://www.w3.org/WAI/ARIA/apg/patterns/tabs/",
10
+ "wcag": ["2.2 AA"]
11
+ },
12
+ "W3CName": "Tabs"
13
+ },
14
+
15
+ "selectors": {
16
+ "tablist": "[role=tablist]",
17
+ "tab": "[role=tab]",
18
+ "panel": "[role=tabpanel]",
19
+ "focusable": "[role=tab]",
20
+ "relative": "[role=tab]"
21
+ },
22
+
23
+ "observables": {
24
+ "observable": "focus | visible | attribute | role",
25
+ "target": "tab | relative | panel | tablist",
26
+ "relative": "first | last | next | previous"
27
+ },
28
+
29
+ "static": [
30
+ {
31
+ "assertions": [
32
+ {
33
+ "target": "tablist",
34
+ "assertion": "toHaveAttribute",
35
+ "attribute": "aria-label | aria-labelledby",
36
+ "failureMessage": "Tab list doesn't conform to the ARIA Tab pattern as specified in APG 1.2. Tablist should have 'aria-label' or 'aria-labelledby' attribute to provide an accessible label for assistive technology."
37
+ },
38
+ {
39
+ "target": "tab",
40
+ "assertion": "toHaveAttribute",
41
+ "attribute": "aria-selected",
42
+ "expectedValue": "true | false",
43
+ "failureMessage": "Tab element doesn't conform to the ARIA Tab pattern as specified in APG 1.2. Tab element should have 'aria-selected=true | false' attribute. This helps assistive technology to keep track of the active state of the tab panel that the tab controls."
44
+ },
45
+ {
46
+ "target": "tab",
47
+ "assertion": "toHaveAttribute",
48
+ "attribute": "aria-controls",
49
+ "failureMessage": "Tab element doesn't conform to the ARIA Tab pattern as specified in APG 1.2. Tab element should have 'aria-controls' attribute that points to the id of the tab panel it controls. This helps assistive technology to associate the tab with its corresponding tab panel."
50
+ },
51
+ {
52
+ "target": "panel",
53
+ "assertion": "toHaveAttribute",
54
+ "attribute": "role",
55
+ "expectedValue": "tabpanel",
56
+ "failureMessage": "Tab panel doesn't conform to the ARIA Tab pattern as specified in APG 1.2. Each tab panel should have 'role=tabpanel' attribute. This helps assistive technology identify the panel as a significant region of the page."
57
+ },
58
+ {
59
+ "target": "panel",
60
+ "assertion": "toHaveAttribute",
61
+ "attribute": "aria-labelledby",
62
+ "failureMessage": "Tab panel doesn't conform to the ARIA Tab pattern as specified in APG 1.2. Each tab panel should have 'aria-labelledby' attribute that points to the id of the tab that controls it. This helps assistive technology associate the tab panel with its corresponding tab."
63
+ },
64
+ {
65
+ "target": "tab",
66
+ "assertion": "toHaveAttribute",
67
+ "attribute": "tabindex",
68
+ "expectedValue": "0 | -1",
69
+ "failureMessage": "Tab element doesn't conform to the ARIA Tab pattern. Tab should have 'tabindex' to enable roving tabindex keyboard navigation. Active tab should have tabindex='0', inactive tabs should have tabindex='-1'."
70
+ },
71
+ {
72
+ "target": "tablist",
73
+ "assertion": "toHaveAttribute",
74
+ "attribute": "aria-orientation",
75
+ "expectedValue": "horizontal | vertical",
76
+ "failureMessage": "Tab list doesn't conform to the ARIA Tab pattern as specified in APG 1.2. Tab list should have 'aria-orientation' attribute set to 'horizontal' or 'vertical'."
77
+ }
78
+ ]
79
+ }
80
+ ],
81
+
82
+ "dynamic": [
83
+ {
84
+ "description": "Only one tab panel is visible at a time.",
85
+ "action": [],
86
+ "assertions": [
87
+ {
88
+ "target": "panel",
89
+ "assertion": "toBeVisible",
90
+ "failureMessage": "At least one tab panel should be visible initially."
91
+ }
92
+ ]
93
+ },
94
+ {
95
+ "description": "Right Arrow moves focus to next tab and activates it (automatic activation).",
96
+ "isVertical": false,
97
+ "action": [
98
+ { "type": "focus", "target": "focusable" },
99
+ { "type": "keypress", "target": "focusable", "key": "ArrowRight" }
100
+ ],
101
+ "assertions": [
102
+ {
103
+ "target": "relative",
104
+ "assertion": "toHaveFocus",
105
+ "expectedValue": "second",
106
+ "failureMessage": "Focus should move to the second tab after pressing Right Arrow."
107
+ },
108
+ {
109
+ "target": "relative",
110
+ "assertion": "toHaveAttribute",
111
+ "attribute": "aria-selected",
112
+ "expectedValue": "true",
113
+ "relativeTarget": "second",
114
+ "failureMessage": "Second tab should be activated (aria-selected=true) after Right Arrow press."
115
+ },
116
+ {
117
+ "target": "relative",
118
+ "assertion": "toHaveAttribute",
119
+ "attribute": "tabindex",
120
+ "expectedValue": "0",
121
+ "relativeTarget": "second",
122
+ "failureMessage": "Second tab should have tabindex='0' after activation."
123
+ },
124
+ {
125
+ "target": "relative",
126
+ "assertion": "toHaveAttribute",
127
+ "attribute": "aria-selected",
128
+ "expectedValue": "false",
129
+ "relativeTarget": "first",
130
+ "failureMessage": "Previous tab should have aria-selected=false after moving to another tab."
131
+ }
132
+ ]
133
+ },
134
+ {
135
+ "description": "Left Arrow moves focus to previous tab and activates it (wraps to last).",
136
+ "isVertical": false,
137
+ "action": [
138
+ { "type": "focus", "target": "focusable" },
139
+ { "type": "keypress", "target": "focusable", "key": "ArrowLeft" }
140
+ ],
141
+ "assertions": [
142
+ {
143
+ "target": "relative",
144
+ "assertion": "toHaveFocus",
145
+ "expectedValue": "last",
146
+ "failureMessage": "Focus should wrap to last tab after pressing Left Arrow on first tab."
147
+ },
148
+ {
149
+ "target": "relative",
150
+ "assertion": "toHaveAttribute",
151
+ "attribute": "aria-selected",
152
+ "expectedValue": "true",
153
+ "relativeTarget": "last",
154
+ "failureMessage": "Last tab should be activated (aria-selected=true) after Left Arrow press."
155
+ },
156
+ {
157
+ "target": "relative",
158
+ "assertion": "toHaveAttribute",
159
+ "attribute": "tabindex",
160
+ "expectedValue": "0",
161
+ "relativeTarget": "last",
162
+ "failureMessage": "Last tab should have tabindex='0' after activation."
163
+ }
164
+ ]
165
+ },
166
+ {
167
+ "description": "Down Arrow moves focus to next tab and activates it (vertical tabs).",
168
+ "isVertical": true,
169
+ "action": [
170
+ { "type": "focus", "target": "focusable" },
171
+ { "type": "keypress", "target": "focusable", "key": "ArrowDown" }
172
+ ],
173
+ "assertions": [
174
+ {
175
+ "target": "relative",
176
+ "assertion": "toHaveFocus",
177
+ "expectedValue": "second",
178
+ "failureMessage": "Focus should move to second tab after pressing Down Arrow (vertical orientation)."
179
+ },
180
+ {
181
+ "target": "relative",
182
+ "assertion": "toHaveAttribute",
183
+ "attribute": "aria-selected",
184
+ "expectedValue": "true",
185
+ "relativeTarget": "second",
186
+ "failureMessage": "Second tab should be activated after Down Arrow press."
187
+ }
188
+ ]
189
+ },
190
+ {
191
+ "description": "Up Arrow moves focus to previous tab and activates it (wraps to last, vertical tabs).",
192
+ "isVertical": true,
193
+ "action": [
194
+ { "type": "focus", "target": "focusable" },
195
+ { "type": "keypress", "target": "focusable", "key": "ArrowUp" }
196
+ ],
197
+ "assertions": [
198
+ {
199
+ "target": "relative",
200
+ "assertion": "toHaveFocus",
201
+ "expectedValue": "last",
202
+ "failureMessage": "Focus should wrap to last tab after pressing Up Arrow on first tab (vertical orientation)."
203
+ },
204
+ {
205
+ "target": "relative",
206
+ "assertion": "toHaveAttribute",
207
+ "attribute": "aria-selected",
208
+ "expectedValue": "true",
209
+ "relativeTarget": "last",
210
+ "failureMessage": "Last tab should be activated after Up Arrow press."
211
+ }
212
+ ]
213
+ },
214
+ {
215
+ "description": "Home moves focus to first tab and activates it.",
216
+ "isOptional": true,
217
+ "action": [
218
+ { "type": "keypress", "target": "focusable", "key": "ArrowRight" },
219
+ { "type": "keypress", "target": "focusable", "key": "Home" }
220
+ ],
221
+ "assertions": [
222
+ {
223
+ "target": "relative",
224
+ "assertion": "toHaveFocus",
225
+ "expectedValue": "first",
226
+ "failureMessage": "Focus should move to first tab after pressing Home."
227
+ },
228
+ {
229
+ "target": "relative",
230
+ "assertion": "toHaveAttribute",
231
+ "attribute": "aria-selected",
232
+ "expectedValue": "true",
233
+ "relativeTarget": "first",
234
+ "failureMessage": "First tab should be activated after Home press."
235
+ }
236
+ ]
237
+ },
238
+ {
239
+ "description": "End moves focus to last tab and activates it.",
240
+ "isOptional": true,
241
+ "action": [
242
+ { "type": "keypress", "target": "focusable", "key": "End" }
243
+ ],
244
+ "assertions": [
245
+ {
246
+ "target": "relative",
247
+ "assertion": "toHaveFocus",
248
+ "expectedValue": "last",
249
+ "failureMessage": "Focus should move to last tab after pressing End."
250
+ },
251
+ {
252
+ "target": "relative",
253
+ "assertion": "toHaveAttribute",
254
+ "attribute": "aria-selected",
255
+ "expectedValue": "true",
256
+ "relativeTarget": "last",
257
+ "failureMessage": "Last tab should be activated after End press."
258
+ }
259
+ ]
260
+ },
261
+ {
262
+ "description": "Clicking a tab activates it and displays its panel.",
263
+ "action": [
264
+ { "type": "click", "target": "relative", "relativeTarget": "second" }
265
+ ],
266
+ "assertions": [
267
+ {
268
+ "target": "relative",
269
+ "assertion": "toHaveAttribute",
270
+ "attribute": "aria-selected",
271
+ "expectedValue": "true",
272
+ "relativeTarget": "second",
273
+ "failureMessage": "Clicked tab should have aria-selected=true."
274
+ },
275
+ {
276
+ "target": "relative",
277
+ "assertion": "toHaveAttribute",
278
+ "attribute": "tabindex",
279
+ "expectedValue": "0",
280
+ "relativeTarget": "second",
281
+ "failureMessage": "Clicked tab should have tabindex='0'."
282
+ },
283
+ {
284
+ "target": "relative",
285
+ "assertion": "toHaveAttribute",
286
+ "attribute": "aria-selected",
287
+ "expectedValue": "false",
288
+ "relativeTarget": "first",
289
+ "failureMessage": "Previous tab should have aria-selected=false after clicking another tab."
290
+ }
291
+ ]
292
+ },
293
+ {
294
+ "description": "Pressing Enter on tab element activates its associated tab panel.",
295
+ "action": [
296
+ { "type": "keypress", "target": "tab", "key": "Enter" }
297
+ ],
298
+ "assertions": [
299
+ {
300
+ "target": "panel",
301
+ "assertion": "toBeVisible",
302
+ "failureMessage": "Panel should be visible after pressing Enter on trigger."
303
+ },
304
+ {
305
+ "target": "tab",
306
+ "assertion": "toHaveAttribute",
307
+ "attribute": "aria-selected",
308
+ "expectedValue": "true",
309
+ "failureMessage": "Tab element's should have aria-selected=true after pressing Enter."
310
+ },
311
+ {
312
+ "target": "tab",
313
+ "assertion": "toHaveAttribute",
314
+ "attribute": "tabindex",
315
+ "expectedValue": "0",
316
+ "failureMessage": "Tab element's should have tabindex='0' after pressing Enter."
317
+ }
318
+ ]
319
+ },
320
+ {
321
+ "description": "Pressing Space on tab element activates its associated tab panel.",
322
+ "action": [
323
+ { "type": "keypress", "target": "tab", "key": "Space" }
324
+ ],
325
+ "assertions": [
326
+ {
327
+ "target": "panel",
328
+ "assertion": "toBeVisible",
329
+ "failureMessage": "Panel should be visible after pressing Space on trigger."
330
+ },
331
+ {
332
+ "target": "tab",
333
+ "assertion": "toHaveAttribute",
334
+ "attribute": "aria-selected",
335
+ "expectedValue": "true",
336
+ "failureMessage": "Tab element's should have aria-selected=true after pressing Space."
337
+ },
338
+ {
339
+ "target": "tab",
340
+ "assertion": "toHaveAttribute",
341
+ "attribute": "tabindex",
342
+ "expectedValue": "0",
343
+ "failureMessage": "Tab element's should have tabindex='0' after pressing Enter."
344
+ }
345
+ ]
346
+ }
347
+ ]
348
+ }
@@ -37,6 +37,10 @@ var init_contract = __esm({
37
37
  accordion: {
38
38
  path: "./contracts/AccordionContract.json",
39
39
  component: "accordion"
40
+ },
41
+ tabs: {
42
+ path: "./contracts/TabsContract.json",
43
+ component: "tabs"
40
44
  }
41
45
  };
42
46
  }
@@ -156,7 +160,7 @@ ${"\u2500".repeat(60)}`);
156
160
  this.log(`\u{1F4A1} Optional Enhancements (${suggestions.length}):
157
161
  `);
158
162
  this.log(`These features are optional per APG guidelines but recommended`);
159
- this.log(`for improved user experience and keyboard navigation:
163
+ this.log(`for improved user experience and keyboard interaction:
160
164
  `);
161
165
  suggestions.forEach((test, index) => {
162
166
  this.log(`${index + 1}. ${test.description}`);
@@ -356,9 +360,9 @@ async function runContractTestsPlaywright(componentName, url) {
356
360
  }
357
361
  await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
358
362
  }
359
- const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
363
+ const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container || componentContract.selectors.tablist || componentContract.selectors.tab;
360
364
  if (!mainSelector) {
361
- throw new Error(`CRITICAL: No main selector (trigger, input, or container) found in contract for ${componentName}`);
365
+ throw new Error(`CRITICAL: No main selector (trigger, input, container, tablist, or tab) found in contract for ${componentName}`);
362
366
  }
363
367
  try {
364
368
  await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
@@ -420,28 +424,52 @@ async function runContractTestsPlaywright(componentName, url) {
420
424
  failures.push(`Target ${test.target} not found.`);
421
425
  continue;
422
426
  }
427
+ const isRedundantCheck = (selector, attrName, expectedVal) => {
428
+ const attrPattern = new RegExp(`\\[${attrName}(?:=["']?([^\\]"']+)["']?)?\\]`);
429
+ const match = selector.match(attrPattern);
430
+ if (!match) return false;
431
+ if (!expectedVal) return true;
432
+ const selectorValue = match[1];
433
+ if (selectorValue) {
434
+ const expectedValues = expectedVal.split(" | ");
435
+ return expectedValues.includes(selectorValue);
436
+ }
437
+ return false;
438
+ };
423
439
  if (!test.expectedValue) {
424
440
  const attributes = test.attribute.split(" | ");
425
441
  let hasAny = false;
442
+ let allRedundant = true;
426
443
  for (const attr of attributes) {
427
- const value = await target.getAttribute(attr.trim());
444
+ const attrTrimmed = attr.trim();
445
+ if (isRedundantCheck(targetSelector, attrTrimmed)) {
446
+ passes.push(`${attrTrimmed} on ${test.target} verified by selector (already present in: ${targetSelector}).`);
447
+ hasAny = true;
448
+ continue;
449
+ }
450
+ allRedundant = false;
451
+ const value = await target.getAttribute(attrTrimmed);
428
452
  if (value !== null) {
429
453
  hasAny = true;
430
454
  break;
431
455
  }
432
456
  }
433
- if (!hasAny) {
457
+ if (!hasAny && !allRedundant) {
434
458
  failures.push(test.failureMessage + ` None of the attributes "${test.attribute}" are present.`);
435
- } else {
459
+ } else if (!allRedundant && hasAny) {
436
460
  passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
437
461
  }
438
462
  } else {
439
- const attributeValue = await target.getAttribute(test.attribute);
440
- const expectedValues = test.expectedValue.split(" | ");
441
- if (!attributeValue || !expectedValues.includes(attributeValue)) {
442
- failures.push(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
463
+ if (isRedundantCheck(targetSelector, test.attribute, test.expectedValue)) {
464
+ passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
443
465
  } else {
444
- passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
466
+ const attributeValue = await target.getAttribute(test.attribute);
467
+ const expectedValues = test.expectedValue.split(" | ");
468
+ if (!attributeValue || !expectedValues.includes(attributeValue)) {
469
+ failures.push(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
470
+ } else {
471
+ passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
472
+ }
445
473
  }
446
474
  }
447
475
  }
@@ -550,6 +578,19 @@ This indicates a problem with the menu component's close functionality.`
550
578
  if (shouldSkipTest) {
551
579
  continue;
552
580
  }
581
+ if (componentContract.selectors.panel && componentContract.selectors.tab && componentContract.selectors.tablist) {
582
+ if (dynamicTest.isVertical !== void 0 && componentContract.selectors.tablist) {
583
+ const tablistSelector = componentContract.selectors.tablist;
584
+ const tablist = page.locator(tablistSelector).first();
585
+ const orientation = await tablist.getAttribute("aria-orientation");
586
+ const isVertical = orientation === "vertical";
587
+ if (dynamicTest.isVertical !== isVertical) {
588
+ const skipReason = dynamicTest.isVertical ? `Skipping vertical tabs test - component has horizontal orientation` : `Skipping horizontal tabs test - component has vertical orientation`;
589
+ reporter.reportTest(dynamicTest, "skip", skipReason);
590
+ continue;
591
+ }
592
+ }
593
+ }
553
594
  for (const act of action) {
554
595
  if (!page || page.isClosed()) {
555
596
  failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
@@ -1,4 +1,4 @@
1
- import { closeSharedBrowser, ContractReporter, contract_default } from './chunk-7RMRFSJL.js';
1
+ import { closeSharedBrowser, ContractReporter, contract_default } from './chunk-XLG3MIPQ.js';
2
2
  import { axe } from 'jest-axe';
3
3
  import fs from 'fs/promises';
4
4
 
@@ -106,7 +106,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
106
106
  const devServerUrl = await checkDevServer(url);
107
107
  if (devServerUrl) {
108
108
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
109
- const { runContractTestsPlaywright } = await import('./contractTestRunnerPlaywright-7BPRTIN4.js');
109
+ const { runContractTestsPlaywright } = await import('./contractTestRunnerPlaywright-N77NEY25.js');
110
110
  contract = await runContractTestsPlaywright(componentName, devServerUrl);
111
111
  } else {
112
112
  throw new Error(