aria-ease 6.5.1 → 6.6.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.
@@ -197,11 +197,6 @@ ${"\u2500".repeat(60)}`);
197
197
  ${"\u2550".repeat(60)}`);
198
198
  this.log(`\u{1F4CA} Summary
199
199
  `);
200
- const staticIcon = this.staticFailures === 0 ? "\u2705" : "\u274C";
201
- const staticStatus = this.staticFailures === 0 ? "PASS" : "FAIL";
202
- this.log(`${staticIcon} Static ARIA Tests: ${staticStatus}`);
203
- this.log(` ${this.staticPasses}/${this.staticPasses + this.staticFailures} required attributes present`);
204
- this.log("");
205
200
  if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
206
201
  this.log(`\u2705 All ${totalRun} tests passed!`);
207
202
  this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
@@ -1,4 +1,4 @@
1
- import { ContractReporter, contract_default, createTestPage } from './chunk-AUJAN4RK.js';
1
+ import { ContractReporter, contract_default, createTestPage } from './chunk-LKN5PRYD.js';
2
2
  import { readFileSync } from 'fs';
3
3
  import { expect } from '@playwright/test';
4
4
 
@@ -143,29 +143,20 @@ This indicates a problem with the menu component's close functionality.`
143
143
  }
144
144
  }
145
145
  async shouldSkipTest(test, page) {
146
- for (const act of test.action) {
147
- if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
148
- const submenuSelector = this.selectors[act.target];
149
- if (submenuSelector) {
150
- const submenuCount = await page.locator(submenuSelector).count();
151
- if (submenuCount === 0) {
152
- return true;
153
- }
154
- }
155
- }
146
+ const requiresSubmenu = test.action.some(
147
+ (act) => act.target === "submenu" || act.target === "submenuTrigger" || act.target === "submenuItems"
148
+ ) || test.assertions.some(
149
+ (assertion) => assertion.target === "submenu" || assertion.target === "submenuTrigger" || assertion.target === "submenuItems"
150
+ );
151
+ if (!requiresSubmenu) {
152
+ return false;
156
153
  }
157
- for (const assertion of test.assertions) {
158
- if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
159
- const submenuSelector = this.selectors[assertion.target];
160
- if (submenuSelector) {
161
- const submenuCount = await page.locator(submenuSelector).count();
162
- if (submenuCount === 0) {
163
- return true;
164
- }
165
- }
166
- }
154
+ const submenuTriggerSelector = this.selectors.submenuTrigger;
155
+ if (!submenuTriggerSelector) {
156
+ return true;
167
157
  }
168
- return false;
158
+ const submenuTriggerCount = await page.locator(submenuTriggerSelector).count();
159
+ return submenuTriggerCount === 0;
169
160
  }
170
161
  getMainSelector() {
171
162
  return this.mainSelector;
@@ -274,6 +265,9 @@ var ActionExecutor = class {
274
265
  this.selectors = selectors;
275
266
  this.timeoutMs = timeoutMs;
276
267
  }
268
+ isOptionalMenuTarget(target) {
269
+ return ["submenu", "submenuTrigger", "submenuItems"].includes(target);
270
+ }
277
271
  /**
278
272
  * Check if error is due to browser/page being closed
279
273
  */
@@ -394,7 +388,7 @@ var ActionExecutor = class {
394
388
  } else if (keyValue.includes(" ")) {
395
389
  keyValue = keyValue.replace(/ /g, "");
396
390
  }
397
- if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
391
+ if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape", "Home", "End", "Tab", "Shift+Tab"].includes(keyValue)) {
398
392
  await this.page.keyboard.press(keyValue);
399
393
  return { success: true };
400
394
  }
@@ -405,9 +399,10 @@ var ActionExecutor = class {
405
399
  const locator = this.page.locator(selector).first();
406
400
  const elementCount = await locator.count();
407
401
  if (elementCount === 0) {
402
+ const optionalMenuTarget = this.isOptionalMenuTarget(target);
408
403
  return {
409
404
  success: false,
410
- error: `${target} element not found (optional submenu test)`,
405
+ error: optionalMenuTarget ? `${target} element not found (optional submenu test)` : `${target} element not found.`,
411
406
  shouldBreak: true
412
407
  // Signal to skip this test
413
408
  };
@@ -764,23 +759,35 @@ This usually means:
764
759
  }).catch(() => {
765
760
  });
766
761
  }
