aria-ease 6.11.0 → 6.12.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 (28) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-XERMSYEH.js → chunk-FZ7GMIJB.js} +0 -21
  3. package/dist/{chunk-NI3MQCAS.js → chunk-GJGUY643.js} +2 -2
  4. package/dist/cli.cjs +26 -63
  5. package/dist/cli.js +3 -3
  6. package/dist/{configLoader-DWHOHXHL.js → configLoader-Q7N5XV4P.js} +2 -2
  7. package/dist/{configLoader-UJZHQBYS.js → configLoader-REHK3S3Q.js} +1 -1
  8. package/dist/{contractTestRunnerPlaywright-QDXSK3FE.js → contractTestRunnerPlaywright-DIXP5DQ3.js} +5 -20
  9. package/dist/{contractTestRunnerPlaywright-WNWQYSXZ.js → contractTestRunnerPlaywright-EWAWQVHT.js} +5 -20
  10. package/dist/index.cjs +32 -66
  11. package/dist/index.d.cts +3 -0
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +23 -15
  14. package/dist/src/utils/test/{chunk-XERMSYEH.js → chunk-FZ7GMIJB.js} +1 -21
  15. package/dist/src/utils/test/{configLoader-SHJSRG2A.js → configLoader-NA7IBCS3.js} +2 -2
  16. package/dist/src/utils/test/{contractTestRunnerPlaywright-Z2AHXSNM.js → contractTestRunnerPlaywright-CIZOXYRW.js} +5 -19
  17. package/dist/src/utils/test/dsl/index.cjs +7 -4
  18. package/dist/src/utils/test/dsl/index.d.cts +3 -0
  19. package/dist/src/utils/test/dsl/index.d.ts +3 -0
  20. package/dist/src/utils/test/dsl/index.js +7 -4
  21. package/dist/src/utils/test/index.cjs +19 -55
  22. package/dist/src/utils/test/index.js +16 -10
  23. package/dist/{test-O3J4ZPQR.js → test-HBPCSYH5.js} +16 -11
  24. package/package.json +2 -3
  25. package/dist/src/utils/test/aria-contracts/accordion/accordion.contract.json +0 -290
  26. package/dist/src/utils/test/aria-contracts/combobox/combobox.listbox.contract.json +0 -463
  27. package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +0 -562
  28. package/dist/src/utils/test/aria-contracts/tabs/tabs.contract.json +0 -361
@@ -30,31 +30,6 @@ var __export = (target, all) => {
30
30
  __defProp(target, name, { get: all[name], enumerable: true });
31
31
  };
32
32
 
33
- // src/utils/test/contract/contract.json
34
- var contract_default;
35
- var init_contract = __esm({
36
- "src/utils/test/contract/contract.json"() {
37
- contract_default = {
38
- menu: {
39
- path: "./aria-contracts/menu/menu.contract.json",
40
- component: "menu"
41
- },
42
- "combobox.listbox": {
43
- path: "./aria-contracts/combobox/combobox.listbox.contract.json",
44
- component: "combobox.listbox"
45
- },
46
- accordion: {
47
- path: "./aria-contracts/accordion/accordion.contract.json",
48
- component: "accordion"
49
- },
50
- tabs: {
51
- path: "./aria-contracts/tabs/tabs.contract.json",
52
- component: "tabs"
53
- }
54
- };
55
- }
56
- });
57
-
58
33
  // src/utils/test/src/ContractReporter.ts
59
34
  var ContractReporter;
