aria-ease 6.9.0 → 6.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -487,14 +487,14 @@ function validateConfig(config) {
487
487
  if (!Array.isArray(cfg.contracts)) {
488
488
  errors.push("contracts must be an array");
489
489
  } else {
490
- cfg.contracts.forEach((contract2, idx) => {
491
- if (typeof contract2 !== "object" || contract2 === null) {
490
+ cfg.contracts.forEach((contract, idx) => {
491
+ if (typeof contract !== "object" || contract === null) {
492
492
  errors.push(`contracts[${idx}] must be an object`);
493
493
  } else {
494
- if (typeof contract2.src !== "string") {
494
+ if (typeof contract.src !== "string") {
495
495
  errors.push(`contracts[${idx}].src is required and must be a string`);
496
496
  }
497
- if (contract2.out !== void 0 && typeof contract2.out !== "string") {
497
+ if (contract.out !== void 0 && typeof contract.out !== "string") {
498
498
  errors.push(`contracts[${idx}].out must be a string`);
499
499
  }
500
500
  }
@@ -2060,7 +2060,7 @@ var init_badgeHelper = __esm({
2060
2060
  var index_exports = {};
2061
2061
  __export(index_exports, {
2062
2062
  cleanupTests: () => cleanupTests,
2063
- contract: () => contract,
2063
+ createContract: () => createContract,
2064
2064
  makeAccordionAccessible: () => makeAccordionAccessible,
2065
2065
  makeBlockAccessible: () => makeBlockAccessible,
2066
2066
  makeCheckboxAccessible: () => makeCheckboxAccessible,
@@ -3870,7 +3870,7 @@ var ContractBuilder = class {
3870
3870
  };
3871
3871
  }
3872
3872
  };
3873
- function contract(componentName, define) {
3873
+ function createContract(componentName, define) {
3874
3874
  const builder = new ContractBuilder(componentName);
3875
3875
  define(builder);
3876
3876
  return new FluentContract(builder.build());
@@ -4112,14 +4112,14 @@ Error: ${error instanceof Error ? error.message : String(error)}`
4112
4112
  }
4113
4113
  }
4114
4114
  }
4115
- let contract2;
4115
+ let contract;
4116
4116
  try {
4117
4117
  if (url) {
4118
4118
  const devServerUrl = await checkDevServer(url);
4119
4119
  if (devServerUrl) {
4120
4120
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
4121
4121
  const { runContractTestsPlaywright: runContractTestsPlaywright2 } = await Promise.resolve().then(() => (init_contractTestRunnerPlaywright(), contractTestRunnerPlaywright_exports));
4122
- contract2 = await runContractTestsPlaywright2(componentName, devServerUrl, strictness, config, configBaseDir);
4122
+ contract = await runContractTestsPlaywright2(componentName, devServerUrl, strictness, config, configBaseDir);
4123
4123
  } else {
4124
4124
  throw new Error(
4125
4125
  `\u274C Dev server not running at ${url}
@@ -4128,7 +4128,7 @@ Please start your dev server and try again.`
4128
4128
  }
4129
4129
  } else if (component) {
4130
4130
  console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
4131
- contract2 = await runContractTests(componentName, component, strictness);
4131
+ contract = await runContractTests(componentName, component, strictness);
4132
4132
  } else {
4133
4133
  throw new Error("\u274C Either component or URL must be provided");
4134
4134
  }
@@ -4141,13 +4141,13 @@ Please start your dev server and try again.`
4141
4141
  const result = {
4142
4142
  violations: results.violations,
4143
4143
  raw: results,
4144
- contract: contract2
4144
+ contract
4145
4145
  };
4146
- if (contract2.failures.length > 0 && url === "Playwright") {
4146
+ if (contract.failures.length > 0 && url === "Playwright") {
4147
4147
  throw new Error(
4148
4148
  `
4149
- \u274C ${contract2.failures.length} accessibility contract test${contract2.failures.length > 1 ? "s" : ""} failed (Playwright mode)
4150
- \u2705 ${contract2.passes.length} test${contract2.passes.length > 1 ? "s" : ""} passed
4149
+ \u274C ${contract.failures.length} accessibility contract test${contract.failures.length > 1 ? "s" : ""} failed (Playwright mode)
4150
+ \u2705 ${contract.passes.length} test${contract.passes.length > 1 ? "s" : ""} passed
4151
4151
 
4152
4152
  \u{1F4CB} Review the detailed test report above for specific failures.
4153
4153
  \u{1F4A1} Contract tests validate ARIA attributes and keyboard interactions per W3C APG guidelines.`
@@ -4223,7 +4223,7 @@ async function cleanupTests() {
4223
4223
  // Annotate the CommonJS export names for ESM import in node:
4224
4224
  0 && (module.exports = {
4225
4225
  cleanupTests,
4226
- contract,
4226
+ createContract,
4227
4227
  makeAccordionAccessible,
4228
4228
  makeBlockAccessible,
4229
4229
  makeCheckboxAccessible,
package/dist/index.d.cts CHANGED
@@ -327,7 +327,7 @@ declare class ContractBuilder {
327
327
  private validateDynamicTargets;
328
328
  build(): JsonContract;
329
329
  }
330
- declare function contract(componentName: string, define: (c: ContractBuilder) => void): FluentContract;
330
+ declare function createContract(componentName: string, define: (c: ContractBuilder) => void): FluentContract;
331
331
 
332
332
  type StrictnessMode = 'minimal' | 'balanced' | 'strict' | 'paranoid';
333
333
 
@@ -348,4 +348,4 @@ declare function testUiComponent(componentName: string, component: HTMLElement |
348
348
  */
349
349
  declare function cleanupTests(): Promise<void>;
350
350
 
351
- export { ContractBuilder, type JsonContract, type RelationshipInvariant, cleanupTests, contract, makeAccordionAccessible, makeBlockAccessible, makeCheckboxAccessible, makeComboboxAccessible, makeMenuAccessible, makeRadioAccessible, makeTabsAccessible, makeToggleAccessible, testUiComponent };
351
+ export { ContractBuilder, type JsonContract, type RelationshipInvariant, cleanupTests, createContract, makeAccordionAccessible, makeBlockAccessible, makeCheckboxAccessible, makeComboboxAccessible, makeMenuAccessible, makeRadioAccessible, makeTabsAccessible, makeToggleAccessible, testUiComponent };
package/dist/index.d.ts CHANGED
@@ -327,7 +327,7 @@ declare class ContractBuilder {
327
327
  private validateDynamicTargets;
328
328
  build(): JsonContract;
329
329
  }
330
- declare function contract(componentName: string, define: (c: ContractBuilder) => void): FluentContract;
330
+ declare function createContract(componentName: string, define: (c: ContractBuilder) => void): FluentContract;
331
331
 
332
332
  type StrictnessMode = 'minimal' | 'balanced' | 'strict' | 'paranoid';
333
333
 
@@ -348,4 +348,4 @@ declare function testUiComponent(componentName: string, component: HTMLElement |
348
348
  */
349
349
  declare function cleanupTests(): Promise<void>;
350
350
 
351
- export { ContractBuilder, type JsonContract, type RelationshipInvariant, cleanupTests, contract, makeAccordionAccessible, makeBlockAccessible, makeCheckboxAccessible, makeComboboxAccessible, makeMenuAccessible, makeRadioAccessible, makeTabsAccessible, makeToggleAccessible, testUiComponent };
351
+ export { ContractBuilder, type JsonContract, type RelationshipInvariant, cleanupTests, createContract, makeAccordionAccessible, makeBlockAccessible, makeCheckboxAccessible, makeComboboxAccessible, makeMenuAccessible, makeRadioAccessible, makeTabsAccessible, makeToggleAccessible, testUiComponent };
package/dist/index.js CHANGED
@@ -1805,7 +1805,7 @@ var ContractBuilder = class {
1805
1805
  };
1806
1806
  }
1807
1807
  };
1808
- function contract(componentName, define) {
1808
+ function createContract(componentName, define) {
1809
1809
  const builder = new ContractBuilder(componentName);
1810
1810
  define(builder);
1811
1811
  return new FluentContract(builder.build());
@@ -2041,14 +2041,14 @@ Error: ${error instanceof Error ? error.message : String(error)}`
2041
2041
  }
2042
2042
  }
2043
2043
  }
2044
- let contract2;
2044
+ let contract;
2045
2045
  try {
2046
2046
  if (url) {
2047
2047
  const devServerUrl = await checkDevServer(url);
2048
2048
  if (devServerUrl) {
2049
2049
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
2050
2050
  const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-XBWJZMR3.js");
2051
- contract2 = await runContractTestsPlaywright(componentName, devServerUrl, strictness, config, configBaseDir);
2051
+ contract = await runContractTestsPlaywright(componentName, devServerUrl, strictness, config, configBaseDir);
2052
2052
  } else {
2053
2053
  throw new Error(
2054
2054
  `\u274C Dev server not running at ${url}
@@ -2057,7 +2057,7 @@ Please start your dev server and try again.`
2057
2057
  }
2058
2058
  } else if (component) {
2059
2059
  console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
2060
- contract2 = await runContractTests(componentName, component, strictness);
2060
+ contract = await runContractTests(componentName, component, strictness);
2061
2061
  } else {
2062
2062
  throw new Error("\u274C Either component or URL must be provided");
2063
2063
  }
@@ -2070,13 +2070,13 @@ Please start your dev server and try again.`
2070
2070
  const result = {
2071
2071
  violations: results.violations,
2072
2072
  raw: results,
2073
- contract: contract2
2073
+ contract
2074
2074
  };
2075
- if (contract2.failures.length > 0 && url === "Playwright") {
2075
+ if (contract.failures.length > 0 && url === "Playwright") {
2076
2076
  throw new Error(
2077
2077
  `
2078
- \u274C ${contract2.failures.length} accessibility contract test${contract2.failures.length > 1 ? "s" : ""} failed (Playwright mode)
2079
- \u2705 ${contract2.passes.length} test${contract2.passes.length > 1 ? "s" : ""} passed
2078
+ \u274C ${contract.failures.length} accessibility contract test${contract.failures.length > 1 ? "s" : ""} failed (Playwright mode)
2079
+ \u2705 ${contract.passes.length} test${contract.passes.length > 1 ? "s" : ""} passed
2080
2080
 
2081
2081
  \u{1F4CB} Review the detailed test report above for specific failures.
2082
2082
  \u{1F4A1} Contract tests validate ARIA attributes and keyboard interactions per W3C APG guidelines.`
@@ -2151,7 +2151,7 @@ async function cleanupTests() {
2151
2151
  }
2152
2152
  export {
2153
2153
  cleanupTests,
2154
- contract,
2154
+ createContract,
2155
2155
  makeAccordionAccessible,
2156
2156
  makeBlockAccessible,
2157
2157
  makeCheckboxAccessible,
@@ -0,0 +1,320 @@
1
+ 'use strict';
2
+
3
+ // src/utils/test/dsl/index.ts
4
+ var FluentContract = class {
5
+ constructor(jsonContract) {
6
+ this.jsonContract = jsonContract;
7
+ }
8
+ toJSON() {
9
+ return this.jsonContract;
10
+ }
11
+ };
12
+ var StaticTargetBuilder = class {
13
+ constructor(targetName, sink) {
14
+ this.targetName = targetName;
15
+ this.sink = sink;
16
+ }
17
+ has(attribute, expectedValue) {
18
+ const create = (level) => {
19
+ this.sink.push({
20
+ target: this.targetName,
21
+ attribute,
22
+ expectedValue,
23
+ failureMessage: `Expected ${this.targetName} to have ${attribute}${expectedValue !== void 0 ? `=${expectedValue}` : ""}.`,
24
+ level
25
+ });
26
+ };
27
+ return {
28
+ required: () => create("required"),
29
+ recommended: () => create("recommended"),
30
+ optional: () => create("optional")
31
+ };
32
+ }
33
+ };
34
+ var StaticBuilder = class {
35
+ constructor(sink) {
36
+ this.sink = sink;
37
+ }
38
+ target(targetName) {
39
+ return new StaticTargetBuilder(targetName, this.sink);
40
+ }
41
+ };
42
+ var DynamicChain = class {
43
+ constructor(key, testsSink, selectors) {
44
+ this.key = key;
45
+ this.testsSink = testsSink;
46
+ this.selectors = selectors;
47
+ }
48
+ selectorTarget = "";
49
+ actions = [];
50
+ assertions = [];
51
+ explicitDescription = "";
52
+ on(target) {
53
+ this.selectorTarget = target;
54
+ this.actions.push({ type: "keypress", target, key: this.key });
55
+ return this;
56
+ }
57
+ describe(description) {
58
+ this.explicitDescription = description;
59
+ return this;
60
+ }
61
+ focus(targetExpression) {
62
+ const parsed = this.parseRelativeExpression(targetExpression);
63
+ if (parsed) {
64
+ if (!this.selectors[parsed.selectorKey]) {
65
+ const availableSelectors = Object.keys(this.selectors).sort().join(", ") || "(none)";
66
+ throw new Error(
67
+ `Invalid focus target expression "${targetExpression}": selector "${parsed.selectorKey}" is not defined. Available selectors: ${availableSelectors}`
68
+ );
69
+ }
70
+ if (!this.selectors.relative && this.selectors[parsed.selectorKey]) {
71
+ this.selectors.relative = this.selectors[parsed.selectorKey];
72
+ }
73
+ this.assertions.push({
74
+ target: "relative",
75
+ assertion: "toHaveFocus",
76
+ relativeTarget: parsed.relativeTarget
77
+ });
78
+ } else {
79
+ this.assertions.push({
80
+ target: targetExpression,
81
+ assertion: "toHaveFocus"
82
+ });
83
+ }
84
+ return this;
85
+ }
86
+ visible(target) {
87
+ this.assertions.push({ target, assertion: "toBeVisible" });
88
+ return this;
89
+ }
90
+ hidden(target) {
91
+ this.assertions.push({ target, assertion: "notToBeVisible" });
92
+ return this;
93
+ }
94
+ has(target, attribute, expectedValue) {
95
+ this.assertions.push({
96
+ target,
97
+ assertion: "toHaveAttribute",
98
+ attribute,
99
+ expectedValue
100
+ });
101
+ return this;
102
+ }
103
+ required() {
104
+ this.finalize("required");
105
+ }
106
+ recommended() {
107
+ this.finalize("recommended");
108
+ }
109
+ optional() {
110
+ this.finalize("optional");
111
+ }
112
+ finalize(level) {
113
+ if (!this.selectorTarget) {
114
+ throw new Error("Dynamic contract chain requires .on(<selectorKey>) before level terminator.");
115
+ }
116
+ const description = this.explicitDescription || `Pressing ${this.key} on ${this.selectorTarget} satisfies expected behavior.`;
117
+ this.testsSink.push({
118
+ description,
119
+ level,
120
+ action: this.actions,
121
+ assertions: this.assertions.map((a) => ({ ...a, level }))
122
+ });
123
+ }
124
+ parseRelativeExpression(input) {
125
+ const match = input.match(/^(next|previous|first|last)\(([^)]+)\)$/);
126
+ if (!match) return null;
127
+ const relativeTarget = match[1];
128
+ const selectorKey = match[2].trim();
129
+ return { relativeTarget, selectorKey };
130
+ }
131
+ };
132
+ var ContractBuilder = class {
133
+ constructor(componentName) {
134
+ this.componentName = componentName;
135
+ }
136
+ metaValue = {};
137
+ selectorsValue = {};
138
+ relationshipInvariants = [];
139
+ staticAssertions = [];
140
+ dynamicTests = [];
141
+ meta(meta) {
142
+ this.metaValue = { ...this.metaValue, ...meta };
143
+ return this;
144
+ }
145
+ selectors(selectors) {
146
+ this.selectorsValue = { ...this.selectorsValue, ...selectors };
147
+ return this;
148
+ }
149
+ relationship(invariant) {
150
+ this.relationshipInvariants.push(invariant);
151
+ return this;
152
+ }
153
+ relationships(builderFn) {
154
+ builderFn({
155
+ ariaReference: (from, attribute, to) => {
156
+ const create = (level) => {
157
+ this.relationshipInvariants.push({
158
+ type: "aria-reference",
159
+ from,
160
+ attribute,
161
+ to,
162
+ level
163
+ });
164
+ };
165
+ return {
166
+ required: () => create("required"),
167
+ recommended: () => create("recommended"),
168
+ optional: () => create("optional")
169
+ };
170
+ },
171
+ contains: (parent, child) => {
172
+ const create = (level) => {
173
+ this.relationshipInvariants.push({
174
+ type: "contains",
175
+ parent,
176
+ child,
177
+ level
178
+ });
179
+ };
180
+ return {
181
+ required: () => create("required"),
182
+ recommended: () => create("recommended"),
183
+ optional: () => create("optional")
184
+ };
185
+ }
186
+ });
187
+ return this;
188
+ }
189
+ static(builderFn) {
190
+ builderFn(new StaticBuilder(this.staticAssertions));
191
+ return this;
192
+ }
193
+ when(key) {
194
+ return new DynamicChain(key, this.dynamicTests, this.selectorsValue);
195
+ }
196
+ validateRelationshipInvariants() {
197
+ if (this.relationshipInvariants.length === 0) {
198
+ return;
199
+ }
200
+ const selectorKeys = new Set(Object.keys(this.selectorsValue));
201
+ const available = Object.keys(this.selectorsValue).sort().join(", ");
202
+ const errors = [];
203
+ this.relationshipInvariants.forEach((invariant, index) => {
204
+ const prefix = `relationships[${index}] (${invariant.type})`;
205
+ if (invariant.type === "aria-reference") {
206
+ if (!selectorKeys.has(invariant.from)) {
207
+ errors.push(`${prefix}: "from" references unknown selector "${invariant.from}"`);
208
+ }
209
+ if (!selectorKeys.has(invariant.to)) {
210
+ errors.push(`${prefix}: "to" references unknown selector "${invariant.to}"`);
211
+ }
212
+ }
213
+ if (invariant.type === "contains") {
214
+ if (!selectorKeys.has(invariant.parent)) {
215
+ errors.push(`${prefix}: "parent" references unknown selector "${invariant.parent}"`);
216
+ }
217
+ if (!selectorKeys.has(invariant.child)) {
218
+ errors.push(`${prefix}: "child" references unknown selector "${invariant.child}"`);
219
+ }
220
+ }
221
+ });
222
+ if (errors.length > 0) {
223
+ const availableSelectorsMessage = available.length > 0 ? available : "(none)";
224
+ throw new Error(
225
+ [
226
+ `Contract invariant validation failed for component "${this.componentName}".`,
227
+ ...errors.map((error) => `- ${error}`),
228
+ `Available selectors: ${availableSelectorsMessage}`
229
+ ].join("\n")
230
+ );
231
+ }
232
+ }
233
+ validateStaticTargets() {
234
+ const selectorKeys = new Set(Object.keys(this.selectorsValue));
235
+ const available = Object.keys(this.selectorsValue).sort().join(", ") || "(none)";
236
+ const errors = [];
237
+ this.staticAssertions.forEach((assertion, index) => {
238
+ if (!selectorKeys.has(assertion.target)) {
239
+ errors.push(`static.assertions[${index}]: target "${assertion.target}" is not defined in selectors`);
240
+ }
241
+ });
242
+ if (errors.length > 0) {
243
+ throw new Error(
244
+ [
245
+ `Contract static target validation failed for component "${this.componentName}".`,
246
+ ...errors.map((error) => `- ${error}`),
247
+ `Available selectors: ${available}`
248
+ ].join("\n")
249
+ );
250
+ }
251
+ }
252
+ validateDynamicTargets() {
253
+ const selectorKeys = new Set(Object.keys(this.selectorsValue));
254
+ const available = Object.keys(this.selectorsValue).sort().join(", ") || "(none)";
255
+ const errors = [];
256
+ const isValidActionTarget = (target) => {
257
+ return selectorKeys.has(target) || target === "document" || target === "relative";
258
+ };
259
+ const isValidAssertionTarget = (target) => {
260
+ return selectorKeys.has(target) || target === "relative";
261
+ };
262
+ this.dynamicTests.forEach((test, testIndex) => {
263
+ test.action.forEach((action, actionIndex) => {
264
+ if (!isValidActionTarget(action.target)) {
265
+ errors.push(
266
+ `dynamic[${testIndex}].action[${actionIndex}]: target "${action.target}" is not defined in selectors`
267
+ );
268
+ }
269
+ });
270
+ test.assertions.forEach((assertion, assertionIndex) => {
271
+ if (!isValidAssertionTarget(assertion.target)) {
272
+ errors.push(
273
+ `dynamic[${testIndex}].assertions[${assertionIndex}]: target "${assertion.target}" is not defined in selectors`
274
+ );
275
+ }
276
+ if (assertion.target === "relative" && !this.selectorsValue.relative) {
277
+ errors.push(
278
+ `dynamic[${testIndex}].assertions[${assertionIndex}]: target "relative" requires selectors.relative to be defined`
279
+ );
280
+ }
281
+ });
282
+ });
283
+ if (errors.length > 0) {
284
+ throw new Error(
285
+ [
286
+ `Contract dynamic target validation failed for component "${this.componentName}".`,
287
+ ...errors.map((error) => `- ${error}`),
288
+ `Available selectors: ${available}`,
289
+ `Allowed special targets: document, relative`
290
+ ].join("\n")
291
+ );
292
+ }
293
+ }
294
+ build() {
295
+ this.validateRelationshipInvariants();
296
+ this.validateStaticTargets();
297
+ this.validateDynamicTargets();
298
+ const fallbackId = this.metaValue.id || `aria-ease.contract.${this.componentName}`;
299
+ return {
300
+ meta: {
301
+ id: fallbackId,
302
+ version: this.metaValue.version || "1.0.0",
303
+ description: this.metaValue.description || `Fluent contract for ${this.componentName}`,
304
+ source: this.metaValue.source,
305
+ W3CName: this.metaValue.W3CName
306
+ },
307
+ selectors: this.selectorsValue,
308
+ relationships: this.relationshipInvariants,
309
+ static: [{ assertions: this.staticAssertions }],
310
+ dynamic: this.dynamicTests
311
+ };
312
+ }
313
+ };
314
+ function createContract(componentName, define) {
315
+ const builder = new ContractBuilder(componentName);
316
+ define(builder);
317
+ return new FluentContract(builder.build());
318
+ }
319
+
320
+ exports.createContract = createContract;
@@ -0,0 +1,136 @@
1
+ type Level = "required" | "recommended" | "optional";
2
+ type ContractMeta = {
3
+ id?: string;
4
+ version?: string;
5
+ description?: string;
6
+ source?: {
7
+ apg?: string;
8
+ wcag?: string[];
9
+ };
10
+ W3CName?: string;
11
+ };
12
+ type SelectorsMap = Record<string, string>;
13
+ type RelationshipInvariant = {
14
+ type: "aria-reference";
15
+ from: string;
16
+ attribute: string;
17
+ to: string;
18
+ level?: Level;
19
+ } | {
20
+ type: "contains";
21
+ parent: string;
22
+ child: string;
23
+ level?: Level;
24
+ };
25
+ type StaticAssertion = {
26
+ target: string;
27
+ attribute: string;
28
+ expectedValue?: string;
29
+ failureMessage: string;
30
+ level: Level;
31
+ };
32
+ type DynamicAction = {
33
+ type: "focus" | "type" | "click" | "keypress" | "hover";
34
+ target: string;
35
+ key?: string;
36
+ value?: string;
37
+ relativeTarget?: string;
38
+ };
39
+ type DynamicAssertion = {
40
+ target: string;
41
+ assertion: "toBeVisible" | "notToBeVisible" | "toHaveAttribute" | "toHaveValue" | "toHaveFocus" | "toHaveRole";
42
+ attribute?: string;
43
+ expectedValue?: string;
44
+ failureMessage?: string;
45
+ relativeTarget?: string;
46
+ level?: Level;
47
+ };
48
+ type DynamicTest = {
49
+ description: string;
50
+ level?: Level;
51
+ action: DynamicAction[];
52
+ assertions: DynamicAssertion[];
53
+ };
54
+ type JsonContract = {
55
+ meta?: ContractMeta;
56
+ selectors: SelectorsMap;
57
+ relationships?: RelationshipInvariant[];
58
+ static: Array<{
59
+ assertions: StaticAssertion[];
60
+ }>;
61
+ dynamic: DynamicTest[];
62
+ };
63
+ declare class FluentContract {
64
+ private readonly jsonContract;
65
+ constructor(jsonContract: JsonContract);
66
+ toJSON(): JsonContract;
67
+ }
68
+ declare class StaticTargetBuilder {
69
+ private readonly targetName;
70
+ private readonly sink;
71
+ constructor(targetName: string, sink: StaticAssertion[]);
72
+ has(attribute: string, expectedValue?: string): {
73
+ required: () => void;
74
+ recommended: () => void;
75
+ optional: () => void;
76
+ };
77
+ }
78
+ declare class StaticBuilder {
79
+ private readonly sink;
80
+ constructor(sink: StaticAssertion[]);
81
+ target(targetName: string): StaticTargetBuilder;
82
+ }
83
+ declare class DynamicChain {
84
+ private readonly key;
85
+ private readonly testsSink;
86
+ private readonly selectors;
87
+ private selectorTarget;
88
+ private readonly actions;
89
+ private readonly assertions;
90
+ private explicitDescription;
91
+ constructor(key: string, testsSink: DynamicTest[], selectors: SelectorsMap);
92
+ on(target: string): this;
93
+ describe(description: string): this;
94
+ focus(targetExpression: string): this;
95
+ visible(target: string): this;
96
+ hidden(target: string): this;
97
+ has(target: string, attribute: string, expectedValue?: string): this;
98
+ required(): void;
99
+ recommended(): void;
100
+ optional(): void;
101
+ private finalize;
102
+ private parseRelativeExpression;
103
+ }
104
+ declare class ContractBuilder {
105
+ private readonly componentName;
106
+ private metaValue;
107
+ private selectorsValue;
108
+ private readonly relationshipInvariants;
109
+ private readonly staticAssertions;
110
+ private readonly dynamicTests;
111
+ constructor(componentName: string);
112
+ meta(meta: ContractMeta): this;
113
+ selectors(selectors: SelectorsMap): this;
114
+ relationship(invariant: RelationshipInvariant): this;
115
+ relationships(builderFn: (r: {
116
+ ariaReference: (from: string, attribute: string, to: string) => {
117
+ required: () => void;
118
+ recommended: () => void;
119
+ optional: () => void;
120
+ };
121
+ contains: (parent: string, child: string) => {
122
+ required: () => void;
123
+ recommended: () => void;
124
+ optional: () => void;
125
+ };
126
+ }) => void): this;
127
+ static(builderFn: (s: StaticBuilder) => void): this;
128
+ when(key: string): DynamicChain;
129
+ private validateRelationshipInvariants;
130
+ private validateStaticTargets;
131
+ private validateDynamicTargets;
132
+ build(): JsonContract;
133
+ }
134
+ declare function createContract(componentName: string, define: (c: ContractBuilder) => void): FluentContract;
135
+
136
+ export { ContractBuilder, type JsonContract, type RelationshipInvariant, createContract };
@@ -0,0 +1,136 @@
1
+ type Level = "required" | "recommended" | "optional";
2
+ type ContractMeta = {
3
+ id?: string;
4
+ version?: string;
5
+ description?: string;
6
+ source?: {
7
+ apg?: string;
8
+ wcag?: string[];
9
+ };
10
+ W3CName?: string;
11
+ };
12
+ type SelectorsMap = Record<string, string>;
13
+ type RelationshipInvariant = {
14
+ type: "aria-reference";
15
+ from: string;
16
+ attribute: string;
17
+ to: string;
18
+ level?: Level;
19
+ } | {
20
+ type: "contains";
21
+ parent: string;
22
+ child: string;
23
+ level?: Level;
24
+ };
25
+ type StaticAssertion = {
26
+ target: string;
27
+ attribute: string;
28
+ expectedValue?: string;
29
+ failureMessage: string;
30
+ level: Level;
31
+ };
32
+ type DynamicAction = {
33
+ type: "focus" | "type" | "click" | "keypress" | "hover";
34
+ target: string;
35
+ key?: string;
36
+ value?: string;
37
+ relativeTarget?: string;
38
+ };
39
+ type DynamicAssertion = {
40
+ target: string;
41
+ assertion: "toBeVisible" | "notToBeVisible" | "toHaveAttribute" | "toHaveValue" | "toHaveFocus" | "toHaveRole";
42
+ attribute?: string;
43
+ expectedValue?: string;
44
+ failureMessage?: string;
45
+ relativeTarget?: string;
46
+ level?: Level;
47
+ };
48
+ type DynamicTest = {
49
+ description: string;
50
+ level?: Level;
51
+ action: DynamicAction[];
52
+ assertions: DynamicAssertion[];
53
+ };
54
+ type JsonContract = {
55
+ meta?: ContractMeta;
56
+ selectors: SelectorsMap;
57
+ relationships?: RelationshipInvariant[];
58
+ static: Array<{
59
+ assertions: StaticAssertion[];
60
+ }>;
61
+ dynamic: DynamicTest[];
62
+ };
63
+ declare class FluentContract {
64
+ private readonly jsonContract;
65
+ constructor(jsonContract: JsonContract);
66
+ toJSON(): JsonContract;
67
+ }
68
+ declare class StaticTargetBuilder {
69
+ private readonly targetName;
70
+ private readonly sink;
71
+ constructor(targetName: string, sink: StaticAssertion[]);
72
+ has(attribute: string, expectedValue?: string): {
73
+ required: () => void;
74
+ recommended: () => void;
75
+ optional: () => void;
76
+ };
77
+ }
78
+ declare class StaticBuilder {
79
+ private readonly sink;
80
+ constructor(sink: StaticAssertion[]);
81
+ target(targetName: string): StaticTargetBuilder;
82
+ }
83
+ declare class DynamicChain {
84
+ private readonly key;
85
+ private readonly testsSink;
86
+ private readonly selectors;
87
+ private selectorTarget;
88
+ private readonly actions;
89
+ private readonly assertions;
90
+ private explicitDescription;
91
+ constructor(key: string, testsSink: DynamicTest[], selectors: SelectorsMap);
92
+ on(target: string): this;
93
+ describe(description: string): this;
94
+ focus(targetExpression: string): this;
95
+ visible(target: string): this;
96
+ hidden(target: string): this;
97
+ has(target: string, attribute: string, expectedValue?: string): this;
98
+ required(): void;
99
+ recommended(): void;
100
+ optional(): void;
101
+ private finalize;
102
+ private parseRelativeExpression;
103
+ }
104
+ declare class ContractBuilder {
105
+ private readonly componentName;
106
+ private metaValue;
107
+ private selectorsValue;
108
+ private readonly relationshipInvariants;
109
+ private readonly staticAssertions;
110
+ private readonly dynamicTests;
111
+ constructor(componentName: string);
112
+ meta(meta: ContractMeta): this;
113
+ selectors(selectors: SelectorsMap): this;
114
+ relationship(invariant: RelationshipInvariant): this;
115
+ relationships(builderFn: (r: {
116
+ ariaReference: (from: string, attribute: string, to: string) => {
117
+ required: () => void;
118
+ recommended: () => void;
119
+ optional: () => void;
120
+ };
121
+ contains: (parent: string, child: string) => {
122
+ required: () => void;
123
+ recommended: () => void;
124
+ optional: () => void;
125
+ };
126
+ }) => void): this;
127
+ static(builderFn: (s: StaticBuilder) => void): this;
128
+ when(key: string): DynamicChain;
129
+ private validateRelationshipInvariants;
130
+ private validateStaticTargets;
131
+ private validateDynamicTargets;
132
+ build(): JsonContract;
133
+ }
134
+ declare function createContract(componentName: string, define: (c: ContractBuilder) => void): FluentContract;
135
+
136
+ export { ContractBuilder, type JsonContract, type RelationshipInvariant, createContract };
@@ -0,0 +1,318 @@
1
+ // src/utils/test/dsl/index.ts
2
+ var FluentContract = class {
3
+ constructor(jsonContract) {
4
+ this.jsonContract = jsonContract;
5
+ }
6
+ toJSON() {
7
+ return this.jsonContract;
8
+ }
9
+ };
10
+ var StaticTargetBuilder = class {
11
+ constructor(targetName, sink) {
12
+ this.targetName = targetName;
13
+ this.sink = sink;
14
+ }
15
+ has(attribute, expectedValue) {
16
+ const create = (level) => {
17
+ this.sink.push({
18
+ target: this.targetName,
19
+ attribute,
20
+ expectedValue,
21
+ failureMessage: `Expected ${this.targetName} to have ${attribute}${expectedValue !== void 0 ? `=${expectedValue}` : ""}.`,
22
+ level
23
+ });
24
+ };
25
+ return {
26
+ required: () => create("required"),
27
+ recommended: () => create("recommended"),
28
+ optional: () => create("optional")
29
+ };
30
+ }
31
+ };
32
+ var StaticBuilder = class {
33
+ constructor(sink) {
34
+ this.sink = sink;
35
+ }
36
+ target(targetName) {
37
+ return new StaticTargetBuilder(targetName, this.sink);
38
+ }
39
+ };
40
+ var DynamicChain = class {
41
+ constructor(key, testsSink, selectors) {
42
+ this.key = key;
43
+ this.testsSink = testsSink;
44
+ this.selectors = selectors;
45
+ }
46
+ selectorTarget = "";
47
+ actions = [];
48
+ assertions = [];
49
+ explicitDescription = "";
50
+ on(target) {
51
+ this.selectorTarget = target;
52
+ this.actions.push({ type: "keypress", target, key: this.key });
53
+ return this;
54
+ }
55
+ describe(description) {
56
+ this.explicitDescription = description;
57
+ return this;
58
+ }
59
+ focus(targetExpression) {
60
+ const parsed = this.parseRelativeExpression(targetExpression);
61
+ if (parsed) {
62
+ if (!this.selectors[parsed.selectorKey]) {
63
+ const availableSelectors = Object.keys(this.selectors).sort().join(", ") || "(none)";
64
+ throw new Error(
65
+ `Invalid focus target expression "${targetExpression}": selector "${parsed.selectorKey}" is not defined. Available selectors: ${availableSelectors}`
66
+ );
67
+ }
68
+ if (!this.selectors.relative && this.selectors[parsed.selectorKey]) {
69
+ this.selectors.relative = this.selectors[parsed.selectorKey];
70
+ }
71
+ this.assertions.push({
72
+ target: "relative",
73
+ assertion: "toHaveFocus",
74
+ relativeTarget: parsed.relativeTarget
75
+ });
76
+ } else {
77
+ this.assertions.push({
78
+ target: targetExpression,
79
+ assertion: "toHaveFocus"
80
+ });
81
+ }
82
+ return this;
83
+ }
84
+ visible(target) {
85
+ this.assertions.push({ target, assertion: "toBeVisible" });
86
+ return this;
87
+ }
88
+ hidden(target) {
89
+ this.assertions.push({ target, assertion: "notToBeVisible" });
90
+ return this;
91
+ }
92
+ has(target, attribute, expectedValue) {
93
+ this.assertions.push({
94
+ target,
95
+ assertion: "toHaveAttribute",
96
+ attribute,
97
+ expectedValue
98
+ });
99
+ return this;
100
+ }
101
+ required() {
102
+ this.finalize("required");
103
+ }
104
+ recommended() {
105
+ this.finalize("recommended");
106
+ }
107
+ optional() {
108
+ this.finalize("optional");
109
+ }
110
+ finalize(level) {
111
+ if (!this.selectorTarget) {
112
+ throw new Error("Dynamic contract chain requires .on(<selectorKey>) before level terminator.");
113
+ }
114
+ const description = this.explicitDescription || `Pressing ${this.key} on ${this.selectorTarget} satisfies expected behavior.`;
115
+ this.testsSink.push({
116
+ description,
117
+ level,
118
+ action: this.actions,
119
+ assertions: this.assertions.map((a) => ({ ...a, level }))
120
+ });
121
+ }
122
+ parseRelativeExpression(input) {
123
+ const match = input.match(/^(next|previous|first|last)\(([^)]+)\)$/);
124
+ if (!match) return null;
125
+ const relativeTarget = match[1];
126
+ const selectorKey = match[2].trim();
127
+ return { relativeTarget, selectorKey };
128
+ }
129
+ };
130
+ var ContractBuilder = class {
131
+ constructor(componentName) {
132
+ this.componentName = componentName;
133
+ }
134
+ metaValue = {};
135
+ selectorsValue = {};
136
+ relationshipInvariants = [];
137
+ staticAssertions = [];
138
+ dynamicTests = [];
139
+ meta(meta) {
140
+ this.metaValue = { ...this.metaValue, ...meta };
141
+ return this;
142
+ }
143
+ selectors(selectors) {
144
+ this.selectorsValue = { ...this.selectorsValue, ...selectors };
145
+ return this;
146
+ }
147
+ relationship(invariant) {
148
+ this.relationshipInvariants.push(invariant);
149
+ return this;
150
+ }
151
+ relationships(builderFn) {
152
+ builderFn({
153
+ ariaReference: (from, attribute, to) => {
154
+ const create = (level) => {
155
+ this.relationshipInvariants.push({
156
+ type: "aria-reference",
157
+ from,
158
+ attribute,
159
+ to,
160
+ level
161
+ });
162
+ };
163
+ return {
164
+ required: () => create("required"),
165
+ recommended: () => create("recommended"),
166
+ optional: () => create("optional")
167
+ };
168
+ },
169
+ contains: (parent, child) => {
170
+ const create = (level) => {
171
+ this.relationshipInvariants.push({
172
+ type: "contains",
173
+ parent,
174
+ child,
175
+ level
176
+ });
177
+ };
178
+ return {
179
+ required: () => create("required"),
180
+ recommended: () => create("recommended"),
181
+ optional: () => create("optional")
182
+ };
183
+ }
184
+ });
185
+ return this;
186
+ }
187
+ static(builderFn) {
188
+ builderFn(new StaticBuilder(this.staticAssertions));
189
+ return this;
190
+ }
191
+ when(key) {
192
+ return new DynamicChain(key, this.dynamicTests, this.selectorsValue);
193
+ }
194
+ validateRelationshipInvariants() {
195
+ if (this.relationshipInvariants.length === 0) {
196
+ return;
197
+ }
198
+ const selectorKeys = new Set(Object.keys(this.selectorsValue));
199
+ const available = Object.keys(this.selectorsValue).sort().join(", ");
200
+ const errors = [];
201
+ this.relationshipInvariants.forEach((invariant, index) => {
202
+ const prefix = `relationships[${index}] (${invariant.type})`;
203
+ if (invariant.type === "aria-reference") {
204
+ if (!selectorKeys.has(invariant.from)) {
205
+ errors.push(`${prefix}: "from" references unknown selector "${invariant.from}"`);
206
+ }
207
+ if (!selectorKeys.has(invariant.to)) {
208
+ errors.push(`${prefix}: "to" references unknown selector "${invariant.to}"`);
209
+ }
210
+ }
211
+ if (invariant.type === "contains") {
212
+ if (!selectorKeys.has(invariant.parent)) {
213
+ errors.push(`${prefix}: "parent" references unknown selector "${invariant.parent}"`);
214
+ }
215
+ if (!selectorKeys.has(invariant.child)) {
216
+ errors.push(`${prefix}: "child" references unknown selector "${invariant.child}"`);
217
+ }
218
+ }
219
+ });
220
+ if (errors.length > 0) {
221
+ const availableSelectorsMessage = available.length > 0 ? available : "(none)";
222
+ throw new Error(
223
+ [
224
+ `Contract invariant validation failed for component "${this.componentName}".`,
225
+ ...errors.map((error) => `- ${error}`),
226
+ `Available selectors: ${availableSelectorsMessage}`
227
+ ].join("\n")
228
+ );
229
+ }
230
+ }
231
+ validateStaticTargets() {
232
+ const selectorKeys = new Set(Object.keys(this.selectorsValue));
233
+ const available = Object.keys(this.selectorsValue).sort().join(", ") || "(none)";
234
+ const errors = [];
235
+ this.staticAssertions.forEach((assertion, index) => {
236
+ if (!selectorKeys.has(assertion.target)) {
237
+ errors.push(`static.assertions[${index}]: target "${assertion.target}" is not defined in selectors`);
238
+ }
239
+ });
240
+ if (errors.length > 0) {
241
+ throw new Error(
242
+ [
243
+ `Contract static target validation failed for component "${this.componentName}".`,
244
+ ...errors.map((error) => `- ${error}`),
245
+ `Available selectors: ${available}`
246
+ ].join("\n")
247
+ );
248
+ }
249
+ }
250
+ validateDynamicTargets() {
251
+ const selectorKeys = new Set(Object.keys(this.selectorsValue));
252
+ const available = Object.keys(this.selectorsValue).sort().join(", ") || "(none)";
253
+ const errors = [];
254
+ const isValidActionTarget = (target) => {
255
+ return selectorKeys.has(target) || target === "document" || target === "relative";
256
+ };
257
+ const isValidAssertionTarget = (target) => {
258
+ return selectorKeys.has(target) || target === "relative";
259
+ };
260
+ this.dynamicTests.forEach((test, testIndex) => {
261
+ test.action.forEach((action, actionIndex) => {
262
+ if (!isValidActionTarget(action.target)) {
263
+ errors.push(
264
+ `dynamic[${testIndex}].action[${actionIndex}]: target "${action.target}" is not defined in selectors`
265
+ );
266
+ }
267
+ });
268
+ test.assertions.forEach((assertion, assertionIndex) => {
269
+ if (!isValidAssertionTarget(assertion.target)) {
270
+ errors.push(
271
+ `dynamic[${testIndex}].assertions[${assertionIndex}]: target "${assertion.target}" is not defined in selectors`
272
+ );
273
+ }
274
+ if (assertion.target === "relative" && !this.selectorsValue.relative) {
275
+ errors.push(
276
+ `dynamic[${testIndex}].assertions[${assertionIndex}]: target "relative" requires selectors.relative to be defined`
277
+ );
278
+ }
279
+ });
280
+ });
281
+ if (errors.length > 0) {
282
+ throw new Error(
283
+ [
284
+ `Contract dynamic target validation failed for component "${this.componentName}".`,
285
+ ...errors.map((error) => `- ${error}`),
286
+ `Available selectors: ${available}`,
287
+ `Allowed special targets: document, relative`
288
+ ].join("\n")
289
+ );
290
+ }
291
+ }
292
+ build() {
293
+ this.validateRelationshipInvariants();
294
+ this.validateStaticTargets();
295
+ this.validateDynamicTargets();
296
+ const fallbackId = this.metaValue.id || `aria-ease.contract.${this.componentName}`;
297
+ return {
298
+ meta: {
299
+ id: fallbackId,
300
+ version: this.metaValue.version || "1.0.0",
301
+ description: this.metaValue.description || `Fluent contract for ${this.componentName}`,
302
+ source: this.metaValue.source,
303
+ W3CName: this.metaValue.W3CName
304
+ },
305
+ selectors: this.selectorsValue,
306
+ relationships: this.relationshipInvariants,
307
+ static: [{ assertions: this.staticAssertions }],
308
+ dynamic: this.dynamicTests
309
+ };
310
+ }
311
+ };
312
+ function createContract(componentName, define) {
313
+ const builder = new ContractBuilder(componentName);
314
+ define(builder);
315
+ return new FluentContract(builder.build());
316
+ }
317
+
318
+ export { createContract };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aria-ease",
3
- "version": "6.9.0",
3
+ "version": "6.9.1",
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",
@@ -20,9 +20,10 @@
20
20
  "build:core": "tsup ./index.ts --format esm,cjs --dts --outDir dist --external jest-axe --external @testing-library/react --external @axe-core/playwright --external playwright",
21
21
  "build:modules": "tsup ./src/combobox/index.ts ./src/accordion/index.ts ./src/block/index.ts ./src/checkbox/index.ts ./src/menu/index.ts ./src/tabs/index.ts ./src/radio/index.ts ./src/toggle/index.ts ./src/contracts/index.ts --format esm,cjs --dts --treeshake --outDir dist/src",
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
+ "build:dsl": "tsup ./src/utils/test/dsl/index.ts --format esm,cjs --dts --treeshake --outDir dist/src/utils/test/dsl",
23
24
  "build:cli": "tsup ./src/utils/cli/cli.ts --format esm,cjs --dts --outDir bin --external commander --external chalk --external jest-axe --external @testing-library/react --external @axe-core/playwright --external playwright",
24
25
  "build:contracts": "mkdir -p ./dist/src/utils/test && cp -r ./src/utils/test/contract/aria-contracts ./dist/src/utils/test/",
25
- "build": "npm run clean && npm run build:core && npm run build:modules && npm run build:test && npm run build:cli && npm run build:contracts"
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"
26
27
  },
27
28
  "repository": {
28
29
  "type": "git",
@@ -118,10 +119,10 @@
118
119
  "import": "./dist/src/tabs/index.js",
119
120
  "require": "./dist/src/tabs/index.cjs"
120
121
  },
121
- "./contracts": {
122
- "types": "./dist/src/contracts/index.d.ts",
123
- "import": "./dist/src/contracts/index.js",
124
- "require": "./dist/src/contracts/index.cjs"
122
+ "./contract": {
123
+ "types": "./dist/src/utils/test/dsl/index.d.ts",
124
+ "import": "./dist/src/utils/test/dsl/index.js",
125
+ "require": "./dist/src/utils/test/dsl/index.cjs"
125
126
  }
126
127
  },
127
128
  "files": [