767
- const failuresBeforeStatic = failures.length;
762
+ const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
763
+ let staticFailed = 0;
768
764
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
769
765
  for (const test of componentContract.static[0]?.assertions || []) {
770
766
  if (test.target === "relative") continue;
771
767
  const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
768
+ if (componentName === "menu" && test.target === "submenuTrigger" && !hasSubmenuCapability) {
769
+ passes.push(`Skipping submenu static assertion for ${test.target}: no submenu capability detected in rendered component.`);
770
+ reporter.reportStaticTest(staticDescription, true);
771
+ continue;
772
+ }
772
773
  const targetSelector = componentContract.selectors[test.target];
773
774
  if (!targetSelector) {
774
775
  const failure = `Selector for target ${test.target} not found.`;
775
776
  failures.push(failure);
777
+ staticFailed += 1;
776
778
  reporter.reportStaticTest(staticDescription, false, failure);
777
779
  continue;
778
780
  }
779
781
  const target = page.locator(targetSelector).first();
780
782
  const exists = await target.count() > 0;
781
783
  if (!exists) {
784
+ if (test.isOptional === true) {
785
+ reporter.reportStaticTest(staticDescription, true);
786
+ continue;
787
+ }
782
788
  const failure = `Target ${test.target} not found.`;
783
789
  failures.push(failure);
790
+ staticFailed += 1;
784
791
  reporter.reportStaticTest(staticDescription, false, failure);
785
792
  continue;
786
793
  }
@@ -817,6 +824,7 @@ This usually means:
817
824
  if (!hasAny && !allRedundant) {
818
825
  const failure = test.failureMessage + ` None of the attributes "${test.attribute}" are present.`;
819
826
  failures.push(failure);
827
+ staticFailed += 1;
820
828
  reporter.reportStaticTest(staticDescription, false, failure);
821
829
  } else if (!allRedundant && hasAny) {
822
830
  passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
@@ -842,6 +850,7 @@ This usually means:
842
850
  reporter.reportStaticTest(staticDescription, true);
843
851
  } else if (!result.success && result.failMessage) {
844
852
  failures.push(result.failMessage);
853
+ staticFailed += 1;
845
854
  reporter.reportStaticTest(staticDescription, false, result.failMessage);
846
855
  }
847
856
  }
@@ -871,9 +880,12 @@ This usually means:
871
880
  }
872
881
  const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
873
882
  const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
883
+ let shouldSkipCurrentTest = false;
884
+ let shouldAbortCurrentTest = false;
874
885
  for (const act of action) {
875
886
  if (!page || page.isClosed()) {
876
887
  failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
888
+ shouldAbortCurrentTest = true;
877
889
  break;
878
890
  }
879
891
  let result;
@@ -891,18 +903,29 @@ This usually means:
891
903
  continue;
892
904
  }
893
905
  if (!result.success) {
894
- if (result.error) {
895
- failures.push(result.error);
896
- }
897
906
  if (result.shouldBreak) {
898
907
  if (result.error?.includes("optional submenu test")) {
899
908
  reporter.reportTest(dynamicTest, "skip", result.error);
909
+ shouldSkipCurrentTest = true;
910
+ } else if (result.error) {
911
+ failures.push(result.error);
912
+ shouldAbortCurrentTest = true;
900
913
  }
901
914
  break;
902
915
  }
916
+ if (result.error) {
917
+ failures.push(result.error);
918
+ }
903
919
  continue;
904
920
  }
905
921
  }
922
+ if (shouldSkipCurrentTest) {
923
+ continue;
924
+ }
925
+ if (shouldAbortCurrentTest) {
926
+ reporter.reportTest(dynamicTest, "fail", failures[failures.length - 1]);
927
+ continue;
928
+ }
906
929
  for (const assertion of assertions) {
907
930
  const result = await assertionRunner.validate(assertion, dynamicTest.description);
908
931
  if (result.success && result.passMessage) {
@@ -922,7 +945,6 @@ This usually means:
922
945
  }
923
946
  }
924
947
  const staticTotal = componentContract.static[0].assertions.length;
925
- const staticFailed = failures.length - failuresBeforeStatic;
926
948
  const staticPassed = Math.max(0, staticTotal - staticFailed);
927
949
  reporter.reportStatic(staticPassed, staticFailed);
928
950
  reporter.summary(failures);
@@ -234,11 +234,6 @@ ${"\u2500".repeat(60)}`);
234
234
  ${"\u2550".repeat(60)}`);
235
235
  this.log(`\u{1F4CA} Summary
236
236
  `);
237
- const staticIcon = this.staticFailures === 0 ? "\u2705" : "\u274C";
238
- const staticStatus = this.staticFailures === 0 ? "PASS" : "FAIL";
239
- this.log(`${staticIcon} Static ARIA Tests: ${staticStatus}`);
240
- this.log(` ${this.staticPasses}/${this.staticPasses + this.staticFailures} required attributes present`);
241
- this.log("");
242
237
  if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