60
35
  var init_ContractReporter = __esm({
@@ -473,8 +448,8 @@ function validateConfig(config) {
473
448
  if (typeof comp.name !== "string") {
474
449
  errors.push(`test.components[${idx}].name must be a string`);
475
450
  }
476
- if (comp.path !== void 0 && typeof comp.path !== "string") {
477
- errors.push(`test.components[${idx}].path must be a string when provided`);
451
+ if (comp.contractPath !== void 0 && typeof comp.contractPath !== "string") {
452
+ errors.push(`test.components[${idx}].contractPath must be a string when provided`);
478
453
  }
479
454
  if (comp.strategyPath !== void 0 && typeof comp.strategyPath !== "string") {
480
455
  errors.push(`test.components[${idx}].strategyPath must be a string when provided`);
@@ -848,12 +823,6 @@ var init_StrategyRegistry = __esm({
848
823
  (m) => m.TabsComponentStrategy
849
824
  )
850
825
  );
851
- this.builtInStrategies.set(
852
- "combobox.listbox",
853
- () => Promise.resolve().then(() => (init_ComboboxComponentStrategy(), ComboboxComponentStrategy_exports)).then(
854
- (m) => m.ComboboxComponentStrategy
855
- )
856
- );
857
826
  }
858
827
  /**
859
828
  * Load a strategy - either from custom path or built-in registry
@@ -901,7 +870,6 @@ var init_StrategyRegistry = __esm({
901
870
  var ComponentDetector;
902
871
  var init_ComponentDetector = __esm({
903
872
  "src/utils/test/src/ComponentDetector.ts"() {
904
- init_contract();
905
873
  init_StrategyRegistry();
906
874
  ComponentDetector = class {
907
875
  static strategyRegistry = new StrategyRegistry();
@@ -922,11 +890,7 @@ var init_ComponentDetector = __esm({
922
890
  */
923
891
  static async detect(componentName, componentConfig, actionTimeoutMs = 400, assertionTimeoutMs = 400, configBaseDir) {
924
892
  const typedComponentConfig = this.isComponentConfig(componentConfig) ? componentConfig : void 0;
925
- let contractPath = typedComponentConfig?.path;
926
- if (!contractPath) {
927
- const contractTyped = contract_default;
928
- contractPath = contractTyped[componentName]?.path;
929
- }
893
+ const contractPath = typedComponentConfig?.contractPath;
930
894
  if (!contractPath) {
931
895
  throw new Error(`Contract path not found for component: ${componentName}`);
932
896
  }
@@ -959,7 +923,7 @@ var init_ComponentDetector = __esm({
959
923
  if (!strategyClass) {
960
924
  return null;
961
925
  }
962
- const mainSelector = selectors.trigger || selectors.input || selectors.tablist || selectors.container;
926
+ const mainSelector = selectors.main;
963
927
  if (componentName === "tabs") {
964
928
  return new strategyClass(mainSelector, selectors);
965
929
  }
@@ -1528,7 +1492,7 @@ __export(contractTestRunnerPlaywright_exports, {
1528
1492
  });
1529
1493
  async function runContractTestsPlaywright(componentName, url, strictness, config, configBaseDir) {
1530
1494
  const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
1531
- const isCustomContract = !!componentConfig?.path;
1495
+ const isCustomContract = !!componentConfig?.contractPath;
1532
1496
  const reporter = new ContractReporter(true, isCustomContract);
1533
1497
  const defaultTimeouts = {
1534
1498
  actionTimeoutMs: 400,
@@ -1568,11 +1532,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
1568
1532
  defaultTimeouts.componentReadyTimeoutMs
1569
1533
  );
1570
1534
  const strictnessMode = normalizeStrictness(strictness);
1571
- let contractPath = componentConfig?.path;
1572
- if (!contractPath) {
1573
- const contractTyped = contract_default;
1574
- contractPath = contractTyped[componentName]?.path;
1575
- }
1535
+ const contractPath = componentConfig?.contractPath;
1576
1536
  if (!contractPath) {
1577
1537
  throw new Error(`Contract path not found for component: ${componentName}`);
1578
1538
  }
@@ -2065,7 +2025,6 @@ This usually means:
2065
2025
  }
2066
2026
  var init_contractTestRunnerPlaywright = __esm({
2067
2027
  "src/utils/test/src/contractTestRunnerPlaywright.ts"() {
2068
- init_contract();
2069
2028
  init_playwrightTestHarness();
2070
2029
  init_ComponentDetector();
2071
2030
  init_ContractReporter();
@@ -2185,19 +2144,15 @@ var init_badgeHelper = __esm({
2185
2144
  });
2186
2145
 
2187
2146
  // src/utils/test/src/contractTestRunner.ts
2188
- init_contract();
2189
2147
  init_ContractReporter();
2190
2148
  init_strictness();
2191
- async function runContractTests(componentName, component, strictness) {
2149
+ async function runContractTests(contractPath, componentName, component, strictness) {
2192
2150
  const reporter = new ContractReporter(false);
2193
2151
  const strictnessMode = normalizeStrictness(strictness);
2194
- const contractTyped = contract_default;
2195
- const contractPath = contractTyped[componentName]?.path;
2196
2152
  if (!contractPath) {
2197
- throw new Error(`No contract found for component: ${componentName}`);
2153
+ throw new Error(`No contract path provided for component: ${componentName}`);
2198
2154
  }
2199
- const resolvedPath = new URL(contractPath, (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).pathname;
2200
- const contractData = await fs__default.default.readFile(resolvedPath, "utf-8");
2155
+ const contractData = await fs__default.default.readFile(contractPath, "utf-8");
2201
2156
  const componentContract = JSON.parse(contractData);
2202
2157
  const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
2203
2158
  reporter.start(componentName, totalTests);
@@ -2430,7 +2385,16 @@ Please start your dev server and try again.`
2430
2385
  }
2431
2386
  } else if (component) {
2432
2387
  console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
2433
- contract = await runContractTests(componentName, component, strictness);
2388
+ const contractPath = config.test?.components?.find((comp) => comp?.name === componentName)?.contractPath;
2389
+ if (!contractPath) {
2390
+ throw new Error(`\u274C No contract path found for component: ${componentName}`);
2391
+ }
2392
+ contract = await runContractTests(
2393
+ path3__default.default.resolve(configBaseDir, contractPath),
2394
+ componentName,
2395
+ component,
2396
+ strictness
2397
+ );
2434
2398
  } else {
2435
2399
  throw new Error("\u274C Either component or URL must be provided");
2436
2400
  }
@@ -1,18 +1,15 @@
1
- import { normalizeStrictness, closeSharedBrowser, ContractReporter, contract_default, normalizeLevel, resolveEnforcement } from './chunk-XERMSYEH.js';
1
+ import { normalizeStrictness, closeSharedBrowser, ContractReporter, normalizeLevel, resolveEnforcement } from './chunk-FZ7GMIJB.js';
2
2
  import { axe } from 'jest-axe';
3
3
  import fs from 'fs/promises';
4
4
  import path from 'path';
5
5
 
6
- async function runContractTests(componentName, component, strictness) {
6
+ async function runContractTests(contractPath, componentName, component, strictness) {
7
7
  const reporter = new ContractReporter(false);
8
8
  const strictnessMode = normalizeStrictness(strictness);
9
- const contractTyped = contract_default;
10
- const contractPath = contractTyped[componentName]?.path;
11
9
  if (!contractPath) {
12
- throw new Error(`No contract found for component: ${componentName}`);
10
+ throw new Error(`No contract path provided for component: ${componentName}`);
13
11
  }
14
- const resolvedPath = new URL(contractPath, import.meta.url).pathname;
15
- const contractData = await fs.readFile(resolvedPath, "utf-8");
12
+ const contractData = await fs.readFile(contractPath, "utf-8");
16
13
  const componentContract = JSON.parse(contractData);
17
14
  const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
18
15
  reporter.start(componentName, totalTests);
@@ -209,7 +206,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
209
206
  let configBaseDir = typeof process !== "undefined" ? process.cwd() : "";
210
207
  if (typeof process !== "undefined" && typeof process.cwd === "function") {
211
208
  try {
212
- const { loadConfig } = await import('./configLoader-SHJSRG2A.js');
209
+ const { loadConfig } = await import('./configLoader-NA7IBCS3.js');
213
210
  const result2 = await loadConfig(process.cwd());
214
211
  config = result2.config;
215
212
  if (result2.configPath) {
@@ -231,7 +228,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
231
228
  const devServerUrl = await checkDevServer(url);
232
229
  if (devServerUrl) {
233
230
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
234
- const { runContractTestsPlaywright } = await import('./contractTestRunnerPlaywright-Z2AHXSNM.js');
231
+ const { runContractTestsPlaywright } = await import('./contractTestRunnerPlaywright-CIZOXYRW.js');
235
232
  contract = await runContractTestsPlaywright(componentName, devServerUrl, strictness, config, configBaseDir);
236
233
  } else {
237
234
  throw new Error(
@@ -241,7 +238,16 @@ Please start your dev server and try again.`
241
238
  }
242
239
  } else if (component) {
243
240
  console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
244
- contract = await runContractTests(componentName, component, strictness);
241
+ const contractPath = config.test?.components?.find((comp) => comp?.name === componentName)?.contractPath;
242
+ if (!contractPath) {
243
+ throw new Error(`\u274C No contract path found for component: ${componentName}`);
244
+ }
245
+ contract = await runContractTests(
246
+ path.resolve(configBaseDir, contractPath),
247
+ componentName,
248
+ component,
249
+ strictness
250
+ );
245
251
  } else {
246
252
  throw new Error("\u274C Either component or URL must be provided");
247
253
  }
@@ -1,11 +1,10 @@
1
1
  import {
2
2
  ContractReporter,
3
3
  closeSharedBrowser,
4
- contract_default,
5
4
  normalizeLevel,
6
5
  normalizeStrictness,
7
6
  resolveEnforcement
8
- } from "./chunk-XERMSYEH.js";
7
+ } from "./chunk-FZ7GMIJB.js";
9
8
  import "./chunk-I2KLQ2HA.js";
10
9
 
11
10
  // src/utils/test/src/test.ts
@@ -13,16 +12,13 @@ import { axe } from "jest-axe";
13
12
 
14
13
  // src/utils/test/src/contractTestRunner.ts
15
14
  import fs from "fs/promises";
16
- async function runContractTests(componentName, component, strictness) {
15
+ async function runContractTests(contractPath, componentName, component, strictness) {
17
16
  const reporter = new ContractReporter(false);
18
17
  const strictnessMode = normalizeStrictness(strictness);
19
- const contractTyped = contract_default;
20
- const contractPath = contractTyped[componentName]?.path;
21
18
  if (!contractPath) {
22
- throw new Error(`No contract found for component: ${componentName}`);
19
+ throw new Error(`No contract path provided for component: ${componentName}`);
23
20
  }
24
- const resolvedPath = new URL(contractPath, import.meta.url).pathname;
25
- const contractData = await fs.readFile(resolvedPath, "utf-8");
21
+ const contractData = await fs.readFile(contractPath, "utf-8");
26
22
  const componentContract = JSON.parse(contractData);
27
23
  const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
28
24
  reporter.start(componentName, totalTests);
@@ -222,7 +218,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
222
218
  let configBaseDir = typeof process !== "undefined" ? process.cwd() : "";
223
219
  if (typeof process !== "undefined" && typeof process.cwd === "function") {
224
220
  try {
225
- const { loadConfig } = await import("./configLoader-UJZHQBYS.js");
221
+ const { loadConfig } = await import("./configLoader-REHK3S3Q.js");
226
222
  const result2 = await loadConfig(process.cwd());
227
223
  config = result2.config;
228
224
  if (result2.configPath) {
@@ -244,7 +240,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
244
240
  const devServerUrl = await checkDevServer(url);
245
241
  if (devServerUrl) {
246
242
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
247
- const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-QDXSK3FE.js");
243
+ const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-EWAWQVHT.js");
248
244
  contract = await runContractTestsPlaywright(componentName, devServerUrl, strictness, config, configBaseDir);
249
245
  } else {
250
246
  throw new Error(
@@ -254,7 +250,16 @@ Please start your dev server and try again.`
254
250
  }
255
251
  } else if (component) {
256
252
  console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
257
- contract = await runContractTests(componentName, component, strictness);
253
+ const contractPath = config.test?.components?.find((comp) => comp?.name === componentName)?.contractPath;
254
+ if (!contractPath) {
255
+ throw new Error(`\u274C No contract path found for component: ${componentName}`);
256
+ }
257
+ contract = await runContractTests(
258
+ path.resolve(configBaseDir, contractPath),
259
+ componentName,
260
+ component,
261
+ strictness
262
+ );
258
263
  } else {
259
264
  throw new Error("\u274C Either component or URL must be provided");
260
265
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aria-ease",
3
- "version": "6.11.0",
3
+ "version": "6.12.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",
@@ -22,8 +22,7 @@
22
22
  "build:test": "tsup ./src/utils/test/index.ts --format esm,cjs --dts --treeshake --external jest-axe --external @testing-library/react --external playwright --external @playwright/test --outDir dist/src/utils/test",
23
23
  "build:dsl": "tsup ./src/utils/test/dsl/index.ts --format esm,cjs --dts --treeshake --outDir dist/src/utils/test/dsl",
24
24
  "build:cli": "tsup ./src/utils/cli/cli.ts --format esm,cjs --dts --outDir dist --external commander --external chalk --external jest-axe --external @testing-library/react --external @axe-core/playwright --external playwright",
25
- "build:contracts": "mkdir -p ./dist/src/utils/test && cp -r ./src/utils/test/contract/aria-contracts ./dist/src/utils/test/",
26
- "build": "npm run clean && npm run build:core && npm run build:modules && npm run build:test && npm run build:dsl && npm run build:cli && npm run build:contracts"
25
+ "build": "npm run clean && npm run build:core && npm run build:modules && npm run build:test && npm run build:dsl && npm run build:cli"
27
26
  },
28
27
  "repository": {
29
28
  "type": "git",
@@ -1,290 +0,0 @@
1
- {
2
- "meta": {
3
- "id": "aria-ease.contract.accordion",
4
- "version": "1.0.0",
5
- "created": "09-02-2026",
6
- "lastUpdated": "19-03-2026",
7
- "description": "ARIA Accordion interaction contract. Validates the ARIA and interaction contract for a custom accordion component following the ARIA Authoring Practices Guide accordion with show/hide pattern.",
8
- "source": {
9
- "apg": "https://www.w3.org/WAI/ARIA/apg/patterns/accordion/",
10
- "wcag": ["2.2 AA"]
11
- },
12
- "W3CName": "Accordion"
13
- },
14
-
15
- "selectors": {
16
- "trigger": "[data-accordion] [aria-controls][aria-expanded], [aria-controls][aria-expanded]",
17
- "focusable": "[data-accordion] [aria-controls][aria-expanded], [aria-controls][aria-expanded]",
18
- "relative": "[data-accordion] [aria-controls][aria-expanded], [aria-controls][aria-expanded]",
19
- "panel": "[role='region'][aria-labelledby], [role='region'][aria-labelledby]"
20
- },
21
-
22
- "observables": {
23
- "observable": "focus | visible | attribute | role",
24
- "target": "trigger | relative | panel | focusable",
25
- "relative": "first | last | next | previous"
26
- },
27
-
28
- "relationships": [
29
- {
30
- "type": "aria-reference",
31
- "from": "trigger",
32
- "attribute": "aria-controls",
33
- "to": "panel"
34
- },
35
- {
36
- "type": "aria-reference",
37
- "from": "panel",
38
- "attribute": "aria-labelledby",
39
- "to": "trigger"
40
- }
41
- ],
42
-
43
- "static": [
44
- {
45
- "assertions": [
46
- {
47
- "target": "trigger",
48
- "assertion": "toHaveAttribute",
49
- "attribute": "aria-expanded",
50
- "expectedValue": "true | false",
51
- "failureMessage": "Accordion trigger button doesn't conform to the ARIA Accordion Button pattern as specified in APG 1.2. Accordion trigger button should have 'aria-expanded=true | false' attribute. This helps assistive technology to keep track of the open or close state of the accordion panel div that the button controls."
52
- },
53
- {
54
- "target": "trigger",
55
- "assertion": "toHaveAttribute",
56
- "attribute": "aria-controls",
57
- "failureMessage": "Accordion trigger button doesn't conform to the ARIA Accordion Button pattern as specified in APG 1.2. Accordion trigger button should have 'aria-controls' attribute that points to the id of the accordion panel div it controls with 'role=region'. This helps assistive technology to identify the accordion panel div that the button controls."
58
- },
59
- {
60
- "target": "panel",
61
- "assertion": "toHaveAttribute",
62
- "attribute": "aria-labelledby | aria-label",
63
- "failureMessage": "Accordion panel doesn't conform to the ARIA Accordion pattern as specified in APG 1.2. Accordion panel should have 'aria-labelledby' attribute that points to the id of the trigger button that controls it."
64
- }
65
- ]
66
- }
67
- ],
68
-
69
- "dynamic": [
70
- {
71
- "description": "Clicking the trigger expands a collapsed panel.",
72
- "action": [
73
- { "type": "click", "target": "trigger" }
74
- ],
75
- "assertions": [
76
- {
77
- "target": "panel",
78
- "assertion": "toBeVisible",
79
- "failureMessage": "Panel should be visible after clicking the trigger."
80
- },
81
- {
82
- "target": "trigger",
83
- "assertion": "toHaveAttribute",
84
- "attribute": "aria-expanded",
85
- "expectedValue": "true",
86
- "failureMessage": "Trigger's aria-expanded attribute should be true after clicking to expand."
87
- }
88
- ]
89
- },
90
- {
91
- "description": "Clicking the trigger again collapses an expanded panel.",
92
- "action": [
93
- { "type": "click", "target": "trigger" },
94
- { "type": "click", "target": "trigger" }
95
- ],
96
- "assertions": [
97
- {
98
- "target": "panel",
99
- "assertion": "notToBeVisible",
100
- "failureMessage": "Panel should not be visible after clicking the trigger twice."
101
- },
102
- {
103
- "target": "trigger",
104
- "assertion": "toHaveAttribute",
105
- "attribute": "aria-expanded",
106
- "expectedValue": "false",
107
- "failureMessage": "Trigger's aria-expanded attribute should be false after collapsing."
108
- }
109
- ]
110
- },
111
- {
112
- "description": "Pressing Enter on trigger expands a collapsed panel.",
113
- "action": [
114
- { "type": "keypress", "target": "trigger", "key": "Enter" }
115
- ],
116
- "assertions": [
117
- {
118
- "target": "panel",
119
- "assertion": "toBeVisible",
120
- "failureMessage": "Panel should be visible after pressing Enter on trigger."
121
- },
122
- {
123
- "target": "trigger",
124
- "assertion": "toHaveAttribute",
125
- "attribute": "aria-expanded",
126
- "expectedValue": "true",
127
- "failureMessage": "Trigger's should have aria-expanded=true after pressing Enter."
128
- }
129
- ]
130
- },
131
- {
132
- "description": "Pressing Space on trigger expands a collapsed panel.",
133
- "action": [
134
- { "type": "keypress", "target": "trigger", "key": "Space" }
135
- ],
136
- "assertions": [
137
- {
138
- "target": "panel",
139
- "assertion": "toBeVisible",
140
- "failureMessage": "Panel should be visible after pressing Space on trigger."
141
- },
142
- {
143
- "target": "trigger",
144
- "assertion": "toHaveAttribute",
145
- "attribute": "aria-expanded",
146
- "expectedValue": "true",
147
- "failureMessage": "Trigger's should have aria-expanded=true after pressing Space."
148
- }
149
- ]
150
- },
151
- {
152
- "description": "Pressing Enter on trigger collapses an expanded panel.",
153
- "action": [
154
- { "type": "keypress", "target": "trigger", "key": "Enter" },
155
- { "type": "keypress", "target": "trigger", "key": "Enter" }
156
- ],
157
- "assertions": [
158
- {
159
- "target": "panel",
160
- "assertion": "notToBeVisible",
161
- "failureMessage": "Panel should not be visible after pressing Enter on expanded trigger."
162
- },
163
- {
164
- "target": "trigger",
165
- "assertion": "toHaveAttribute",
166
- "attribute": "aria-expanded",
167
- "expectedValue": "false",
168
- "failureMessage": "Trigger's aria-expanded should be false after collapsing with Enter."
169
- }
170
- ]
171
- },
172
- {
173
- "description": "Pressing Space on trigger collapses an expanded panel.",
174
- "action": [
175
- { "type": "keypress", "target": "trigger", "key": "Space" },
176
- { "type": "keypress", "target": "trigger", "key": "Space" }
177
- ],
178
- "assertions": [
179
- {
180
- "target": "panel",
181
- "assertion": "notToBeVisible",
182
- "failureMessage": "Panel should not be visible after pressing Space on expanded trigger."
183
- },
184
- {
185
- "target": "trigger",
186
- "assertion": "toHaveAttribute",
187
- "attribute": "aria-expanded",
188
- "expectedValue": "false",
189
- "failureMessage": "Trigger's aria-expanded should be false after collapsing with Space."
190
- }
191
- ]
192
- },
193
- {
194
- "description": "Down Arrow moves focus to next accordion trigger.",
195
- "isMultiple": true,
196
- "level": "optional",
197
- "action": [
198
- { "type": "keypress", "target": "focusable", "key": "ArrowDown" }
199
- ],
200
- "assertions": [
201
- {
202
- "target": "relative",
203
- "assertion": "toHaveFocus",
204
- "expectedValue": "second",
205
- "failureMessage": "Focus should move to the next accordion trigger after pressing Down Arrow."
206
- }
207
- ]
208
- },
209
- {
210
- "description": "Up Arrow moves focus to previous accordion trigger.",
211
- "isMultiple": true,
212
- "level": "optional",
213
- "action": [
214
- { "type": "keypress", "target": "focusable", "key": "ArrowUp" }
215
- ],
216
- "assertions": [
217
- {
218
- "target": "relative",
219
- "assertion": "toHaveFocus",
220
- "expectedValue": "first",
221
- "failureMessage": "Focus should move to the previous accordion trigger after pressing Up Arrow."
222
- }
223
- ]
224
- },
225
- {
226
- "description": "Home moves focus to first accordion trigger.",
227
- "isMultiple": true,
228
- "level": "optional",
229
- "action": [
230
- { "type": "keypress", "target": "focusable", "key": "Home" }
231
- ],
232
- "assertions": [
233
- {
234
- "target": "relative",
235
- "assertion": "toHaveFocus",
236
- "expectedValue": "first",
237
- "failureMessage": "Focus should move to the first accordion trigger after pressing Home."
238
- }
239
- ]
240
- },
241
- {
242
- "description": "End moves focus to last accordion trigger.",
243
- "isMultiple": true,
244
- "level": "optional",
245
- "action": [
246
- { "type": "keypress", "target": "focusable", "key": "End" }
247
- ],
248
- "assertions": [
249
- {
250
- "target": "relative",
251
- "assertion": "toHaveFocus",
252
- "expectedValue": "last",
253
- "failureMessage": "Focus should move to the last accordion trigger after pressing End."
254
- }
255
- ]
256
- },
257
- {
258
- "description": "Down Arrow wraps focus from last trigger to first trigger.",
259
- "isMultiple": true,
260
- "level": "optional",
261
- "action": [
262
- { "type": "keypress", "target": "focusable", "key": "ArrowDown" }
263
- ],
264
- "assertions": [
265
- {
266
- "target": "relative",
267
- "assertion": "toHaveFocus",
268
- "expectedValue": "first",
269
- "failureMessage": "Focus should wrap from last to first accordion trigger after pressing Down Arrow."
270
- }
271
- ]
272
- },
273
- {
274
- "description": "Up Arrow wraps focus from first trigger to last trigger.",
275
- "isMultiple": true,
276
- "level": "optional",
277
- "action": [
278
- { "type": "keypress", "target": "focusable", "key": "ArrowUp" }
279
- ],
280
- "assertions": [
281
- {
282
- "target": "relative",
283
- "assertion": "toHaveFocus",
284
- "expectedValue": "last",
285
- "failureMessage": "Focus should wrap from first to last accordion trigger after pressing Up Arrow."
286
- }
287
- ]
288
- }
289
- ]
290
- }