@domglyph/testing 2.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 llcortex
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,284 @@
1
+ # @domglyph/testing (formerly [@cortexui/testing](https://www.npmjs.com/package/@cortexui/testing))
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@domglyph/testing?color=0ea5e9)](https://www.npmjs.com/package/@domglyph/testing)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](../../LICENSE)
5
+
6
+ AI contract validation and testing utilities for DOMglyph.
7
+
8
+ ---
9
+
10
+ ## Overview
11
+
12
+ `@domglyph/testing` gives you everything you need to verify that components correctly implement the DOMglyph AI contract. It includes:
13
+
14
+ - **`validateAIContractNode`** — validates `data-ai-*` attributes on a DOM element
15
+ - **`runComponentComplianceChecks`** — full compliance check combining AI contract and accessibility
16
+ - **Vitest matchers** — `toBeAIContractValid`, `toHaveAIAttributes`, `toPassAccessibilityChecks`
17
+ - **Test fixtures** — pre-built DOM elements for unit testing contract validators
18
+ - **`runAccessibilityChecks`** — accessibility audit for individual elements
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install --save-dev @domglyph/testing
26
+ ```
27
+
28
+ ---
29
+
30
+ ## API Reference
31
+
32
+ ### validateAIContractNode(element)
33
+
34
+ Validates all `data-ai-*` attributes on a DOM element against the DOMglyph AI contract specification. Returns a result object — does not throw.
35
+
36
+ ```ts
37
+ import { validateAIContractNode } from '@domglyph/testing';
38
+
39
+ const result = validateAIContractNode(element);
40
+ // {
41
+ // valid: boolean,
42
+ // errors: string[],
43
+ // attributes: AIAttributeMap
44
+ // }
45
+ ```
46
+
47
+ Example:
48
+
49
+ ```ts
50
+ import { validateAIContractNode } from '@domglyph/testing';
51
+ import { render, screen } from '@testing-library/react';
52
+ import { ActionButton } from '@domglyph/components';
53
+
54
+ render(<ActionButton action="save-profile" state="idle" label="Save" />);
55
+ const btn = screen.getByRole('button');
56
+
57
+ const result = validateAIContractNode(btn);
58
+ // { valid: true, errors: [], attributes: { role: 'action', id: 'save-profile', state: 'idle', ... } }
59
+ ```
60
+
61
+ When there are violations:
62
+
63
+ ```ts
64
+ // A button with data-ai-role="action" but no data-ai-id or data-ai-action
65
+ const result = validateAIContractNode(badElement);
66
+ // {
67
+ // valid: false,
68
+ // errors: [
69
+ // 'Elements with data-ai-role="action" must have data-ai-id',
70
+ // 'Elements with data-ai-role="action" must have data-ai-action'
71
+ // ],
72
+ // attributes: { role: 'action', id: null, action: null, state: null, ... }
73
+ // }
74
+ ```
75
+
76
+ ---
77
+
78
+ ### runComponentComplianceChecks(element, options)
79
+
80
+ Runs a full compliance check that covers both the AI contract and accessibility requirements. Useful for a single comprehensive assertion in integration tests.
81
+
82
+ ```ts
83
+ import { runComponentComplianceChecks } from '@domglyph/testing';
84
+
85
+ const result = runComponentComplianceChecks(element, {
86
+ requiredAttributes: ['data-ai-id', 'data-ai-role', 'data-ai-state'],
87
+ requiredAriaAttributes: ['aria-label'],
88
+ });
89
+ // {
90
+ // valid: boolean,
91
+ // contractErrors: string[],
92
+ // accessibilityErrors: string[]
93
+ // }
94
+ ```
95
+
96
+ ---
97
+
98
+ ### Vitest Matchers
99
+
100
+ Register custom matchers to write expressive assertions:
101
+
102
+ ```ts
103
+ import { registerCortexMatchers } from '@domglyph/testing';
104
+ import { expect } from 'vitest';
105
+
106
+ // Call once in your vitest setup file (vitest.setup.ts)
107
+ registerCortexMatchers(expect);
108
+ ```
109
+
110
+ Once registered, the following matchers are available on any DOM element:
111
+
112
+ #### `toBeAIContractValid()`
113
+
114
+ Asserts that the element passes full AI contract validation.
115
+
116
+ ```ts
117
+ expect(element).toBeAIContractValid();
118
+ // Fails with: "Expected element to have a valid AI contract, but found 2 error(s): ..."
119
+ ```
120
+
121
+ #### `toHaveAIAttributes(attributes)`
122
+
123
+ Asserts that the element has specific `data-ai-*` attribute values.
124
+
125
+ ```ts
126
+ expect(element).toHaveAIAttributes({
127
+ 'data-ai-role': 'action',
128
+ 'data-ai-action': 'save-profile',
129
+ 'data-ai-state': 'idle',
130
+ });
131
+ ```
132
+
133
+ #### `toPassAccessibilityChecks()`
134
+
135
+ Asserts that the element passes the DOMglyph accessibility audit.
136
+
137
+ ```ts
138
+ expect(element).toPassAccessibilityChecks();
139
+ ```
140
+
141
+ ---
142
+
143
+ ### Test Fixtures
144
+
145
+ Fixtures create valid DOM elements with AI contract attributes. Use them for testing validators, matchers, and utilities without needing to render full components.
146
+
147
+ ```ts
148
+ import { createActionFixture, createStatusFixture, createFieldFixture, createFormFixture } from '@domglyph/testing';
149
+
150
+ // Creates a <button> with data-ai-role="action" and the given props
151
+ const btn = createActionFixture({ action: 'save-profile', state: 'idle' });
152
+
153
+ // Creates a <div> with data-ai-role="status" and the given type
154
+ const banner = createStatusFixture({ type: 'success', message: 'Saved!' });
155
+
156
+ // Creates an <input> with data-ai-role="field"
157
+ const input = createFieldFixture({ id: 'user-email', fieldType: 'email', required: true });
158
+
159
+ // Creates a <form> element with data-ai-role="form"
160
+ const form = createFormFixture({ id: 'contact-form' });
161
+ ```
162
+
163
+ ---
164
+
165
+ ### runAccessibilityChecks(element)
166
+
167
+ Runs a focused accessibility audit on a single element and returns any violations:
168
+
169
+ ```ts
170
+ import { runAccessibilityChecks } from '@domglyph/testing';
171
+
172
+ const result = runAccessibilityChecks(element);
173
+ // {
174
+ // passed: boolean,
175
+ // violations: Array<{
176
+ // rule: string,
177
+ // message: string,
178
+ // impact: 'critical' | 'serious' | 'moderate' | 'minor'
179
+ // }>
180
+ // }
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Example Test Suite
186
+
187
+ A complete test file for a custom component, covering AI contract compliance, state transitions, and accessibility:
188
+
189
+ ```ts
190
+ import { describe, test, expect, beforeAll } from 'vitest';
191
+ import { render, screen } from '@testing-library/react';
192
+ import userEvent from '@testing-library/user-event';
193
+ import { validateAIContractNode, runAccessibilityChecks, registerCortexMatchers } from '@domglyph/testing';
194
+ import { MyButton } from '../src/MyButton';
195
+
196
+ beforeAll(() => {
197
+ registerCortexMatchers(expect);
198
+ });
199
+
200
+ describe('MyButton', () => {
201
+ describe('AI contract', () => {
202
+ test('has a valid AI contract in idle state', () => {
203
+ render(<MyButton action="confirm-order" state="idle" label="Confirm order" />);
204
+ const btn = screen.getByRole('button');
205
+ const result = validateAIContractNode(btn);
206
+ expect(result.valid).toBe(true);
207
+ expect(result.errors).toHaveLength(0);
208
+ });
209
+
210
+ test('has the correct data-ai-* attributes', () => {
211
+ render(<MyButton action="confirm-order" state="idle" label="Confirm order" />);
212
+ const btn = screen.getByRole('button');
213
+ expect(btn).toHaveAIAttributes({
214
+ 'data-ai-role': 'action',
215
+ 'data-ai-id': 'confirm-order',
216
+ 'data-ai-action': 'confirm-order',
217
+ 'data-ai-state': 'idle',
218
+ });
219
+ });
220
+
221
+ test('passes full contract validation', () => {
222
+ render(<MyButton action="confirm-order" state="idle" label="Confirm order" />);
223
+ expect(screen.getByRole('button')).toBeAIContractValid();
224
+ });
225
+ });
226
+
227
+ describe('state transitions', () => {
228
+ test('reflects loading state in data-ai-state', () => {
229
+ const { rerender } = render(
230
+ <MyButton action="confirm-order" state="idle" label="Confirm order" />
231
+ );
232
+ expect(screen.getByRole('button')).toHaveAttribute('data-ai-state', 'idle');
233
+
234
+ rerender(<MyButton action="confirm-order" state="loading" label="Confirm order" />);
235
+ expect(screen.getByRole('button')).toHaveAttribute('data-ai-state', 'loading');
236
+ });
237
+
238
+ test('reflects error state in data-ai-state', () => {
239
+ const { rerender } = render(
240
+ <MyButton action="confirm-order" state="idle" label="Confirm order" />
241
+ );
242
+ rerender(<MyButton action="confirm-order" state="error" label="Confirm order" />);
243
+ expect(screen.getByRole('button')).toHaveAttribute('data-ai-state', 'error');
244
+ });
245
+
246
+ test('is disabled in loading state', () => {
247
+ render(<MyButton action="confirm-order" state="loading" label="Confirm order" />);
248
+ expect(screen.getByRole('button')).toBeDisabled();
249
+ });
250
+ });
251
+
252
+ describe('accessibility', () => {
253
+ test('passes accessibility checks', () => {
254
+ render(<MyButton action="confirm-order" state="idle" label="Confirm order" />);
255
+ expect(screen.getByRole('button')).toPassAccessibilityChecks();
256
+ });
257
+
258
+ test('has accessible name from label', () => {
259
+ render(<MyButton action="confirm-order" state="idle" label="Confirm order" />);
260
+ expect(screen.getByRole('button', { name: 'Confirm order' })).toBeInTheDocument();
261
+ });
262
+ });
263
+ });
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Part of DOMglyph
269
+
270
+ `@domglyph/testing` is part of the [DOMglyph](../../README.md) design system.
271
+
272
+ - [Main repository](../../README.md)
273
+ - [Documentation](http://localhost:3001/docs/testing)
274
+ - [Contributing](../../CONTRIBUTING.md)
275
+
276
+ ---
277
+
278
+ ## ☕ Support
279
+
280
+ If you find DOMglyph useful, you can support the project:
281
+
282
+ 👉 https://buymeacoffee.com/nishchya
283
+
284
+ It helps keep the project alive and growing.
@@ -0,0 +1,99 @@
1
+ import { AIRole, AIState, AIAttributeValidationResult, AIAttributeMap, AIAttributeElement, validateAIAttributes, extractAIAttributes } from '@domglyph/ai-contract';
2
+ import { ExpectStatic } from 'vitest';
3
+
4
+ interface TestHarness {
5
+ readonly name: string;
6
+ readonly runner: "vitest";
7
+ }
8
+ interface VirtualAttribute {
9
+ readonly name: string;
10
+ readonly value: string;
11
+ }
12
+ interface VirtualElementOptions {
13
+ readonly tagName?: string;
14
+ readonly textContent?: string;
15
+ readonly hidden?: boolean;
16
+ readonly attributes?: Record<string, string | undefined>;
17
+ }
18
+ interface VirtualElement extends AIAttributeElement {
19
+ readonly tagName: string;
20
+ readonly textContent: string;
21
+ readonly hidden: boolean;
22
+ readonly attributes: readonly VirtualAttribute[];
23
+ getAttribute(name: string): string | null;
24
+ hasAttribute(name: string): boolean;
25
+ }
26
+ interface ContractCheckResult {
27
+ readonly valid: boolean;
28
+ readonly errors: readonly string[];
29
+ readonly attributes: AIAttributeMap;
30
+ }
31
+ interface AccessibilityCheckOptions {
32
+ readonly expectedRole?: AIRole;
33
+ readonly requireAccessibleName?: boolean;
34
+ }
35
+ interface AccessibilityCheckResult {
36
+ readonly valid: boolean;
37
+ readonly errors: readonly string[];
38
+ }
39
+ interface ComplianceCheckOptions extends AccessibilityCheckOptions {
40
+ readonly requiredAttributes?: readonly string[];
41
+ readonly allowedStates?: readonly AIState[];
42
+ }
43
+ interface ComplianceCheckResult {
44
+ readonly valid: boolean;
45
+ readonly contract: AIAttributeValidationResult;
46
+ readonly accessibility: AccessibilityCheckResult;
47
+ readonly errors: readonly string[];
48
+ }
49
+ interface MetadataSnapshot {
50
+ readonly target: string;
51
+ readonly attributes: AIAttributeMap;
52
+ }
53
+
54
+ declare function runAccessibilityChecks(element: ElementLike$2, options?: AccessibilityCheckOptions): AccessibilityCheckResult;
55
+ type ElementLike$2 = {
56
+ readonly textContent?: string;
57
+ getAttribute?: (name: string) => string | null;
58
+ readonly [key: string]: unknown;
59
+ };
60
+
61
+ declare function validateAIContractNode(element: ElementLike$1): ContractCheckResult;
62
+ declare function runComponentComplianceChecks(element: ElementLike$1, options?: ComplianceCheckOptions): ComplianceCheckResult;
63
+ type ElementLike$1 = Parameters<typeof validateAIAttributes>[0];
64
+
65
+ declare function createVirtualElement(options?: VirtualElementOptions): VirtualElement;
66
+
67
+ declare function createActionFixture(): VirtualElement;
68
+ declare function createStatusFixture(): VirtualElement;
69
+
70
+ declare const cortexMatchers: {
71
+ toBeAIContractValid(received: ElementLike): {
72
+ message: () => string;
73
+ pass: boolean;
74
+ };
75
+ toHaveAIAttributes(received: ElementLike, expected: Record<string, string>): {
76
+ message: () => string;
77
+ pass: boolean;
78
+ };
79
+ toPassAccessibilityChecks(received: ElementLike): {
80
+ message: () => string;
81
+ pass: boolean;
82
+ };
83
+ toMatchAIMetadataSnapshot(received: {
84
+ readonly target: string;
85
+ readonly attributes: Record<string, string | undefined>;
86
+ }, expected: string): {
87
+ message: () => string;
88
+ pass: boolean;
89
+ };
90
+ };
91
+ declare function registerCortexMatchers(expect: ExpectStatic): void;
92
+ type ElementLike = Parameters<typeof validateAIAttributes>[0];
93
+
94
+ declare function createMetadataSnapshot(target: string, element: Parameters<typeof extractAIAttributes>[0]): MetadataSnapshot;
95
+ declare function serializeMetadataSnapshot(snapshot: MetadataSnapshot): string;
96
+
97
+ declare const harness: TestHarness;
98
+
99
+ export { type AccessibilityCheckOptions, type AccessibilityCheckResult, type ComplianceCheckOptions, type ComplianceCheckResult, type ContractCheckResult, type MetadataSnapshot, type TestHarness, type VirtualAttribute, type VirtualElement, type VirtualElementOptions, cortexMatchers, createActionFixture, createMetadataSnapshot, createStatusFixture, createVirtualElement, harness, registerCortexMatchers, runAccessibilityChecks, runComponentComplianceChecks, serializeMetadataSnapshot, validateAIContractNode };
package/dist/index.js ADDED
@@ -0,0 +1,220 @@
1
+ // src/accessibility.ts
2
+ import { DATA_AI_ROLE } from "@domglyph/ai-contract";
3
+ function runAccessibilityChecks(element, options = {}) {
4
+ const errors = [];
5
+ const role = readAttribute(element, "role") ?? readAttribute(element, DATA_AI_ROLE);
6
+ const accessibleName = readAttribute(element, "aria-label") ?? readAttribute(element, "title") ?? readTextContent(element);
7
+ if (options.expectedRole && role !== options.expectedRole) {
8
+ errors.push(`Expected accessible role "${options.expectedRole}" but received "${role ?? "none"}".`);
9
+ }
10
+ if (options.requireAccessibleName !== false && (!accessibleName || accessibleName.trim() === "")) {
11
+ errors.push("Accessible name is required.");
12
+ }
13
+ if (readAttribute(element, "aria-hidden") === "true" && accessibleName) {
14
+ errors.push("aria-hidden content should not be used as an accessible target.");
15
+ }
16
+ return {
17
+ errors,
18
+ valid: errors.length === 0
19
+ };
20
+ }
21
+ function readAttribute(element, name) {
22
+ const value = element.getAttribute?.(name);
23
+ if (value !== null && value !== void 0) {
24
+ return value;
25
+ }
26
+ const recordValue = element[name];
27
+ return typeof recordValue === "string" ? recordValue : void 0;
28
+ }
29
+ function readTextContent(element) {
30
+ return typeof element.textContent === "string" ? element.textContent : void 0;
31
+ }
32
+
33
+ // src/contract.ts
34
+ import {
35
+ DATA_AI_ID,
36
+ DATA_AI_ROLE as DATA_AI_ROLE2,
37
+ DATA_AI_STATE,
38
+ extractAIAttributes,
39
+ validateAIAttributes
40
+ } from "@domglyph/ai-contract";
41
+ function validateAIContractNode(element) {
42
+ const result = validateAIAttributes(element);
43
+ return {
44
+ attributes: result.attributes,
45
+ errors: result.errors,
46
+ valid: result.valid
47
+ };
48
+ }
49
+ function runComponentComplianceChecks(element, options = {}) {
50
+ const contract = validateAIAttributes(element);
51
+ const accessibility = runAccessibilityChecks(element, options);
52
+ const metadata = extractAIAttributes(element);
53
+ const errors = [...contract.errors, ...accessibility.errors];
54
+ for (const name of options.requiredAttributes ?? []) {
55
+ if (metadata[name] === void 0) {
56
+ errors.push(`${name} is required for component compliance.`);
57
+ }
58
+ }
59
+ if (options.allowedStates && metadata[DATA_AI_STATE] !== void 0) {
60
+ const states = metadata[DATA_AI_STATE]?.split(",").map((state) => state.trim()).filter(Boolean) ?? [];
61
+ for (const state of states) {
62
+ if (!options.allowedStates.includes(state)) {
63
+ errors.push(`${DATA_AI_STATE} contains unsupported state "${state}".`);
64
+ }
65
+ }
66
+ }
67
+ if (metadata[DATA_AI_ROLE2] === void 0 && metadata[DATA_AI_ID] !== void 0) {
68
+ errors.push(`${DATA_AI_ROLE2} is required when ${DATA_AI_ID} is present.`);
69
+ }
70
+ return {
71
+ accessibility,
72
+ contract,
73
+ errors,
74
+ valid: errors.length === 0
75
+ };
76
+ }
77
+
78
+ // src/dom.ts
79
+ function createVirtualElement(options = {}) {
80
+ const attributes = Object.entries(options.attributes ?? {}).filter((entry) => typeof entry[1] === "string").map(([name, value]) => ({ name, value }));
81
+ return {
82
+ attributes,
83
+ dataset: Object.fromEntries(
84
+ attributes.filter((attribute) => attribute.name.startsWith("data-")).map((attribute) => [toDatasetKey(attribute.name), attribute.value])
85
+ ),
86
+ getAttribute(name) {
87
+ return attributes.find((attribute) => attribute.name === name)?.value ?? null;
88
+ },
89
+ hasAttribute(name) {
90
+ return attributes.some((attribute) => attribute.name === name);
91
+ },
92
+ hidden: options.hidden ?? false,
93
+ tagName: options.tagName ?? "div",
94
+ textContent: options.textContent ?? ""
95
+ };
96
+ }
97
+ function toDatasetKey(name) {
98
+ return name.replace(/^data-/, "").split("-").map((part, index) => index === 0 ? part : `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join("");
99
+ }
100
+
101
+ // src/fixtures.ts
102
+ import {
103
+ AIEvent,
104
+ AIRole,
105
+ AIState,
106
+ createAIAttributes
107
+ } from "@domglyph/ai-contract";
108
+ function createActionFixture() {
109
+ return createVirtualElement({
110
+ attributes: {
111
+ ...createAIAttributes({
112
+ action: {
113
+ expectedOutcome: "Profile saved",
114
+ id: "save-profile",
115
+ name: "Save profile"
116
+ },
117
+ id: "save-profile-button",
118
+ role: AIRole.ACTION,
119
+ state: [AIState.IDLE]
120
+ }),
121
+ "aria-label": "Save profile",
122
+ role: "button"
123
+ },
124
+ tagName: "button",
125
+ textContent: "Save profile"
126
+ });
127
+ }
128
+ function createStatusFixture() {
129
+ return createVirtualElement({
130
+ attributes: {
131
+ ...createAIAttributes({
132
+ event: AIEvent.ACTION_COMPLETED,
133
+ id: "save-status",
134
+ role: AIRole.STATUS,
135
+ state: AIState.SUCCESS,
136
+ status: "success"
137
+ }),
138
+ role: "status"
139
+ },
140
+ textContent: "Profile saved"
141
+ });
142
+ }
143
+
144
+ // src/matchers.ts
145
+ import { extractAIAttributes as extractAIAttributes3, validateAIAttributes as validateAIAttributes2 } from "@domglyph/ai-contract";
146
+
147
+ // src/snapshot.ts
148
+ import { extractAIAttributes as extractAIAttributes2 } from "@domglyph/ai-contract";
149
+ function createMetadataSnapshot(target, element) {
150
+ return {
151
+ attributes: extractAIAttributes2(element),
152
+ target
153
+ };
154
+ }
155
+ function serializeMetadataSnapshot(snapshot) {
156
+ return JSON.stringify(snapshot, null, 2);
157
+ }
158
+
159
+ // src/matchers.ts
160
+ var cortexMatchers = {
161
+ toBeAIContractValid(received) {
162
+ const validation = validateAIAttributes2(received);
163
+ return {
164
+ message: () => validation.valid ? "Expected AI contract validation to fail." : `Expected AI contract to be valid.
165
+ ${validation.errors.join("\n")}`,
166
+ pass: validation.valid
167
+ };
168
+ },
169
+ toHaveAIAttributes(received, expected) {
170
+ const attributes = extractAIAttributes3(received);
171
+ const missing = Object.entries(expected).filter(([key, value]) => attributes[key] !== value);
172
+ return {
173
+ message: () => missing.length === 0 ? "Expected AI attribute assertion to fail." : `Missing or mismatched attributes:
174
+ ${missing.map(([key, value]) => `${key}: ${value}`).join("\n")}`,
175
+ pass: missing.length === 0
176
+ };
177
+ },
178
+ toPassAccessibilityChecks(received) {
179
+ const result = runAccessibilityChecks(received);
180
+ return {
181
+ message: () => result.valid ? "Expected accessibility checks to fail." : `Expected element to pass accessibility checks.
182
+ ${result.errors.join("\n")}`,
183
+ pass: result.valid
184
+ };
185
+ },
186
+ toMatchAIMetadataSnapshot(received, expected) {
187
+ const actual = serializeMetadataSnapshot({
188
+ attributes: received.attributes,
189
+ target: received.target
190
+ });
191
+ return {
192
+ message: () => `Expected metadata snapshot to match.
193
+ Actual:
194
+ ${actual}`,
195
+ pass: actual === expected
196
+ };
197
+ }
198
+ };
199
+ function registerCortexMatchers(expect) {
200
+ expect.extend(cortexMatchers);
201
+ }
202
+
203
+ // src/index.ts
204
+ var harness = {
205
+ name: "domglyph-ai-testkit",
206
+ runner: "vitest"
207
+ };
208
+ export {
209
+ cortexMatchers,
210
+ createActionFixture,
211
+ createMetadataSnapshot,
212
+ createStatusFixture,
213
+ createVirtualElement,
214
+ harness,
215
+ registerCortexMatchers,
216
+ runAccessibilityChecks,
217
+ runComponentComplianceChecks,
218
+ serializeMetadataSnapshot,
219
+ validateAIContractNode
220
+ };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@domglyph/testing",
3
+ "version": "2.0.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@domglyph/components": "2.0.0",
19
+ "@domglyph/ai-contract": "2.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "tsup": "^8.5.1"
23
+ },
24
+ "scripts": {
25
+ "build": "tsup src/index.ts --format esm --dts --clean",
26
+ "dev": "tsup src/index.ts --format esm --dts --watch",
27
+ "lint": "eslint src --ext .ts",
28
+ "test": "vitest run",
29
+ "typecheck": "tsc -b"
30
+ }
31
+ }