243
238
  this.log(`\u2705 All ${totalRun} tests passed!`);
244
239
  this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
@@ -495,29 +490,20 @@ This indicates a problem with the menu component's close functionality.`
495
490
  }
496
491
  }
497
492
  async shouldSkipTest(test, page) {
498
- for (const act of test.action) {
499
- if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
500
- const submenuSelector = this.selectors[act.target];
501
- if (submenuSelector) {
502
- const submenuCount = await page.locator(submenuSelector).count();
503
- if (submenuCount === 0) {
504
- return true;
505
- }
506
- }
507
- }
493
+ const requiresSubmenu = test.action.some(
494
+ (act) => act.target === "submenu" || act.target === "submenuTrigger" || act.target === "submenuItems"
495
+ ) || test.assertions.some(
496
+ (assertion) => assertion.target === "submenu" || assertion.target === "submenuTrigger" || assertion.target === "submenuItems"
497
+ );
498
+ if (!requiresSubmenu) {
499
+ return false;
508
500
  }
509
- for (const assertion of test.assertions) {
510
- if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
511
- const submenuSelector = this.selectors[assertion.target];
512
- if (submenuSelector) {
513
- const submenuCount = await page.locator(submenuSelector).count();
514
- if (submenuCount === 0) {
515
- return true;
516
- }
517
- }
518
- }
501
+ const submenuTriggerSelector = this.selectors.submenuTrigger;
502
+ if (!submenuTriggerSelector) {
503
+ return true;
519
504
  }
520
- return false;
505
+ const submenuTriggerCount = await page.locator(submenuTriggerSelector).count();
506
+ return submenuTriggerCount === 0;
521
507
  }
522
508
  getMainSelector() {
523
509
  return this.mainSelector;
@@ -652,6 +638,9 @@ var init_ActionExecutor = __esm({
652
638
  this.selectors = selectors;
653
639
  this.timeoutMs = timeoutMs;
654
640
  }
641
+ isOptionalMenuTarget(target) {
642
+ return ["submenu", "submenuTrigger", "submenuItems"].includes(target);
643
+ }
655
644
  /**
656
645
  * Check if error is due to browser/page being closed
657
646
  */
@@ -772,7 +761,7 @@ var init_ActionExecutor = __esm({
772
761
  } else if (keyValue.includes(" ")) {
773
762
  keyValue = keyValue.replace(/ /g, "");
774
763
  }
775
- if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
764
+ if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape", "Home", "End", "Tab", "Shift+Tab"].includes(keyValue)) {
776
765
  await this.page.keyboard.press(keyValue);
777
766
  return { success: true };
778
767
  }
@@ -783,9 +772,10 @@ var init_ActionExecutor = __esm({
783
772
  const locator = this.page.locator(selector).first();
784
773
  const elementCount = await locator.count();
785
774
  if (elementCount === 0) {
775
+ const optionalMenuTarget = this.isOptionalMenuTarget(target);
786
776
  return {
787
777
  success: false,
788
- error: `${target} element not found (optional submenu test)`,
778
+ error: optionalMenuTarget ? `${target} element not found (optional submenu test)` : `${target} element not found.`,
789
779
  shouldBreak: true
790
780
  // Signal to skip this test
791
781
  };
@@ -1154,23 +1144,35 @@ This usually means:
1154
1144
  }).catch(() => {
1155
1145
  });
1156
1146
  }
1157
- const failuresBeforeStatic = failures.length;
1147
+ const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
1148
+ let staticFailed = 0;
1158
1149
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
1159
1150
  for (const test of componentContract.static[0]?.assertions || []) {
1160
1151
  if (test.target === "relative") continue;
1161
1152
  const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
1153
+ if (componentName === "menu" && test.target === "submenuTrigger" && !hasSubmenuCapability) {
1154
+ passes.push(`Skipping submenu static assertion for ${test.target}: no submenu capability detected in rendered component.`);
1155
+ reporter.reportStaticTest(staticDescription, true);
1156
+ continue;
1157
+ }
1162
1158
  const targetSelector = componentContract.selectors[test.target];
1163
1159
  if (!targetSelector) {
1164
1160
  const failure = `Selector for target ${test.target} not found.`;
1165
1161
  failures.push(failure);
1162
+ staticFailed += 1;
1166
1163
  reporter.reportStaticTest(staticDescription, false, failure);
1167
1164
  continue;
1168
1165
  }
1169
1166
  const target = page.locator(targetSelector).first();
1170
1167
  const exists = await target.count() > 0;
1171
1168
  if (!exists) {
1169
+ if (test.isOptional === true) {
1170
+ reporter.reportStaticTest(staticDescription, true);
1171
+ continue;
1172
+ }
1172
1173
  const failure = `Target ${test.target} not found.`;
1173
1174
  failures.push(failure);
1175
+ staticFailed += 1;
1174
1176
  reporter.reportStaticTest(staticDescription, false, failure);
1175
1177
  continue;
1176
1178
  }
@@ -1207,6 +1209,7 @@ This usually means:
1207
1209
  if (!hasAny && !allRedundant) {
1208
1210
  const failure = test.failureMessage + ` None of the attributes "${test.attribute}" are present.`;
1209
1211
  failures.push(failure);
1212
+ staticFailed += 1;
1210
1213
  reporter.reportStaticTest(staticDescription, false, failure);
1211
1214
  } else if (!allRedundant && hasAny) {
1212
1215
  passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
@@ -1232,6 +1235,7 @@ This usually means:
1232
1235
  reporter.reportStaticTest(staticDescription, true);
1233
1236
  } else if (!result.success && result.failMessage) {
1234
1237
  failures.push(result.failMessage);
1238
+ staticFailed += 1;
1235
1239
  reporter.reportStaticTest(staticDescription, false, result.failMessage);
1236
1240
  }
1237
1241
  }
@@ -1261,9 +1265,12 @@ This usually means:
1261
1265
  }
1262
1266
  const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
1263
1267
  const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
1268
+ let shouldSkipCurrentTest = false;
1269
+ let shouldAbortCurrentTest = false;
1264
1270
  for (const act of action) {
1265
1271
  if (!page || page.isClosed()) {
1266
1272
  failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
1273
+ shouldAbortCurrentTest = true;
1267
1274
  break;
1268
1275
  }
1269
1276
  let result;
@@ -1281,18 +1288,29 @@ This usually means:
1281
1288
  continue;
1282
1289
  }
1283
1290
  if (!result.success) {
1284
- if (result.error) {
1285
- failures.push(result.error);
1286
- }
1287
1291
  if (result.shouldBreak) {
1288
1292
  if (result.error?.includes("optional submenu test")) {
1289
1293
  reporter.reportTest(dynamicTest, "skip", result.error);
1294
+ shouldSkipCurrentTest = true;
1295
+ } else if (result.error) {
1296
+ failures.push(result.error);
1297
+ shouldAbortCurrentTest = true;
1290
1298
  }
1291
1299
  break;
1292
1300
  }
1301
+ if (result.error) {
1302
+ failures.push(result.error);
1303
+ }
1293
1304
  continue;
1294
1305
  }
1295
1306
  }
