@hypoth-ui/test-utils 0.1.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) 2025 hypoth-org
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,34 @@
1
+ # @hypoth-ui/test-utils
2
+
3
+ ![Alpha](https://img.shields.io/badge/status-alpha-orange)
4
+
5
+ Shared test utilities for the hypoth-ui design system. Provides helpers for rendering, querying, and asserting on Web Components and React adapters in Vitest.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @hypoth-ui/test-utils --save-dev
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { renderWc, renderReact, waitForElement } from '@hypoth-ui/test-utils';
17
+
18
+ // Render a Web Component for testing
19
+ const el = await renderWc('<ds-button variant="primary">Click</ds-button>');
20
+
21
+ // Render a React adapter for testing
22
+ const { getByRole } = renderReact(<Button variant="primary">Click</Button>);
23
+
24
+ // Wait for a custom element to be defined
25
+ await waitForElement('ds-button');
26
+ ```
27
+
28
+ ## Documentation
29
+
30
+ See the [main README](../../README.md) for full documentation and architecture overview.
31
+
32
+ ## License
33
+
34
+ MIT
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Keyboard simulation helpers for cross-framework testing.
3
+ */
4
+ interface KeyboardEventInit {
5
+ key: string;
6
+ code?: string;
7
+ keyCode?: number;
8
+ shiftKey?: boolean;
9
+ ctrlKey?: boolean;
10
+ altKey?: boolean;
11
+ metaKey?: boolean;
12
+ bubbles?: boolean;
13
+ cancelable?: boolean;
14
+ }
15
+ /**
16
+ * Common key mappings for keyboard simulation.
17
+ */
18
+ declare const Keys: {
19
+ readonly Enter: {
20
+ readonly key: "Enter";
21
+ readonly code: "Enter";
22
+ readonly keyCode: 13;
23
+ };
24
+ readonly Space: {
25
+ readonly key: " ";
26
+ readonly code: "Space";
27
+ readonly keyCode: 32;
28
+ };
29
+ readonly Escape: {
30
+ readonly key: "Escape";
31
+ readonly code: "Escape";
32
+ readonly keyCode: 27;
33
+ };
34
+ readonly Tab: {
35
+ readonly key: "Tab";
36
+ readonly code: "Tab";
37
+ readonly keyCode: 9;
38
+ };
39
+ readonly ArrowUp: {
40
+ readonly key: "ArrowUp";
41
+ readonly code: "ArrowUp";
42
+ readonly keyCode: 38;
43
+ };
44
+ readonly ArrowDown: {
45
+ readonly key: "ArrowDown";
46
+ readonly code: "ArrowDown";
47
+ readonly keyCode: 40;
48
+ };
49
+ readonly ArrowLeft: {
50
+ readonly key: "ArrowLeft";
51
+ readonly code: "ArrowLeft";
52
+ readonly keyCode: 37;
53
+ };
54
+ readonly ArrowRight: {
55
+ readonly key: "ArrowRight";
56
+ readonly code: "ArrowRight";
57
+ readonly keyCode: 39;
58
+ };
59
+ readonly Home: {
60
+ readonly key: "Home";
61
+ readonly code: "Home";
62
+ readonly keyCode: 36;
63
+ };
64
+ readonly End: {
65
+ readonly key: "End";
66
+ readonly code: "End";
67
+ readonly keyCode: 35;
68
+ };
69
+ readonly PageUp: {
70
+ readonly key: "PageUp";
71
+ readonly code: "PageUp";
72
+ readonly keyCode: 33;
73
+ };
74
+ readonly PageDown: {
75
+ readonly key: "PageDown";
76
+ readonly code: "PageDown";
77
+ readonly keyCode: 34;
78
+ };
79
+ readonly Backspace: {
80
+ readonly key: "Backspace";
81
+ readonly code: "Backspace";
82
+ readonly keyCode: 8;
83
+ };
84
+ readonly Delete: {
85
+ readonly key: "Delete";
86
+ readonly code: "Delete";
87
+ readonly keyCode: 46;
88
+ };
89
+ };
90
+ /**
91
+ * Creates a KeyboardEvent with proper initialization.
92
+ */
93
+ declare function createKeyboardEvent(type: "keydown" | "keyup" | "keypress", init: KeyboardEventInit): KeyboardEvent;
94
+ /**
95
+ * Simulates a key press (keydown + keyup) on an element.
96
+ */
97
+ declare function pressKey(element: Element, keyOrInit: keyof typeof Keys | KeyboardEventInit): void;
98
+ /**
99
+ * Simulates a keydown event on an element.
100
+ */
101
+ declare function keyDown(element: Element, keyOrInit: keyof typeof Keys | KeyboardEventInit): void;
102
+ /**
103
+ * Simulates a keyup event on an element.
104
+ */
105
+ declare function keyUp(element: Element, keyOrInit: keyof typeof Keys | KeyboardEventInit): void;
106
+ /**
107
+ * Simulates typing text character by character.
108
+ */
109
+ declare function typeText(element: Element, text: string): void;
110
+ /**
111
+ * Simulates pressing Tab to move focus.
112
+ */
113
+ declare function tab(options?: {
114
+ shiftKey?: boolean;
115
+ }): void;
116
+ /**
117
+ * Simulates pressing Enter key.
118
+ */
119
+ declare function enter(element: Element): void;
120
+ /**
121
+ * Simulates pressing Space key.
122
+ */
123
+ declare function space(element: Element): void;
124
+ /**
125
+ * Simulates pressing Escape key.
126
+ */
127
+ declare function pressEscape(element: Element): void;
128
+ /**
129
+ * Simulates arrow key navigation.
130
+ */
131
+ declare function arrowUp(element: Element): void;
132
+ declare function arrowDown(element: Element): void;
133
+ declare function arrowLeft(element: Element): void;
134
+ declare function arrowRight(element: Element): void;
135
+
136
+ /**
137
+ * ARIA assertion helpers for cross-framework testing.
138
+ */
139
+ interface AriaRole {
140
+ role: string;
141
+ element: Element;
142
+ }
143
+ /**
144
+ * Gets the effective role of an element (explicit or implicit).
145
+ */
146
+ declare function getRole(element: Element): string | null;
147
+ /**
148
+ * Asserts that an element has the specified role.
149
+ */
150
+ declare function hasRole(element: Element, expectedRole: string): boolean;
151
+ /**
152
+ * Gets an ARIA attribute value from an element.
153
+ */
154
+ declare function getAriaAttribute(element: Element, attribute: string): string | null;
155
+ /**
156
+ * Asserts that an element has the specified ARIA attribute with the expected value.
157
+ */
158
+ declare function hasAriaAttribute(element: Element, attribute: string, expectedValue?: string): boolean;
159
+ /**
160
+ * Checks if an element is expanded (aria-expanded="true").
161
+ */
162
+ declare function isExpanded(element: Element): boolean;
163
+ /**
164
+ * Checks if an element is collapsed (aria-expanded="false").
165
+ */
166
+ declare function isCollapsed(element: Element): boolean;
167
+ /**
168
+ * Checks if an element is selected (aria-selected="true").
169
+ */
170
+ declare function isSelected(element: Element): boolean;
171
+ /**
172
+ * Checks if an element is checked (aria-checked="true").
173
+ */
174
+ declare function isChecked(element: Element): boolean;
175
+ /**
176
+ * Checks if an element is disabled (aria-disabled="true" or disabled attribute).
177
+ */
178
+ declare function isDisabled(element: Element): boolean;
179
+ /**
180
+ * Checks if an element is hidden from accessibility tree.
181
+ */
182
+ declare function isHidden(element: Element): boolean;
183
+ /**
184
+ * Checks if an element has a valid accessible name.
185
+ */
186
+ declare function hasAccessibleName(element: Element): boolean;
187
+ /**
188
+ * Gets the accessible name of an element.
189
+ */
190
+ declare function getAccessibleName(element: Element): string;
191
+ /**
192
+ * Queries elements by role.
193
+ */
194
+ declare function queryByRole(container: Element, role: string): Element[];
195
+ /**
196
+ * Gets all focusable elements within a container.
197
+ */
198
+ declare function getFocusableElements(container: Element): Element[];
199
+
200
+ /**
201
+ * Component wrapper abstraction for cross-framework testing.
202
+ */
203
+ /**
204
+ * Component wrapper interface for framework-agnostic testing.
205
+ */
206
+ interface ComponentWrapper {
207
+ /** The root element of the component */
208
+ element: Element;
209
+ /** Query for a single element within the component */
210
+ query(selector: string): Element | null;
211
+ /** Query for all elements matching selector */
212
+ queryAll(selector: string): Element[];
213
+ /** Get an attribute value from the root element */
214
+ getAttribute(name: string): string | null;
215
+ /** Check if root element has an attribute */
216
+ hasAttribute(name: string): boolean;
217
+ /** Get text content of the component */
218
+ getText(): string;
219
+ /** Focus the component */
220
+ focus(): void;
221
+ /** Click the component */
222
+ click(): void;
223
+ /** Wait for the component to update */
224
+ waitForUpdate(): Promise<void>;
225
+ /** Cleanup the component */
226
+ destroy(): void;
227
+ }
228
+ /**
229
+ * Creates a wrapper for a Web Component element.
230
+ */
231
+ declare function wrapElement(element: Element): ComponentWrapper;
232
+ /**
233
+ * Creates a Web Component in a test container and returns a wrapper.
234
+ */
235
+ declare function createComponent<T extends Element>(tagName: string, attributes?: Record<string, string>, innerHTML?: string): Promise<ComponentWrapper & {
236
+ component: T;
237
+ }>;
238
+ /**
239
+ * Cleans up all test components.
240
+ */
241
+ declare function cleanupComponents(): void;
242
+ /**
243
+ * Renders HTML content and returns the first child element wrapped.
244
+ */
245
+ declare function renderHTML(html: string): Promise<ComponentWrapper>;
246
+
247
+ /**
248
+ * Shared button keyboard activation tests.
249
+ *
250
+ * These tests can run against both Web Component and React implementations
251
+ * to ensure consistent behavior across frameworks.
252
+ */
253
+ interface ButtonTestContext {
254
+ /** Creates a button element and returns it */
255
+ createButton: (props?: {
256
+ disabled?: boolean;
257
+ loading?: boolean;
258
+ /** Callback for activation (click for WC, onPress for React) */
259
+ onActivate?: () => void;
260
+ }) => Promise<HTMLElement>;
261
+ /** Cleans up the button element */
262
+ cleanup: () => void;
263
+ /** Gets the clickable element within the button (may be the button itself or an inner element) */
264
+ getClickableElement: (button: HTMLElement) => HTMLElement;
265
+ /** Test assertion functions (from test framework) */
266
+ expect: (value: unknown) => {
267
+ toBe: (expected: unknown) => void;
268
+ toHaveBeenCalled: () => void;
269
+ toHaveBeenCalledTimes: (times: number) => void;
270
+ not: {
271
+ toHaveBeenCalled: () => void;
272
+ };
273
+ };
274
+ /** Creates a mock function */
275
+ createMockFn: () => {
276
+ (): void;
277
+ mock: {
278
+ calls: unknown[][];
279
+ };
280
+ };
281
+ /**
282
+ * Whether keyboard events trigger the activation callback directly
283
+ * (true for React with onPress) or trigger click events (false for WC)
284
+ */
285
+ keyboardTriggersActivation?: boolean;
286
+ /**
287
+ * Whether Space key activates the button (native buttons activate on Space keyup).
288
+ * Some behavior implementations may not handle Space activation.
289
+ * Default: true (tests Space key activation)
290
+ */
291
+ spaceKeyActivates?: boolean;
292
+ }
293
+ /**
294
+ * Runs shared button keyboard activation tests.
295
+ *
296
+ * @example
297
+ * ```ts
298
+ * // In WC test file
299
+ * import { runButtonKeyboardTests } from '@hypoth-ui/test-utils/shared-tests/button';
300
+ *
301
+ * describe('ds-button keyboard', () => {
302
+ * runButtonKeyboardTests({
303
+ * createButton: async (props) => {
304
+ * const button = document.createElement('ds-button');
305
+ * if (props?.disabled) button.disabled = true;
306
+ * document.body.appendChild(button);
307
+ * await button.updateComplete;
308
+ * return button;
309
+ * },
310
+ * cleanup: () => document.body.innerHTML = '',
311
+ * getClickableElement: (button) => button.querySelector('button')!,
312
+ * expect: expect,
313
+ * createMockFn: vi.fn,
314
+ * });
315
+ * });
316
+ * ```
317
+ */
318
+ declare function runButtonKeyboardTests(context: ButtonTestContext, testFn: (name: string, fn: () => void | Promise<void>) => void): void;
319
+ /**
320
+ * Creates test context for Web Component button testing.
321
+ */
322
+ declare function createWCButtonTestContext(expectFn: any, mockFn: any): Omit<ButtonTestContext, "createButton" | "cleanup">;
323
+
324
+ export { type AriaRole, type ButtonTestContext, type ComponentWrapper, type KeyboardEventInit, Keys, arrowDown, arrowLeft, arrowRight, arrowUp, cleanupComponents, createComponent, createKeyboardEvent, createWCButtonTestContext, enter, getAccessibleName, getAriaAttribute, getFocusableElements, getRole, hasAccessibleName, hasAriaAttribute, hasRole, isChecked, isCollapsed, isDisabled, isExpanded, isHidden, isSelected, keyDown, keyUp, pressEscape, pressKey, queryByRole, renderHTML, runButtonKeyboardTests, space, tab, typeText, wrapElement };
package/dist/index.js ADDED
@@ -0,0 +1,483 @@
1
+ // src/keyboard.ts
2
+ var Keys = {
3
+ Enter: { key: "Enter", code: "Enter", keyCode: 13 },
4
+ Space: { key: " ", code: "Space", keyCode: 32 },
5
+ Escape: { key: "Escape", code: "Escape", keyCode: 27 },
6
+ Tab: { key: "Tab", code: "Tab", keyCode: 9 },
7
+ ArrowUp: { key: "ArrowUp", code: "ArrowUp", keyCode: 38 },
8
+ ArrowDown: { key: "ArrowDown", code: "ArrowDown", keyCode: 40 },
9
+ ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft", keyCode: 37 },
10
+ ArrowRight: { key: "ArrowRight", code: "ArrowRight", keyCode: 39 },
11
+ Home: { key: "Home", code: "Home", keyCode: 36 },
12
+ End: { key: "End", code: "End", keyCode: 35 },
13
+ PageUp: { key: "PageUp", code: "PageUp", keyCode: 33 },
14
+ PageDown: { key: "PageDown", code: "PageDown", keyCode: 34 },
15
+ Backspace: { key: "Backspace", code: "Backspace", keyCode: 8 },
16
+ Delete: { key: "Delete", code: "Delete", keyCode: 46 }
17
+ };
18
+ function createKeyboardEvent(type, init) {
19
+ return new KeyboardEvent(type, {
20
+ key: init.key,
21
+ code: init.code ?? init.key,
22
+ keyCode: init.keyCode,
23
+ shiftKey: init.shiftKey ?? false,
24
+ ctrlKey: init.ctrlKey ?? false,
25
+ altKey: init.altKey ?? false,
26
+ metaKey: init.metaKey ?? false,
27
+ bubbles: init.bubbles ?? true,
28
+ cancelable: init.cancelable ?? true
29
+ });
30
+ }
31
+ function pressKey(element, keyOrInit) {
32
+ const init = typeof keyOrInit === "string" ? Keys[keyOrInit] : keyOrInit;
33
+ const keydownEvent = createKeyboardEvent("keydown", init);
34
+ element.dispatchEvent(keydownEvent);
35
+ const keyupEvent = createKeyboardEvent("keyup", init);
36
+ element.dispatchEvent(keyupEvent);
37
+ }
38
+ function keyDown(element, keyOrInit) {
39
+ const init = typeof keyOrInit === "string" ? Keys[keyOrInit] : keyOrInit;
40
+ const event = createKeyboardEvent("keydown", init);
41
+ element.dispatchEvent(event);
42
+ }
43
+ function keyUp(element, keyOrInit) {
44
+ const init = typeof keyOrInit === "string" ? Keys[keyOrInit] : keyOrInit;
45
+ const event = createKeyboardEvent("keyup", init);
46
+ element.dispatchEvent(event);
47
+ }
48
+ function typeText(element, text) {
49
+ for (const char of text) {
50
+ const init = {
51
+ key: char,
52
+ code: `Key${char.toUpperCase()}`,
53
+ keyCode: char.charCodeAt(0)
54
+ };
55
+ pressKey(element, init);
56
+ }
57
+ }
58
+ function tab(options = {}) {
59
+ const activeElement = document.activeElement;
60
+ if (activeElement) {
61
+ pressKey(activeElement, { ...Keys.Tab, shiftKey: options.shiftKey ?? false });
62
+ }
63
+ }
64
+ function enter(element) {
65
+ pressKey(element, "Enter");
66
+ }
67
+ function space(element) {
68
+ pressKey(element, "Space");
69
+ }
70
+ function pressEscape(element) {
71
+ pressKey(element, "Escape");
72
+ }
73
+ function arrowUp(element) {
74
+ pressKey(element, "ArrowUp");
75
+ }
76
+ function arrowDown(element) {
77
+ pressKey(element, "ArrowDown");
78
+ }
79
+ function arrowLeft(element) {
80
+ pressKey(element, "ArrowLeft");
81
+ }
82
+ function arrowRight(element) {
83
+ pressKey(element, "ArrowRight");
84
+ }
85
+
86
+ // src/aria.ts
87
+ function getRole(element) {
88
+ const explicitRole = element.getAttribute("role");
89
+ if (explicitRole) {
90
+ return explicitRole;
91
+ }
92
+ const implicitRoles = {
93
+ BUTTON: "button",
94
+ A: "link",
95
+ INPUT: getInputRole(element),
96
+ SELECT: "combobox",
97
+ TEXTAREA: "textbox",
98
+ DIALOG: "dialog",
99
+ ARTICLE: "article",
100
+ ASIDE: "complementary",
101
+ FOOTER: "contentinfo",
102
+ HEADER: "banner",
103
+ MAIN: "main",
104
+ NAV: "navigation",
105
+ SECTION: "region",
106
+ FORM: "form",
107
+ IMG: "img",
108
+ UL: "list",
109
+ OL: "list",
110
+ LI: "listitem",
111
+ TABLE: "table",
112
+ THEAD: "rowgroup",
113
+ TBODY: "rowgroup",
114
+ TR: "row",
115
+ TH: "columnheader",
116
+ TD: "cell",
117
+ H1: "heading",
118
+ H2: "heading",
119
+ H3: "heading",
120
+ H4: "heading",
121
+ H5: "heading",
122
+ H6: "heading"
123
+ };
124
+ return implicitRoles[element.tagName] ?? null;
125
+ }
126
+ function getInputRole(input) {
127
+ const type = input.type?.toLowerCase() ?? "text";
128
+ const roleMap = {
129
+ button: "button",
130
+ checkbox: "checkbox",
131
+ email: "textbox",
132
+ number: "spinbutton",
133
+ password: "textbox",
134
+ radio: "radio",
135
+ range: "slider",
136
+ search: "searchbox",
137
+ submit: "button",
138
+ tel: "textbox",
139
+ text: "textbox",
140
+ url: "textbox"
141
+ };
142
+ return roleMap[type] ?? "textbox";
143
+ }
144
+ function hasRole(element, expectedRole) {
145
+ const actualRole = getRole(element);
146
+ return actualRole === expectedRole;
147
+ }
148
+ function getAriaAttribute(element, attribute) {
149
+ if (attribute.startsWith("aria-")) {
150
+ return element.getAttribute(attribute);
151
+ }
152
+ return element.getAttribute(`aria-${attribute}`);
153
+ }
154
+ function hasAriaAttribute(element, attribute, expectedValue) {
155
+ const value = getAriaAttribute(element, attribute);
156
+ if (expectedValue === void 0) {
157
+ return value !== null;
158
+ }
159
+ return value === expectedValue;
160
+ }
161
+ function isExpanded(element) {
162
+ return element.getAttribute("aria-expanded") === "true";
163
+ }
164
+ function isCollapsed(element) {
165
+ return element.getAttribute("aria-expanded") === "false";
166
+ }
167
+ function isSelected(element) {
168
+ return element.getAttribute("aria-selected") === "true";
169
+ }
170
+ function isChecked(element) {
171
+ return element.getAttribute("aria-checked") === "true";
172
+ }
173
+ function isDisabled(element) {
174
+ return element.getAttribute("aria-disabled") === "true" || element.hasAttribute("disabled");
175
+ }
176
+ function isHidden(element) {
177
+ return element.getAttribute("aria-hidden") === "true" || element.hasAttribute("hidden") || element.style?.display === "none";
178
+ }
179
+ function hasAccessibleName(element) {
180
+ if (element.getAttribute("aria-label")) {
181
+ return true;
182
+ }
183
+ const labelledBy = element.getAttribute("aria-labelledby");
184
+ if (labelledBy) {
185
+ const labelElement = document.getElementById(labelledBy);
186
+ if (labelElement?.textContent?.trim()) {
187
+ return true;
188
+ }
189
+ }
190
+ const id = element.id;
191
+ if (id) {
192
+ const label = document.querySelector(`label[for="${id}"]`);
193
+ if (label?.textContent?.trim()) {
194
+ return true;
195
+ }
196
+ }
197
+ const role = getRole(element);
198
+ if (role === "button" || role === "link") {
199
+ return Boolean(element.textContent?.trim());
200
+ }
201
+ return false;
202
+ }
203
+ function getAccessibleName(element) {
204
+ const ariaLabel = element.getAttribute("aria-label");
205
+ if (ariaLabel) {
206
+ return ariaLabel;
207
+ }
208
+ const labelledBy = element.getAttribute("aria-labelledby");
209
+ if (labelledBy) {
210
+ const ids = labelledBy.split(/\s+/);
211
+ const labels = ids.map((id2) => document.getElementById(id2)?.textContent?.trim()).filter(Boolean);
212
+ if (labels.length > 0) {
213
+ return labels.join(" ");
214
+ }
215
+ }
216
+ const id = element.id;
217
+ if (id) {
218
+ const label = document.querySelector(`label[for="${id}"]`);
219
+ if (label?.textContent?.trim()) {
220
+ return label.textContent.trim();
221
+ }
222
+ }
223
+ return element.textContent?.trim() ?? "";
224
+ }
225
+ function queryByRole(container, role) {
226
+ const elements = container.querySelectorAll(`[role="${role}"]`);
227
+ const result = [];
228
+ elements.forEach((el) => result.push(el));
229
+ const implicitSelectors = {
230
+ button: "button, input[type='button'], input[type='submit']",
231
+ checkbox: "input[type='checkbox']",
232
+ radio: "input[type='radio']",
233
+ textbox: "input:not([type]), input[type='text'], input[type='email'], input[type='password'], input[type='tel'], input[type='url'], textarea",
234
+ link: "a[href]",
235
+ listbox: "select",
236
+ option: "option",
237
+ dialog: "dialog",
238
+ heading: "h1, h2, h3, h4, h5, h6",
239
+ list: "ul, ol",
240
+ listitem: "li"
241
+ };
242
+ const selector = implicitSelectors[role];
243
+ if (selector) {
244
+ container.querySelectorAll(selector).forEach((el) => {
245
+ if (!el.hasAttribute("role") && !result.includes(el)) {
246
+ result.push(el);
247
+ }
248
+ });
249
+ }
250
+ return result;
251
+ }
252
+ function getFocusableElements(container) {
253
+ const selector = [
254
+ "a[href]",
255
+ "button:not([disabled])",
256
+ "input:not([disabled])",
257
+ "select:not([disabled])",
258
+ "textarea:not([disabled])",
259
+ "[tabindex]:not([tabindex='-1'])",
260
+ "[contenteditable='true']"
261
+ ].join(", ");
262
+ return Array.from(container.querySelectorAll(selector)).filter((el) => {
263
+ return !isHidden(el) && !isDisabled(el);
264
+ });
265
+ }
266
+
267
+ // src/component.ts
268
+ function wrapElement(element) {
269
+ return {
270
+ element,
271
+ query(selector) {
272
+ return element.querySelector(selector);
273
+ },
274
+ queryAll(selector) {
275
+ return Array.from(element.querySelectorAll(selector));
276
+ },
277
+ getAttribute(name) {
278
+ return element.getAttribute(name);
279
+ },
280
+ hasAttribute(name) {
281
+ return element.hasAttribute(name);
282
+ },
283
+ getText() {
284
+ return element.textContent?.trim() ?? "";
285
+ },
286
+ focus() {
287
+ element.focus();
288
+ },
289
+ click() {
290
+ element.click();
291
+ },
292
+ async waitForUpdate() {
293
+ if ("updateComplete" in element) {
294
+ await element.updateComplete;
295
+ }
296
+ await new Promise((resolve) => requestAnimationFrame(resolve));
297
+ },
298
+ destroy() {
299
+ element.remove();
300
+ }
301
+ };
302
+ }
303
+ async function createComponent(tagName, attributes, innerHTML) {
304
+ let container = document.getElementById("test-container");
305
+ if (!container) {
306
+ container = document.createElement("div");
307
+ container.id = "test-container";
308
+ document.body.appendChild(container);
309
+ }
310
+ const element = document.createElement(tagName);
311
+ if (attributes) {
312
+ for (const [name, value] of Object.entries(attributes)) {
313
+ element.setAttribute(name, value);
314
+ }
315
+ }
316
+ if (innerHTML) {
317
+ element.innerHTML = innerHTML;
318
+ }
319
+ container.appendChild(element);
320
+ await customElements.whenDefined(tagName);
321
+ if ("updateComplete" in element) {
322
+ await element.updateComplete;
323
+ }
324
+ const wrapper = wrapElement(element);
325
+ return {
326
+ ...wrapper,
327
+ component: element
328
+ };
329
+ }
330
+ function cleanupComponents() {
331
+ const container = document.getElementById("test-container");
332
+ if (container) {
333
+ container.innerHTML = "";
334
+ }
335
+ }
336
+ async function renderHTML(html) {
337
+ let container = document.getElementById("test-container");
338
+ if (!container) {
339
+ container = document.createElement("div");
340
+ container.id = "test-container";
341
+ document.body.appendChild(container);
342
+ }
343
+ container.innerHTML = html;
344
+ const element = container.firstElementChild;
345
+ if (!element) {
346
+ throw new Error("No element rendered from HTML");
347
+ }
348
+ const customElements2 = container.querySelectorAll(":not(:defined)");
349
+ await Promise.all(
350
+ Array.from(customElements2).map(
351
+ (el) => window.customElements.whenDefined(el.tagName.toLowerCase())
352
+ )
353
+ );
354
+ await new Promise((resolve) => requestAnimationFrame(resolve));
355
+ return wrapElement(element);
356
+ }
357
+
358
+ // src/shared-tests/button.ts
359
+ function runButtonKeyboardTests(context, testFn) {
360
+ const {
361
+ createButton,
362
+ cleanup,
363
+ getClickableElement,
364
+ expect,
365
+ createMockFn,
366
+ keyboardTriggersActivation,
367
+ spaceKeyActivates = true
368
+ } = context;
369
+ testFn("should trigger activation on Enter key", async () => {
370
+ const activationHandler = createMockFn();
371
+ const button = await createButton(
372
+ keyboardTriggersActivation ? { onActivate: activationHandler } : void 0
373
+ );
374
+ const clickable = getClickableElement(button);
375
+ if (!keyboardTriggersActivation) {
376
+ button.addEventListener("click", activationHandler);
377
+ }
378
+ pressKey(clickable, "Enter");
379
+ expect(activationHandler).toHaveBeenCalled();
380
+ cleanup();
381
+ });
382
+ if (spaceKeyActivates) {
383
+ testFn("should trigger activation on Space key", async () => {
384
+ const activationHandler = createMockFn();
385
+ const button = await createButton(
386
+ keyboardTriggersActivation ? { onActivate: activationHandler } : void 0
387
+ );
388
+ const clickable = getClickableElement(button);
389
+ if (!keyboardTriggersActivation) {
390
+ button.addEventListener("click", activationHandler);
391
+ }
392
+ pressKey(clickable, "Space");
393
+ expect(activationHandler).toHaveBeenCalled();
394
+ cleanup();
395
+ });
396
+ }
397
+ testFn("should not trigger activation on Enter when disabled", async () => {
398
+ const activationHandler = createMockFn();
399
+ const button = await createButton(
400
+ keyboardTriggersActivation ? { disabled: true, onActivate: activationHandler } : { disabled: true }
401
+ );
402
+ const clickable = getClickableElement(button);
403
+ if (!keyboardTriggersActivation) {
404
+ button.addEventListener("click", activationHandler);
405
+ }
406
+ pressKey(clickable, "Enter");
407
+ expect(activationHandler).not.toHaveBeenCalled();
408
+ cleanup();
409
+ });
410
+ if (spaceKeyActivates) {
411
+ testFn("should not trigger activation on Space when disabled", async () => {
412
+ const activationHandler = createMockFn();
413
+ const button = await createButton(
414
+ keyboardTriggersActivation ? { disabled: true, onActivate: activationHandler } : { disabled: true }
415
+ );
416
+ const clickable = getClickableElement(button);
417
+ if (!keyboardTriggersActivation) {
418
+ button.addEventListener("click", activationHandler);
419
+ }
420
+ pressKey(clickable, "Space");
421
+ expect(activationHandler).not.toHaveBeenCalled();
422
+ cleanup();
423
+ });
424
+ }
425
+ testFn("should have button role", async () => {
426
+ const button = await createButton();
427
+ const clickable = getClickableElement(button);
428
+ expect(hasRole(clickable, "button")).toBe(true);
429
+ cleanup();
430
+ });
431
+ testFn("should indicate disabled state via aria-disabled", async () => {
432
+ const button = await createButton({ disabled: true });
433
+ const clickable = getClickableElement(button);
434
+ expect(isDisabled(clickable)).toBe(true);
435
+ cleanup();
436
+ });
437
+ }
438
+ function createWCButtonTestContext(expectFn, mockFn) {
439
+ return {
440
+ getClickableElement: (button) => {
441
+ return button.querySelector("button") ?? button;
442
+ },
443
+ expect: expectFn,
444
+ createMockFn: mockFn
445
+ };
446
+ }
447
+ export {
448
+ Keys,
449
+ arrowDown,
450
+ arrowLeft,
451
+ arrowRight,
452
+ arrowUp,
453
+ cleanupComponents,
454
+ createComponent,
455
+ createKeyboardEvent,
456
+ createWCButtonTestContext,
457
+ enter,
458
+ getAccessibleName,
459
+ getAriaAttribute,
460
+ getFocusableElements,
461
+ getRole,
462
+ hasAccessibleName,
463
+ hasAriaAttribute,
464
+ hasRole,
465
+ isChecked,
466
+ isCollapsed,
467
+ isDisabled,
468
+ isExpanded,
469
+ isHidden,
470
+ isSelected,
471
+ keyDown,
472
+ keyUp,
473
+ pressEscape,
474
+ pressKey,
475
+ queryByRole,
476
+ renderHTML,
477
+ runButtonKeyboardTests,
478
+ space,
479
+ tab,
480
+ typeText,
481
+ wrapElement
482
+ };
483
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/keyboard.ts","../src/aria.ts","../src/component.ts","../src/shared-tests/button.ts"],"sourcesContent":["/**\n * Keyboard simulation helpers for cross-framework testing.\n */\n\nexport interface KeyboardEventInit {\n key: string;\n code?: string;\n keyCode?: number;\n shiftKey?: boolean;\n ctrlKey?: boolean;\n altKey?: boolean;\n metaKey?: boolean;\n bubbles?: boolean;\n cancelable?: boolean;\n}\n\n/**\n * Common key mappings for keyboard simulation.\n */\nexport const Keys = {\n Enter: { key: \"Enter\", code: \"Enter\", keyCode: 13 },\n Space: { key: \" \", code: \"Space\", keyCode: 32 },\n Escape: { key: \"Escape\", code: \"Escape\", keyCode: 27 },\n Tab: { key: \"Tab\", code: \"Tab\", keyCode: 9 },\n ArrowUp: { key: \"ArrowUp\", code: \"ArrowUp\", keyCode: 38 },\n ArrowDown: { key: \"ArrowDown\", code: \"ArrowDown\", keyCode: 40 },\n ArrowLeft: { key: \"ArrowLeft\", code: \"ArrowLeft\", keyCode: 37 },\n ArrowRight: { key: \"ArrowRight\", code: \"ArrowRight\", keyCode: 39 },\n Home: { key: \"Home\", code: \"Home\", keyCode: 36 },\n End: { key: \"End\", code: \"End\", keyCode: 35 },\n PageUp: { key: \"PageUp\", code: \"PageUp\", keyCode: 33 },\n PageDown: { key: \"PageDown\", code: \"PageDown\", keyCode: 34 },\n Backspace: { key: \"Backspace\", code: \"Backspace\", keyCode: 8 },\n Delete: { key: \"Delete\", code: \"Delete\", keyCode: 46 },\n} as const;\n\n/**\n * Creates a KeyboardEvent with proper initialization.\n */\nexport function createKeyboardEvent(\n type: \"keydown\" | \"keyup\" | \"keypress\",\n init: KeyboardEventInit\n): KeyboardEvent {\n return new KeyboardEvent(type, {\n key: init.key,\n code: init.code ?? init.key,\n keyCode: init.keyCode,\n shiftKey: init.shiftKey ?? false,\n ctrlKey: init.ctrlKey ?? false,\n altKey: init.altKey ?? false,\n metaKey: init.metaKey ?? false,\n bubbles: init.bubbles ?? true,\n cancelable: init.cancelable ?? true,\n });\n}\n\n/**\n * Simulates a key press (keydown + keyup) on an element.\n */\nexport function pressKey(element: Element, keyOrInit: keyof typeof Keys | KeyboardEventInit): void {\n const init = typeof keyOrInit === \"string\" ? Keys[keyOrInit] : keyOrInit;\n\n const keydownEvent = createKeyboardEvent(\"keydown\", init);\n element.dispatchEvent(keydownEvent);\n\n const keyupEvent = createKeyboardEvent(\"keyup\", init);\n element.dispatchEvent(keyupEvent);\n}\n\n/**\n * Simulates a keydown event on an element.\n */\nexport function keyDown(element: Element, keyOrInit: keyof typeof Keys | KeyboardEventInit): void {\n const init = typeof keyOrInit === \"string\" ? Keys[keyOrInit] : keyOrInit;\n const event = createKeyboardEvent(\"keydown\", init);\n element.dispatchEvent(event);\n}\n\n/**\n * Simulates a keyup event on an element.\n */\nexport function keyUp(element: Element, keyOrInit: keyof typeof Keys | KeyboardEventInit): void {\n const init = typeof keyOrInit === \"string\" ? Keys[keyOrInit] : keyOrInit;\n const event = createKeyboardEvent(\"keyup\", init);\n element.dispatchEvent(event);\n}\n\n/**\n * Simulates typing text character by character.\n */\nexport function typeText(element: Element, text: string): void {\n for (const char of text) {\n const init: KeyboardEventInit = {\n key: char,\n code: `Key${char.toUpperCase()}`,\n keyCode: char.charCodeAt(0),\n };\n pressKey(element, init);\n }\n}\n\n/**\n * Simulates pressing Tab to move focus.\n */\nexport function tab(options: { shiftKey?: boolean } = {}): void {\n const activeElement = document.activeElement;\n if (activeElement) {\n pressKey(activeElement, { ...Keys.Tab, shiftKey: options.shiftKey ?? false });\n }\n}\n\n/**\n * Simulates pressing Enter key.\n */\nexport function enter(element: Element): void {\n pressKey(element, \"Enter\");\n}\n\n/**\n * Simulates pressing Space key.\n */\nexport function space(element: Element): void {\n pressKey(element, \"Space\");\n}\n\n/**\n * Simulates pressing Escape key.\n */\nexport function pressEscape(element: Element): void {\n pressKey(element, \"Escape\");\n}\n\n/**\n * Simulates arrow key navigation.\n */\nexport function arrowUp(element: Element): void {\n pressKey(element, \"ArrowUp\");\n}\n\nexport function arrowDown(element: Element): void {\n pressKey(element, \"ArrowDown\");\n}\n\nexport function arrowLeft(element: Element): void {\n pressKey(element, \"ArrowLeft\");\n}\n\nexport function arrowRight(element: Element): void {\n pressKey(element, \"ArrowRight\");\n}\n","/**\n * ARIA assertion helpers for cross-framework testing.\n */\n\nexport interface AriaRole {\n role: string;\n element: Element;\n}\n\n/**\n * Gets the effective role of an element (explicit or implicit).\n */\nexport function getRole(element: Element): string | null {\n // Check explicit role\n const explicitRole = element.getAttribute(\"role\");\n if (explicitRole) {\n return explicitRole;\n }\n\n // Check implicit role from tag name\n const implicitRoles: Record<string, string> = {\n BUTTON: \"button\",\n A: \"link\",\n INPUT: getInputRole(element as HTMLInputElement),\n SELECT: \"combobox\",\n TEXTAREA: \"textbox\",\n DIALOG: \"dialog\",\n ARTICLE: \"article\",\n ASIDE: \"complementary\",\n FOOTER: \"contentinfo\",\n HEADER: \"banner\",\n MAIN: \"main\",\n NAV: \"navigation\",\n SECTION: \"region\",\n FORM: \"form\",\n IMG: \"img\",\n UL: \"list\",\n OL: \"list\",\n LI: \"listitem\",\n TABLE: \"table\",\n THEAD: \"rowgroup\",\n TBODY: \"rowgroup\",\n TR: \"row\",\n TH: \"columnheader\",\n TD: \"cell\",\n H1: \"heading\",\n H2: \"heading\",\n H3: \"heading\",\n H4: \"heading\",\n H5: \"heading\",\n H6: \"heading\",\n };\n\n return implicitRoles[element.tagName] ?? null;\n}\n\nfunction getInputRole(input: HTMLInputElement): string {\n const type = input.type?.toLowerCase() ?? \"text\";\n const roleMap: Record<string, string> = {\n button: \"button\",\n checkbox: \"checkbox\",\n email: \"textbox\",\n number: \"spinbutton\",\n password: \"textbox\",\n radio: \"radio\",\n range: \"slider\",\n search: \"searchbox\",\n submit: \"button\",\n tel: \"textbox\",\n text: \"textbox\",\n url: \"textbox\",\n };\n return roleMap[type] ?? \"textbox\";\n}\n\n/**\n * Asserts that an element has the specified role.\n */\nexport function hasRole(element: Element, expectedRole: string): boolean {\n const actualRole = getRole(element);\n return actualRole === expectedRole;\n}\n\n/**\n * Gets an ARIA attribute value from an element.\n */\nexport function getAriaAttribute(element: Element, attribute: string): string | null {\n // Handle aria-* attributes\n if (attribute.startsWith(\"aria-\")) {\n return element.getAttribute(attribute);\n }\n return element.getAttribute(`aria-${attribute}`);\n}\n\n/**\n * Asserts that an element has the specified ARIA attribute with the expected value.\n */\nexport function hasAriaAttribute(element: Element, attribute: string, expectedValue?: string): boolean {\n const value = getAriaAttribute(element, attribute);\n if (expectedValue === undefined) {\n return value !== null;\n }\n return value === expectedValue;\n}\n\n/**\n * Checks if an element is expanded (aria-expanded=\"true\").\n */\nexport function isExpanded(element: Element): boolean {\n return element.getAttribute(\"aria-expanded\") === \"true\";\n}\n\n/**\n * Checks if an element is collapsed (aria-expanded=\"false\").\n */\nexport function isCollapsed(element: Element): boolean {\n return element.getAttribute(\"aria-expanded\") === \"false\";\n}\n\n/**\n * Checks if an element is selected (aria-selected=\"true\").\n */\nexport function isSelected(element: Element): boolean {\n return element.getAttribute(\"aria-selected\") === \"true\";\n}\n\n/**\n * Checks if an element is checked (aria-checked=\"true\").\n */\nexport function isChecked(element: Element): boolean {\n return element.getAttribute(\"aria-checked\") === \"true\";\n}\n\n/**\n * Checks if an element is disabled (aria-disabled=\"true\" or disabled attribute).\n */\nexport function isDisabled(element: Element): boolean {\n return (\n element.getAttribute(\"aria-disabled\") === \"true\" ||\n element.hasAttribute(\"disabled\")\n );\n}\n\n/**\n * Checks if an element is hidden from accessibility tree.\n */\nexport function isHidden(element: Element): boolean {\n return (\n element.getAttribute(\"aria-hidden\") === \"true\" ||\n element.hasAttribute(\"hidden\") ||\n (element as HTMLElement).style?.display === \"none\"\n );\n}\n\n/**\n * Checks if an element has a valid accessible name.\n */\nexport function hasAccessibleName(element: Element): boolean {\n // Check aria-label\n if (element.getAttribute(\"aria-label\")) {\n return true;\n }\n\n // Check aria-labelledby\n const labelledBy = element.getAttribute(\"aria-labelledby\");\n if (labelledBy) {\n const labelElement = document.getElementById(labelledBy);\n if (labelElement?.textContent?.trim()) {\n return true;\n }\n }\n\n // Check for associated label element\n const id = element.id;\n if (id) {\n const label = document.querySelector(`label[for=\"${id}\"]`);\n if (label?.textContent?.trim()) {\n return true;\n }\n }\n\n // Check text content for some roles\n const role = getRole(element);\n if (role === \"button\" || role === \"link\") {\n return Boolean((element as HTMLElement).textContent?.trim());\n }\n\n return false;\n}\n\n/**\n * Gets the accessible name of an element.\n */\nexport function getAccessibleName(element: Element): string {\n // Check aria-label first\n const ariaLabel = element.getAttribute(\"aria-label\");\n if (ariaLabel) {\n return ariaLabel;\n }\n\n // Check aria-labelledby\n const labelledBy = element.getAttribute(\"aria-labelledby\");\n if (labelledBy) {\n const ids = labelledBy.split(/\\s+/);\n const labels = ids\n .map((id) => document.getElementById(id)?.textContent?.trim())\n .filter(Boolean);\n if (labels.length > 0) {\n return labels.join(\" \");\n }\n }\n\n // Check for associated label element\n const id = element.id;\n if (id) {\n const label = document.querySelector(`label[for=\"${id}\"]`);\n if (label?.textContent?.trim()) {\n return label.textContent.trim();\n }\n }\n\n // Fall back to text content\n return (element as HTMLElement).textContent?.trim() ?? \"\";\n}\n\n/**\n * Queries elements by role.\n */\nexport function queryByRole(container: Element, role: string): Element[] {\n const elements = container.querySelectorAll(`[role=\"${role}\"]`);\n const result: Element[] = [];\n\n // Add explicit role matches\n elements.forEach((el) => result.push(el));\n\n // Add implicit role matches\n const implicitSelectors: Record<string, string> = {\n button: \"button, input[type='button'], input[type='submit']\",\n checkbox: \"input[type='checkbox']\",\n radio: \"input[type='radio']\",\n textbox: \"input:not([type]), input[type='text'], input[type='email'], input[type='password'], input[type='tel'], input[type='url'], textarea\",\n link: \"a[href]\",\n listbox: \"select\",\n option: \"option\",\n dialog: \"dialog\",\n heading: \"h1, h2, h3, h4, h5, h6\",\n list: \"ul, ol\",\n listitem: \"li\",\n };\n\n const selector = implicitSelectors[role];\n if (selector) {\n container.querySelectorAll(selector).forEach((el) => {\n if (!el.hasAttribute(\"role\") && !result.includes(el)) {\n result.push(el);\n }\n });\n }\n\n return result;\n}\n\n/**\n * Gets all focusable elements within a container.\n */\nexport function getFocusableElements(container: Element): Element[] {\n const selector = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n \"[contenteditable='true']\",\n ].join(\", \");\n\n return Array.from(container.querySelectorAll(selector)).filter((el) => {\n return !isHidden(el) && !isDisabled(el);\n });\n}\n","/**\n * Component wrapper abstraction for cross-framework testing.\n */\n\n/**\n * Component wrapper interface for framework-agnostic testing.\n */\nexport interface ComponentWrapper {\n /** The root element of the component */\n element: Element;\n\n /** Query for a single element within the component */\n query(selector: string): Element | null;\n\n /** Query for all elements matching selector */\n queryAll(selector: string): Element[];\n\n /** Get an attribute value from the root element */\n getAttribute(name: string): string | null;\n\n /** Check if root element has an attribute */\n hasAttribute(name: string): boolean;\n\n /** Get text content of the component */\n getText(): string;\n\n /** Focus the component */\n focus(): void;\n\n /** Click the component */\n click(): void;\n\n /** Wait for the component to update */\n waitForUpdate(): Promise<void>;\n\n /** Cleanup the component */\n destroy(): void;\n}\n\n/**\n * Creates a wrapper for a Web Component element.\n */\nexport function wrapElement(element: Element): ComponentWrapper {\n return {\n element,\n\n query(selector: string): Element | null {\n return element.querySelector(selector);\n },\n\n queryAll(selector: string): Element[] {\n return Array.from(element.querySelectorAll(selector));\n },\n\n getAttribute(name: string): string | null {\n return element.getAttribute(name);\n },\n\n hasAttribute(name: string): boolean {\n return element.hasAttribute(name);\n },\n\n getText(): string {\n return (element as HTMLElement).textContent?.trim() ?? \"\";\n },\n\n focus(): void {\n (element as HTMLElement).focus();\n },\n\n click(): void {\n (element as HTMLElement).click();\n },\n\n async waitForUpdate(): Promise<void> {\n // Wait for Lit updateComplete if available\n if (\"updateComplete\" in element) {\n await (element as { updateComplete: Promise<unknown> }).updateComplete;\n }\n // Additional frame for DOM updates\n await new Promise((resolve) => requestAnimationFrame(resolve));\n },\n\n destroy(): void {\n element.remove();\n },\n };\n}\n\n/**\n * Creates a Web Component in a test container and returns a wrapper.\n */\nexport async function createComponent<T extends Element>(\n tagName: string,\n attributes?: Record<string, string>,\n innerHTML?: string\n): Promise<ComponentWrapper & { component: T }> {\n // Create container if it doesn't exist\n let container = document.getElementById(\"test-container\");\n if (!container) {\n container = document.createElement(\"div\");\n container.id = \"test-container\";\n document.body.appendChild(container);\n }\n\n // Create the element\n const element = document.createElement(tagName) as unknown as T;\n\n // Set attributes\n if (attributes) {\n for (const [name, value] of Object.entries(attributes)) {\n element.setAttribute(name, value);\n }\n }\n\n // Set innerHTML\n if (innerHTML) {\n element.innerHTML = innerHTML;\n }\n\n // Append to container\n container.appendChild(element);\n\n // Wait for component to be ready\n await customElements.whenDefined(tagName);\n\n // Wait for initial render\n if (\"updateComplete\" in element) {\n await (element as { updateComplete: Promise<unknown> }).updateComplete;\n }\n\n const wrapper = wrapElement(element);\n\n return {\n ...wrapper,\n component: element,\n };\n}\n\n/**\n * Cleans up all test components.\n */\nexport function cleanupComponents(): void {\n const container = document.getElementById(\"test-container\");\n if (container) {\n container.innerHTML = \"\";\n }\n}\n\n/**\n * Renders HTML content and returns the first child element wrapped.\n */\nexport async function renderHTML(html: string): Promise<ComponentWrapper> {\n let container = document.getElementById(\"test-container\");\n if (!container) {\n container = document.createElement(\"div\");\n container.id = \"test-container\";\n document.body.appendChild(container);\n }\n\n container.innerHTML = html;\n\n const element = container.firstElementChild;\n if (!element) {\n throw new Error(\"No element rendered from HTML\");\n }\n\n // Wait for any custom elements to be defined\n const customElements = container.querySelectorAll(\":not(:defined)\");\n await Promise.all(\n Array.from(customElements).map((el) =>\n window.customElements.whenDefined(el.tagName.toLowerCase())\n )\n );\n\n // Wait for updates\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n return wrapElement(element);\n}\n","/**\n * Shared button keyboard activation tests.\n *\n * These tests can run against both Web Component and React implementations\n * to ensure consistent behavior across frameworks.\n */\n\nimport { pressKey } from \"../keyboard.js\";\nimport { hasRole, isDisabled } from \"../aria.js\";\n\nexport interface ButtonTestContext {\n /** Creates a button element and returns it */\n createButton: (props?: {\n disabled?: boolean;\n loading?: boolean;\n /** Callback for activation (click for WC, onPress for React) */\n onActivate?: () => void;\n }) => Promise<HTMLElement>;\n /** Cleans up the button element */\n cleanup: () => void;\n /** Gets the clickable element within the button (may be the button itself or an inner element) */\n getClickableElement: (button: HTMLElement) => HTMLElement;\n /** Test assertion functions (from test framework) */\n expect: (value: unknown) => {\n toBe: (expected: unknown) => void;\n toHaveBeenCalled: () => void;\n toHaveBeenCalledTimes: (times: number) => void;\n not: {\n toHaveBeenCalled: () => void;\n };\n };\n /** Creates a mock function */\n createMockFn: () => { (): void; mock: { calls: unknown[][] } };\n /**\n * Whether keyboard events trigger the activation callback directly\n * (true for React with onPress) or trigger click events (false for WC)\n */\n keyboardTriggersActivation?: boolean;\n /**\n * Whether Space key activates the button (native buttons activate on Space keyup).\n * Some behavior implementations may not handle Space activation.\n * Default: true (tests Space key activation)\n */\n spaceKeyActivates?: boolean;\n}\n\n/**\n * Runs shared button keyboard activation tests.\n *\n * @example\n * ```ts\n * // In WC test file\n * import { runButtonKeyboardTests } from '@hypoth-ui/test-utils/shared-tests/button';\n *\n * describe('ds-button keyboard', () => {\n * runButtonKeyboardTests({\n * createButton: async (props) => {\n * const button = document.createElement('ds-button');\n * if (props?.disabled) button.disabled = true;\n * document.body.appendChild(button);\n * await button.updateComplete;\n * return button;\n * },\n * cleanup: () => document.body.innerHTML = '',\n * getClickableElement: (button) => button.querySelector('button')!,\n * expect: expect,\n * createMockFn: vi.fn,\n * });\n * });\n * ```\n */\nexport function runButtonKeyboardTests(\n context: ButtonTestContext,\n testFn: (name: string, fn: () => void | Promise<void>) => void\n): void {\n const {\n createButton,\n cleanup,\n getClickableElement,\n expect,\n createMockFn,\n keyboardTriggersActivation,\n spaceKeyActivates = true,\n } = context;\n\n testFn(\"should trigger activation on Enter key\", async () => {\n const activationHandler = createMockFn();\n const button = await createButton(\n keyboardTriggersActivation ? { onActivate: activationHandler } : undefined\n );\n const clickable = getClickableElement(button);\n\n if (!keyboardTriggersActivation) {\n button.addEventListener(\"click\", activationHandler);\n }\n\n pressKey(clickable, \"Enter\");\n\n expect(activationHandler).toHaveBeenCalled();\n cleanup();\n });\n\n if (spaceKeyActivates) {\n testFn(\"should trigger activation on Space key\", async () => {\n const activationHandler = createMockFn();\n const button = await createButton(\n keyboardTriggersActivation ? { onActivate: activationHandler } : undefined\n );\n const clickable = getClickableElement(button);\n\n if (!keyboardTriggersActivation) {\n button.addEventListener(\"click\", activationHandler);\n }\n\n pressKey(clickable, \"Space\");\n\n expect(activationHandler).toHaveBeenCalled();\n cleanup();\n });\n }\n\n testFn(\"should not trigger activation on Enter when disabled\", async () => {\n const activationHandler = createMockFn();\n const button = await createButton(\n keyboardTriggersActivation\n ? { disabled: true, onActivate: activationHandler }\n : { disabled: true }\n );\n const clickable = getClickableElement(button);\n\n if (!keyboardTriggersActivation) {\n button.addEventListener(\"click\", activationHandler);\n }\n\n pressKey(clickable, \"Enter\");\n\n expect(activationHandler).not.toHaveBeenCalled();\n cleanup();\n });\n\n if (spaceKeyActivates) {\n testFn(\"should not trigger activation on Space when disabled\", async () => {\n const activationHandler = createMockFn();\n const button = await createButton(\n keyboardTriggersActivation\n ? { disabled: true, onActivate: activationHandler }\n : { disabled: true }\n );\n const clickable = getClickableElement(button);\n\n if (!keyboardTriggersActivation) {\n button.addEventListener(\"click\", activationHandler);\n }\n\n pressKey(clickable, \"Space\");\n\n expect(activationHandler).not.toHaveBeenCalled();\n cleanup();\n });\n }\n\n testFn(\"should have button role\", async () => {\n const button = await createButton();\n const clickable = getClickableElement(button);\n\n expect(hasRole(clickable, \"button\")).toBe(true);\n cleanup();\n });\n\n testFn(\"should indicate disabled state via aria-disabled\", async () => {\n const button = await createButton({ disabled: true });\n const clickable = getClickableElement(button);\n\n expect(isDisabled(clickable)).toBe(true);\n cleanup();\n });\n}\n\n/**\n * Creates test context for Web Component button testing.\n */\nexport function createWCButtonTestContext(\n // biome-ignore lint/suspicious/noExplicitAny: Test framework types vary\n expectFn: any,\n // biome-ignore lint/suspicious/noExplicitAny: Test framework types vary\n mockFn: any\n): Omit<ButtonTestContext, \"createButton\" | \"cleanup\"> {\n return {\n getClickableElement: (button: HTMLElement) => {\n // WC buttons typically have an inner button element\n return button.querySelector(\"button\") ?? button;\n },\n expect: expectFn,\n createMockFn: mockFn,\n };\n}\n"],"mappings":";AAmBO,IAAM,OAAO;AAAA,EAClB,OAAO,EAAE,KAAK,SAAS,MAAM,SAAS,SAAS,GAAG;AAAA,EAClD,OAAO,EAAE,KAAK,KAAK,MAAM,SAAS,SAAS,GAAG;AAAA,EAC9C,QAAQ,EAAE,KAAK,UAAU,MAAM,UAAU,SAAS,GAAG;AAAA,EACrD,KAAK,EAAE,KAAK,OAAO,MAAM,OAAO,SAAS,EAAE;AAAA,EAC3C,SAAS,EAAE,KAAK,WAAW,MAAM,WAAW,SAAS,GAAG;AAAA,EACxD,WAAW,EAAE,KAAK,aAAa,MAAM,aAAa,SAAS,GAAG;AAAA,EAC9D,WAAW,EAAE,KAAK,aAAa,MAAM,aAAa,SAAS,GAAG;AAAA,EAC9D,YAAY,EAAE,KAAK,cAAc,MAAM,cAAc,SAAS,GAAG;AAAA,EACjE,MAAM,EAAE,KAAK,QAAQ,MAAM,QAAQ,SAAS,GAAG;AAAA,EAC/C,KAAK,EAAE,KAAK,OAAO,MAAM,OAAO,SAAS,GAAG;AAAA,EAC5C,QAAQ,EAAE,KAAK,UAAU,MAAM,UAAU,SAAS,GAAG;AAAA,EACrD,UAAU,EAAE,KAAK,YAAY,MAAM,YAAY,SAAS,GAAG;AAAA,EAC3D,WAAW,EAAE,KAAK,aAAa,MAAM,aAAa,SAAS,EAAE;AAAA,EAC7D,QAAQ,EAAE,KAAK,UAAU,MAAM,UAAU,SAAS,GAAG;AACvD;AAKO,SAAS,oBACd,MACA,MACe;AACf,SAAO,IAAI,cAAc,MAAM;AAAA,IAC7B,KAAK,KAAK;AAAA,IACV,MAAM,KAAK,QAAQ,KAAK;AAAA,IACxB,SAAS,KAAK;AAAA,IACd,UAAU,KAAK,YAAY;AAAA,IAC3B,SAAS,KAAK,WAAW;AAAA,IACzB,QAAQ,KAAK,UAAU;AAAA,IACvB,SAAS,KAAK,WAAW;AAAA,IACzB,SAAS,KAAK,WAAW;AAAA,IACzB,YAAY,KAAK,cAAc;AAAA,EACjC,CAAC;AACH;AAKO,SAAS,SAAS,SAAkB,WAAwD;AACjG,QAAM,OAAO,OAAO,cAAc,WAAW,KAAK,SAAS,IAAI;AAE/D,QAAM,eAAe,oBAAoB,WAAW,IAAI;AACxD,UAAQ,cAAc,YAAY;AAElC,QAAM,aAAa,oBAAoB,SAAS,IAAI;AACpD,UAAQ,cAAc,UAAU;AAClC;AAKO,SAAS,QAAQ,SAAkB,WAAwD;AAChG,QAAM,OAAO,OAAO,cAAc,WAAW,KAAK,SAAS,IAAI;AAC/D,QAAM,QAAQ,oBAAoB,WAAW,IAAI;AACjD,UAAQ,cAAc,KAAK;AAC7B;AAKO,SAAS,MAAM,SAAkB,WAAwD;AAC9F,QAAM,OAAO,OAAO,cAAc,WAAW,KAAK,SAAS,IAAI;AAC/D,QAAM,QAAQ,oBAAoB,SAAS,IAAI;AAC/C,UAAQ,cAAc,KAAK;AAC7B;AAKO,SAAS,SAAS,SAAkB,MAAoB;AAC7D,aAAW,QAAQ,MAAM;AACvB,UAAM,OAA0B;AAAA,MAC9B,KAAK;AAAA,MACL,MAAM,MAAM,KAAK,YAAY,CAAC;AAAA,MAC9B,SAAS,KAAK,WAAW,CAAC;AAAA,IAC5B;AACA,aAAS,SAAS,IAAI;AAAA,EACxB;AACF;AAKO,SAAS,IAAI,UAAkC,CAAC,GAAS;AAC9D,QAAM,gBAAgB,SAAS;AAC/B,MAAI,eAAe;AACjB,aAAS,eAAe,EAAE,GAAG,KAAK,KAAK,UAAU,QAAQ,YAAY,MAAM,CAAC;AAAA,EAC9E;AACF;AAKO,SAAS,MAAM,SAAwB;AAC5C,WAAS,SAAS,OAAO;AAC3B;AAKO,SAAS,MAAM,SAAwB;AAC5C,WAAS,SAAS,OAAO;AAC3B;AAKO,SAAS,YAAY,SAAwB;AAClD,WAAS,SAAS,QAAQ;AAC5B;AAKO,SAAS,QAAQ,SAAwB;AAC9C,WAAS,SAAS,SAAS;AAC7B;AAEO,SAAS,UAAU,SAAwB;AAChD,WAAS,SAAS,WAAW;AAC/B;AAEO,SAAS,UAAU,SAAwB;AAChD,WAAS,SAAS,WAAW;AAC/B;AAEO,SAAS,WAAW,SAAwB;AACjD,WAAS,SAAS,YAAY;AAChC;;;ACzIO,SAAS,QAAQ,SAAiC;AAEvD,QAAM,eAAe,QAAQ,aAAa,MAAM;AAChD,MAAI,cAAc;AAChB,WAAO;AAAA,EACT;AAGA,QAAM,gBAAwC;AAAA,IAC5C,QAAQ;AAAA,IACR,GAAG;AAAA,IACH,OAAO,aAAa,OAA2B;AAAA,IAC/C,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,KAAK;AAAA,IACL,SAAS;AAAA,IACT,MAAM;AAAA,IACN,KAAK;AAAA,IACL,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,EACN;AAEA,SAAO,cAAc,QAAQ,OAAO,KAAK;AAC3C;AAEA,SAAS,aAAa,OAAiC;AACrD,QAAM,OAAO,MAAM,MAAM,YAAY,KAAK;AAC1C,QAAM,UAAkC;AAAA,IACtC,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,OAAO;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,EACP;AACA,SAAO,QAAQ,IAAI,KAAK;AAC1B;AAKO,SAAS,QAAQ,SAAkB,cAA+B;AACvE,QAAM,aAAa,QAAQ,OAAO;AAClC,SAAO,eAAe;AACxB;AAKO,SAAS,iBAAiB,SAAkB,WAAkC;AAEnF,MAAI,UAAU,WAAW,OAAO,GAAG;AACjC,WAAO,QAAQ,aAAa,SAAS;AAAA,EACvC;AACA,SAAO,QAAQ,aAAa,QAAQ,SAAS,EAAE;AACjD;AAKO,SAAS,iBAAiB,SAAkB,WAAmB,eAAiC;AACrG,QAAM,QAAQ,iBAAiB,SAAS,SAAS;AACjD,MAAI,kBAAkB,QAAW;AAC/B,WAAO,UAAU;AAAA,EACnB;AACA,SAAO,UAAU;AACnB;AAKO,SAAS,WAAW,SAA2B;AACpD,SAAO,QAAQ,aAAa,eAAe,MAAM;AACnD;AAKO,SAAS,YAAY,SAA2B;AACrD,SAAO,QAAQ,aAAa,eAAe,MAAM;AACnD;AAKO,SAAS,WAAW,SAA2B;AACpD,SAAO,QAAQ,aAAa,eAAe,MAAM;AACnD;AAKO,SAAS,UAAU,SAA2B;AACnD,SAAO,QAAQ,aAAa,cAAc,MAAM;AAClD;AAKO,SAAS,WAAW,SAA2B;AACpD,SACE,QAAQ,aAAa,eAAe,MAAM,UAC1C,QAAQ,aAAa,UAAU;AAEnC;AAKO,SAAS,SAAS,SAA2B;AAClD,SACE,QAAQ,aAAa,aAAa,MAAM,UACxC,QAAQ,aAAa,QAAQ,KAC5B,QAAwB,OAAO,YAAY;AAEhD;AAKO,SAAS,kBAAkB,SAA2B;AAE3D,MAAI,QAAQ,aAAa,YAAY,GAAG;AACtC,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,QAAQ,aAAa,iBAAiB;AACzD,MAAI,YAAY;AACd,UAAM,eAAe,SAAS,eAAe,UAAU;AACvD,QAAI,cAAc,aAAa,KAAK,GAAG;AACrC,aAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,KAAK,QAAQ;AACnB,MAAI,IAAI;AACN,UAAM,QAAQ,SAAS,cAAc,cAAc,EAAE,IAAI;AACzD,QAAI,OAAO,aAAa,KAAK,GAAG;AAC9B,aAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,OAAO,QAAQ,OAAO;AAC5B,MAAI,SAAS,YAAY,SAAS,QAAQ;AACxC,WAAO,QAAS,QAAwB,aAAa,KAAK,CAAC;AAAA,EAC7D;AAEA,SAAO;AACT;AAKO,SAAS,kBAAkB,SAA0B;AAE1D,QAAM,YAAY,QAAQ,aAAa,YAAY;AACnD,MAAI,WAAW;AACb,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,QAAQ,aAAa,iBAAiB;AACzD,MAAI,YAAY;AACd,UAAM,MAAM,WAAW,MAAM,KAAK;AAClC,UAAM,SAAS,IACZ,IAAI,CAACA,QAAO,SAAS,eAAeA,GAAE,GAAG,aAAa,KAAK,CAAC,EAC5D,OAAO,OAAO;AACjB,QAAI,OAAO,SAAS,GAAG;AACrB,aAAO,OAAO,KAAK,GAAG;AAAA,IACxB;AAAA,EACF;AAGA,QAAM,KAAK,QAAQ;AACnB,MAAI,IAAI;AACN,UAAM,QAAQ,SAAS,cAAc,cAAc,EAAE,IAAI;AACzD,QAAI,OAAO,aAAa,KAAK,GAAG;AAC9B,aAAO,MAAM,YAAY,KAAK;AAAA,IAChC;AAAA,EACF;AAGA,SAAQ,QAAwB,aAAa,KAAK,KAAK;AACzD;AAKO,SAAS,YAAY,WAAoB,MAAyB;AACvE,QAAM,WAAW,UAAU,iBAAiB,UAAU,IAAI,IAAI;AAC9D,QAAM,SAAoB,CAAC;AAG3B,WAAS,QAAQ,CAAC,OAAO,OAAO,KAAK,EAAE,CAAC;AAGxC,QAAM,oBAA4C;AAAA,IAChD,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAEA,QAAM,WAAW,kBAAkB,IAAI;AACvC,MAAI,UAAU;AACZ,cAAU,iBAAiB,QAAQ,EAAE,QAAQ,CAAC,OAAO;AACnD,UAAI,CAAC,GAAG,aAAa,MAAM,KAAK,CAAC,OAAO,SAAS,EAAE,GAAG;AACpD,eAAO,KAAK,EAAE;AAAA,MAChB;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAKO,SAAS,qBAAqB,WAA+B;AAClE,QAAM,WAAW;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AAEX,SAAO,MAAM,KAAK,UAAU,iBAAiB,QAAQ,CAAC,EAAE,OAAO,CAAC,OAAO;AACrE,WAAO,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,EAAE;AAAA,EACxC,CAAC;AACH;;;AC7OO,SAAS,YAAY,SAAoC;AAC9D,SAAO;AAAA,IACL;AAAA,IAEA,MAAM,UAAkC;AACtC,aAAO,QAAQ,cAAc,QAAQ;AAAA,IACvC;AAAA,IAEA,SAAS,UAA6B;AACpC,aAAO,MAAM,KAAK,QAAQ,iBAAiB,QAAQ,CAAC;AAAA,IACtD;AAAA,IAEA,aAAa,MAA6B;AACxC,aAAO,QAAQ,aAAa,IAAI;AAAA,IAClC;AAAA,IAEA,aAAa,MAAuB;AAClC,aAAO,QAAQ,aAAa,IAAI;AAAA,IAClC;AAAA,IAEA,UAAkB;AAChB,aAAQ,QAAwB,aAAa,KAAK,KAAK;AAAA,IACzD;AAAA,IAEA,QAAc;AACZ,MAAC,QAAwB,MAAM;AAAA,IACjC;AAAA,IAEA,QAAc;AACZ,MAAC,QAAwB,MAAM;AAAA,IACjC;AAAA,IAEA,MAAM,gBAA+B;AAEnC,UAAI,oBAAoB,SAAS;AAC/B,cAAO,QAAiD;AAAA,MAC1D;AAEA,YAAM,IAAI,QAAQ,CAAC,YAAY,sBAAsB,OAAO,CAAC;AAAA,IAC/D;AAAA,IAEA,UAAgB;AACd,cAAQ,OAAO;AAAA,IACjB;AAAA,EACF;AACF;AAKA,eAAsB,gBACpB,SACA,YACA,WAC8C;AAE9C,MAAI,YAAY,SAAS,eAAe,gBAAgB;AACxD,MAAI,CAAC,WAAW;AACd,gBAAY,SAAS,cAAc,KAAK;AACxC,cAAU,KAAK;AACf,aAAS,KAAK,YAAY,SAAS;AAAA,EACrC;AAGA,QAAM,UAAU,SAAS,cAAc,OAAO;AAG9C,MAAI,YAAY;AACd,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACtD,cAAQ,aAAa,MAAM,KAAK;AAAA,IAClC;AAAA,EACF;AAGA,MAAI,WAAW;AACb,YAAQ,YAAY;AAAA,EACtB;AAGA,YAAU,YAAY,OAAO;AAG7B,QAAM,eAAe,YAAY,OAAO;AAGxC,MAAI,oBAAoB,SAAS;AAC/B,UAAO,QAAiD;AAAA,EAC1D;AAEA,QAAM,UAAU,YAAY,OAAO;AAEnC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,WAAW;AAAA,EACb;AACF;AAKO,SAAS,oBAA0B;AACxC,QAAM,YAAY,SAAS,eAAe,gBAAgB;AAC1D,MAAI,WAAW;AACb,cAAU,YAAY;AAAA,EACxB;AACF;AAKA,eAAsB,WAAW,MAAyC;AACxE,MAAI,YAAY,SAAS,eAAe,gBAAgB;AACxD,MAAI,CAAC,WAAW;AACd,gBAAY,SAAS,cAAc,KAAK;AACxC,cAAU,KAAK;AACf,aAAS,KAAK,YAAY,SAAS;AAAA,EACrC;AAEA,YAAU,YAAY;AAEtB,QAAM,UAAU,UAAU;AAC1B,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AAGA,QAAMC,kBAAiB,UAAU,iBAAiB,gBAAgB;AAClE,QAAM,QAAQ;AAAA,IACZ,MAAM,KAAKA,eAAc,EAAE;AAAA,MAAI,CAAC,OAC9B,OAAO,eAAe,YAAY,GAAG,QAAQ,YAAY,CAAC;AAAA,IAC5D;AAAA,EACF;AAGA,QAAM,IAAI,QAAQ,CAAC,YAAY,sBAAsB,OAAO,CAAC;AAE7D,SAAO,YAAY,OAAO;AAC5B;;;AC5GO,SAAS,uBACd,SACA,QACM;AACN,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,oBAAoB;AAAA,EACtB,IAAI;AAEJ,SAAO,0CAA0C,YAAY;AAC3D,UAAM,oBAAoB,aAAa;AACvC,UAAM,SAAS,MAAM;AAAA,MACnB,6BAA6B,EAAE,YAAY,kBAAkB,IAAI;AAAA,IACnE;AACA,UAAM,YAAY,oBAAoB,MAAM;AAE5C,QAAI,CAAC,4BAA4B;AAC/B,aAAO,iBAAiB,SAAS,iBAAiB;AAAA,IACpD;AAEA,aAAS,WAAW,OAAO;AAE3B,WAAO,iBAAiB,EAAE,iBAAiB;AAC3C,YAAQ;AAAA,EACV,CAAC;AAED,MAAI,mBAAmB;AACrB,WAAO,0CAA0C,YAAY;AAC3D,YAAM,oBAAoB,aAAa;AACvC,YAAM,SAAS,MAAM;AAAA,QACnB,6BAA6B,EAAE,YAAY,kBAAkB,IAAI;AAAA,MACnE;AACA,YAAM,YAAY,oBAAoB,MAAM;AAE5C,UAAI,CAAC,4BAA4B;AAC/B,eAAO,iBAAiB,SAAS,iBAAiB;AAAA,MACpD;AAEA,eAAS,WAAW,OAAO;AAE3B,aAAO,iBAAiB,EAAE,iBAAiB;AAC3C,cAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAEA,SAAO,wDAAwD,YAAY;AACzE,UAAM,oBAAoB,aAAa;AACvC,UAAM,SAAS,MAAM;AAAA,MACnB,6BACI,EAAE,UAAU,MAAM,YAAY,kBAAkB,IAChD,EAAE,UAAU,KAAK;AAAA,IACvB;AACA,UAAM,YAAY,oBAAoB,MAAM;AAE5C,QAAI,CAAC,4BAA4B;AAC/B,aAAO,iBAAiB,SAAS,iBAAiB;AAAA,IACpD;AAEA,aAAS,WAAW,OAAO;AAE3B,WAAO,iBAAiB,EAAE,IAAI,iBAAiB;AAC/C,YAAQ;AAAA,EACV,CAAC;AAED,MAAI,mBAAmB;AACrB,WAAO,wDAAwD,YAAY;AACzE,YAAM,oBAAoB,aAAa;AACvC,YAAM,SAAS,MAAM;AAAA,QACnB,6BACI,EAAE,UAAU,MAAM,YAAY,kBAAkB,IAChD,EAAE,UAAU,KAAK;AAAA,MACvB;AACA,YAAM,YAAY,oBAAoB,MAAM;AAE5C,UAAI,CAAC,4BAA4B;AAC/B,eAAO,iBAAiB,SAAS,iBAAiB;AAAA,MACpD;AAEA,eAAS,WAAW,OAAO;AAE3B,aAAO,iBAAiB,EAAE,IAAI,iBAAiB;AAC/C,cAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAEA,SAAO,2BAA2B,YAAY;AAC5C,UAAM,SAAS,MAAM,aAAa;AAClC,UAAM,YAAY,oBAAoB,MAAM;AAE5C,WAAO,QAAQ,WAAW,QAAQ,CAAC,EAAE,KAAK,IAAI;AAC9C,YAAQ;AAAA,EACV,CAAC;AAED,SAAO,oDAAoD,YAAY;AACrE,UAAM,SAAS,MAAM,aAAa,EAAE,UAAU,KAAK,CAAC;AACpD,UAAM,YAAY,oBAAoB,MAAM;AAE5C,WAAO,WAAW,SAAS,CAAC,EAAE,KAAK,IAAI;AACvC,YAAQ;AAAA,EACV,CAAC;AACH;AAKO,SAAS,0BAEd,UAEA,QACqD;AACrD,SAAO;AAAA,IACL,qBAAqB,CAAC,WAAwB;AAE5C,aAAO,OAAO,cAAc,QAAQ,KAAK;AAAA,IAC3C;AAAA,IACA,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AACF;","names":["id","customElements"]}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@hypoth-ui/test-utils",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "description": "Test utilities for the hypoth-ui design system (Alpha)",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/hypoth-ui/hypoth-ui.git",
11
+ "directory": "packages/test-utils"
12
+ },
13
+ "homepage": "https://github.com/hypoth-ui/hypoth-ui#readme",
14
+ "keywords": [
15
+ "testing",
16
+ "utilities",
17
+ "design-system",
18
+ "vitest",
19
+ "hypoth-ui"
20
+ ],
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ },
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "devDependencies": {
32
+ "tsup": "^8.0.0",
33
+ "typescript": "^5.3.0"
34
+ },
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "clean": "rm -rf dist",
38
+ "typecheck": "tsc --noEmit"
39
+ }
40
+ }