1307
+ if (shouldSkipCurrentTest) {
1308
+ continue;
1309
+ }
1310
+ if (shouldAbortCurrentTest) {
1311
+ reporter.reportTest(dynamicTest, "fail", failures[failures.length - 1]);
1312
+ continue;
1313
+ }
1296
1314
  for (const assertion of assertions) {
1297
1315
  const result = await assertionRunner.validate(assertion, dynamicTest.description);
1298
1316
  if (result.success && result.passMessage) {
@@ -1312,7 +1330,6 @@ This usually means:
1312
1330
  }
1313
1331
  }
1314
1332
  const staticTotal = componentContract.static[0].assertions.length;
1315
- const staticFailed = failures.length - failuresBeforeStatic;
1316
1333
  const staticPassed = Math.max(0, staticTotal - staticFailed);
1317
1334
  reporter.reportStatic(staticPassed, staticFailed);
1318
1335
  reporter.summary(failures);
@@ -1,4 +1,4 @@
1
- import { closeSharedBrowser, ContractReporter, contract_default } from './chunk-AUJAN4RK.js';
1
+ import { closeSharedBrowser, ContractReporter, contract_default } from './chunk-LKN5PRYD.js';
2
2
  import { axe } from 'jest-axe';
3
3
  import fs from 'fs/promises';
4
4
 
@@ -108,7 +108,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
108
108
  const devServerUrl = await checkDevServer(url);
109
109
  if (devServerUrl) {
110
110
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
111
- const { runContractTestsPlaywright } = await import('./contractTestRunnerPlaywright-HL73FADJ.js');
111
+ const { runContractTestsPlaywright } = await import('./contractTestRunnerPlaywright-RGKMGXND.js');
112
112
  contract = await runContractTestsPlaywright(componentName, devServerUrl);
113
113
  } else {
114
114
  throw new Error(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aria-ease",
3
- "version": "6.5.1",
3
+ "version": "6.6.0",
4
4
  "description": "Accessibility infrastructure for the entire frontend engineering lifecycle. Build accessible patterns, run automated audits, verify component interactions, and gate deployments — all in one system.",
5
5
  "main": "dist/index.cjs",
6
6
  "type": "module",