@coherent.js/testing 1.0.0-beta.3 → 1.0.0-beta.6
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/package.json +3 -2
- package/types/index.d.ts +283 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coherent.js/testing",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.6",
|
|
4
4
|
"description": "Testing utilities for Coherent.js applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"author": "Coherent.js Team",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"@coherent.js/core": "1.0.0-beta.
|
|
22
|
+
"@coherent.js/core": "1.0.0-beta.6"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"vitest": "^1.0.0"
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"README.md",
|
|
38
38
|
"types/"
|
|
39
39
|
],
|
|
40
|
+
"sideEffects": false,
|
|
40
41
|
"scripts": {
|
|
41
42
|
"build": "node build.mjs",
|
|
42
43
|
"clean": "rm -rf dist",
|
package/types/index.d.ts
CHANGED
|
@@ -3,76 +3,229 @@
|
|
|
3
3
|
* @module @coherent.js/testing
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import type { CoherentNode, CoherentElement, CoherentComponent, ComponentProps } from '@coherent.js/core';
|
|
7
7
|
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Test Renderer Types
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Render options for test utilities
|
|
14
|
+
*/
|
|
8
15
|
export interface RenderOptions {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
/** Wrapper component */
|
|
17
|
+
wrapper?: CoherentComponent;
|
|
18
|
+
/** Context values to provide */
|
|
19
|
+
context?: Record<string, unknown>;
|
|
20
|
+
/** Props to pass to component */
|
|
21
|
+
props?: Record<string, unknown>;
|
|
22
|
+
/** Initial state */
|
|
23
|
+
initialState?: Record<string, unknown>;
|
|
12
24
|
}
|
|
13
25
|
|
|
14
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Result from rendering a component
|
|
28
|
+
*/
|
|
29
|
+
export interface RenderResult {
|
|
30
|
+
/** Rendered HTML string */
|
|
15
31
|
html: string;
|
|
16
|
-
|
|
32
|
+
/** The rendered element structure */
|
|
33
|
+
element: CoherentElement;
|
|
34
|
+
/** Container element (if DOM is available) */
|
|
17
35
|
container: HTMLElement | null;
|
|
18
|
-
|
|
36
|
+
/** Re-render with new props */
|
|
37
|
+
rerender(props?: Record<string, unknown>): void;
|
|
38
|
+
/** Unmount and cleanup */
|
|
19
39
|
unmount(): void;
|
|
40
|
+
/** Debug output */
|
|
20
41
|
debug(): void;
|
|
42
|
+
/** Query helpers */
|
|
43
|
+
getByText(text: string | RegExp): Element | null;
|
|
44
|
+
getByTestId(testId: string): Element | null;
|
|
45
|
+
getAllByText(text: string | RegExp): Element[];
|
|
21
46
|
}
|
|
22
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Test renderer class
|
|
50
|
+
*/
|
|
23
51
|
export class TestRenderer {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
52
|
+
/** Render a component */
|
|
53
|
+
render(component: CoherentNode, options?: RenderOptions): RenderResult;
|
|
54
|
+
|
|
55
|
+
/** Render a component asynchronously */
|
|
56
|
+
renderAsync(component: CoherentNode, options?: RenderOptions): Promise<RenderResult>;
|
|
57
|
+
|
|
58
|
+
/** Shallow render (no children) */
|
|
59
|
+
shallow(component: CoherentNode): RenderResult;
|
|
60
|
+
|
|
61
|
+
/** Cleanup all renders */
|
|
27
62
|
cleanup(): void;
|
|
28
63
|
}
|
|
29
64
|
|
|
30
|
-
|
|
31
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Render a component for testing
|
|
67
|
+
*/
|
|
68
|
+
export function renderComponent(
|
|
69
|
+
component: CoherentComponent | CoherentNode,
|
|
70
|
+
props?: Record<string, unknown>
|
|
71
|
+
): RenderResult;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Render a component asynchronously
|
|
75
|
+
*/
|
|
76
|
+
export function renderComponentAsync(
|
|
77
|
+
component: CoherentNode,
|
|
78
|
+
options?: RenderOptions
|
|
79
|
+
): Promise<RenderResult>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a new test renderer instance
|
|
83
|
+
*/
|
|
32
84
|
export function createTestRenderer(): TestRenderer;
|
|
33
|
-
export function shallowRender(component: any): TestRendererResult;
|
|
34
85
|
|
|
35
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Shallow render a component
|
|
88
|
+
*/
|
|
89
|
+
export function shallowRender(component: CoherentNode): RenderResult;
|
|
36
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Render a node to HTML string
|
|
93
|
+
*/
|
|
94
|
+
export function renderToString(node: CoherentNode): string;
|
|
95
|
+
|
|
96
|
+
// ============================================================================
|
|
97
|
+
// Custom Matchers for Coherent.js
|
|
98
|
+
// ============================================================================
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Coherent.js-specific test matchers
|
|
102
|
+
*/
|
|
103
|
+
export interface CoherentMatchers<R = unknown> {
|
|
104
|
+
// Element structure matchers
|
|
105
|
+
/** Assert element has specific tag name */
|
|
106
|
+
toHaveTag(tagName: string): R;
|
|
107
|
+
/** Assert element contains text */
|
|
108
|
+
toHaveText(text: string): R;
|
|
109
|
+
/** Assert element has attribute (optionally with value) */
|
|
110
|
+
toHaveAttribute(name: string, value?: string): R;
|
|
111
|
+
/** Assert element has CSS class */
|
|
112
|
+
toHaveClassName(className: string): R;
|
|
113
|
+
/** Assert element has children (optionally specific count) */
|
|
114
|
+
toHaveChildren(count?: number): R;
|
|
115
|
+
|
|
116
|
+
// Component matchers
|
|
117
|
+
/** Assert component renders an element with tag */
|
|
118
|
+
toRenderElement(tagName: string): R;
|
|
119
|
+
/** Assert component renders text content */
|
|
120
|
+
toRenderText(text: string): R;
|
|
121
|
+
/** Assert component matches snapshot */
|
|
122
|
+
toMatchComponentSnapshot(): R;
|
|
123
|
+
|
|
124
|
+
// Hydration matchers
|
|
125
|
+
/** Assert hydration completes without mismatch */
|
|
126
|
+
toHydrateWithoutMismatch(): R;
|
|
127
|
+
/** Assert hydrated component has specific state */
|
|
128
|
+
toHaveState(state: Record<string, unknown>): R;
|
|
129
|
+
|
|
130
|
+
// Accessibility matchers
|
|
131
|
+
/** Assert element has accessible name */
|
|
132
|
+
toHaveAccessibleName(name: string): R;
|
|
133
|
+
/** Assert element has ARIA role */
|
|
134
|
+
toHaveRole(role: string): R;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// Test Utilities
|
|
139
|
+
// ============================================================================
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Event simulation options
|
|
143
|
+
*/
|
|
37
144
|
export interface EventOptions {
|
|
38
145
|
bubbles?: boolean;
|
|
39
146
|
cancelable?: boolean;
|
|
40
147
|
composed?: boolean;
|
|
41
|
-
[key: string]:
|
|
148
|
+
[key: string]: unknown;
|
|
42
149
|
}
|
|
43
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Fire DOM events on elements
|
|
153
|
+
*/
|
|
44
154
|
export const fireEvent: {
|
|
155
|
+
/** Fire any event */
|
|
45
156
|
(element: Element, event: Event): boolean;
|
|
157
|
+
/** Fire click event */
|
|
46
158
|
click(element: Element, options?: EventOptions): boolean;
|
|
47
|
-
|
|
48
|
-
|
|
159
|
+
/** Fire change event */
|
|
160
|
+
change(element: Element, options?: EventOptions & { target?: { value?: unknown } }): boolean;
|
|
161
|
+
/** Fire input event */
|
|
162
|
+
input(element: Element, options?: EventOptions & { target?: { value?: unknown } }): boolean;
|
|
163
|
+
/** Fire submit event */
|
|
49
164
|
submit(element: Element, options?: EventOptions): boolean;
|
|
165
|
+
/** Fire keydown event */
|
|
50
166
|
keyDown(element: Element, options?: EventOptions & { key?: string; code?: string }): boolean;
|
|
167
|
+
/** Fire keyup event */
|
|
51
168
|
keyUp(element: Element, options?: EventOptions & { key?: string; code?: string }): boolean;
|
|
169
|
+
/** Fire focus event */
|
|
52
170
|
focus(element: Element, options?: EventOptions): boolean;
|
|
171
|
+
/** Fire blur event */
|
|
53
172
|
blur(element: Element, options?: EventOptions): boolean;
|
|
173
|
+
/** Fire mouseenter event */
|
|
54
174
|
mouseEnter(element: Element, options?: EventOptions): boolean;
|
|
175
|
+
/** Fire mouseleave event */
|
|
55
176
|
mouseLeave(element: Element, options?: EventOptions): boolean;
|
|
56
|
-
[key: string]:
|
|
177
|
+
[key: string]: unknown;
|
|
57
178
|
};
|
|
58
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Wait options
|
|
182
|
+
*/
|
|
59
183
|
export interface WaitOptions {
|
|
184
|
+
/** Timeout in ms */
|
|
60
185
|
timeout?: number;
|
|
186
|
+
/** Check interval in ms */
|
|
61
187
|
interval?: number;
|
|
62
188
|
}
|
|
63
189
|
|
|
64
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Wait for a condition to be true
|
|
192
|
+
*/
|
|
193
|
+
export function waitFor<T>(
|
|
194
|
+
callback: () => T | Promise<T>,
|
|
195
|
+
options?: WaitOptions
|
|
196
|
+
): Promise<T>;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Wait for an element to appear
|
|
200
|
+
*/
|
|
65
201
|
export function waitForElement(selector: string, options?: WaitOptions): Promise<Element>;
|
|
66
|
-
export function waitForElementToBeRemoved(selector: string | Element, options?: WaitOptions): Promise<void>;
|
|
67
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Wait for an element to be removed
|
|
205
|
+
*/
|
|
206
|
+
export function waitForElementToBeRemoved(
|
|
207
|
+
selector: string | Element,
|
|
208
|
+
options?: WaitOptions
|
|
209
|
+
): Promise<void>;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Run a callback and flush pending state updates
|
|
213
|
+
*/
|
|
68
214
|
export function act<T>(callback: () => T | Promise<T>): Promise<T>;
|
|
69
215
|
|
|
70
|
-
|
|
216
|
+
// ============================================================================
|
|
217
|
+
// Mock Utilities
|
|
218
|
+
// ============================================================================
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Mock function interface
|
|
222
|
+
*/
|
|
223
|
+
export interface Mock<T extends (...args: unknown[]) => unknown = (...args: unknown[]) => unknown> {
|
|
71
224
|
(...args: Parameters<T>): ReturnType<T>;
|
|
72
225
|
mock: {
|
|
73
226
|
calls: Parameters<T>[];
|
|
74
|
-
results: Array<{ type: 'return' | 'throw'; value:
|
|
75
|
-
instances:
|
|
227
|
+
results: Array<{ type: 'return' | 'throw'; value: unknown }>;
|
|
228
|
+
instances: unknown[];
|
|
76
229
|
};
|
|
77
230
|
mockClear(): void;
|
|
78
231
|
mockReset(): void;
|
|
@@ -81,14 +234,55 @@ export interface Mock<T extends (...args: any[]) => any = (...args: any[]) => an
|
|
|
81
234
|
mockReturnValue(value: ReturnType<T>): this;
|
|
82
235
|
mockReturnValueOnce(value: ReturnType<T>): this;
|
|
83
236
|
mockResolvedValue(value: ReturnType<T> extends Promise<infer U> ? U : never): this;
|
|
84
|
-
mockRejectedValue(error:
|
|
237
|
+
mockRejectedValue(error: unknown): this;
|
|
85
238
|
}
|
|
86
239
|
|
|
87
|
-
|
|
88
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Create a mock function
|
|
242
|
+
*/
|
|
243
|
+
export function createMock<T extends (...args: unknown[]) => unknown>(
|
|
244
|
+
implementation?: T
|
|
245
|
+
): Mock<T>;
|
|
89
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Create a spy on an object method
|
|
249
|
+
*/
|
|
250
|
+
export function createSpy<T extends (...args: unknown[]) => unknown>(
|
|
251
|
+
object: object,
|
|
252
|
+
method: string
|
|
253
|
+
): Mock<T>;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Mock a component
|
|
257
|
+
*/
|
|
258
|
+
export function mockComponent<P extends ComponentProps = ComponentProps>(
|
|
259
|
+
name: string,
|
|
260
|
+
render?: (props: P) => CoherentNode
|
|
261
|
+
): CoherentComponent<P>;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Create test state with reset capability
|
|
265
|
+
*/
|
|
266
|
+
export function createTestState<T extends Record<string, unknown>>(
|
|
267
|
+
initial: T
|
|
268
|
+
): {
|
|
269
|
+
getState: () => T;
|
|
270
|
+
setState: (updates: Partial<T>) => void;
|
|
271
|
+
reset: () => void;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Cleanup all mocks and rendered components
|
|
276
|
+
*/
|
|
90
277
|
export function cleanup(): void;
|
|
91
278
|
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// Query Utilities
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Query helper interface
|
|
285
|
+
*/
|
|
92
286
|
export interface Within {
|
|
93
287
|
getByText(text: string | RegExp): Element;
|
|
94
288
|
getByRole(role: string, options?: { name?: string | RegExp }): Element;
|
|
@@ -102,9 +296,19 @@ export interface Within {
|
|
|
102
296
|
findAllByText(text: string | RegExp): Promise<Element[]>;
|
|
103
297
|
}
|
|
104
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Create query helpers scoped to an element
|
|
301
|
+
*/
|
|
105
302
|
export function within(element: Element): Within;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Global screen queries (document.body)
|
|
306
|
+
*/
|
|
106
307
|
export const screen: Within;
|
|
107
308
|
|
|
309
|
+
/**
|
|
310
|
+
* User event simulation
|
|
311
|
+
*/
|
|
108
312
|
export const userEvent: {
|
|
109
313
|
click(element: Element): Promise<void>;
|
|
110
314
|
dblClick(element: Element): Promise<void>;
|
|
@@ -118,8 +322,37 @@ export const userEvent: {
|
|
|
118
322
|
paste(element: Element, text: string): Promise<void>;
|
|
119
323
|
};
|
|
120
324
|
|
|
121
|
-
//
|
|
325
|
+
// ============================================================================
|
|
326
|
+
// Assertion Utilities
|
|
327
|
+
// ============================================================================
|
|
122
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Assert element structure matches expected
|
|
331
|
+
*/
|
|
332
|
+
export function assertElementStructure(
|
|
333
|
+
element: CoherentElement,
|
|
334
|
+
expected: Partial<CoherentElement>
|
|
335
|
+
): void;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Standard assertions
|
|
339
|
+
*/
|
|
340
|
+
export const assertions: {
|
|
341
|
+
assertElement(element: unknown): asserts element is Element;
|
|
342
|
+
assertHTMLElement(element: unknown): asserts element is HTMLElement;
|
|
343
|
+
assertInDocument(element: Element | null): asserts element is Element;
|
|
344
|
+
assertVisible(element: Element): void;
|
|
345
|
+
assertHasAttribute(element: Element, attr: string): void;
|
|
346
|
+
assertHasClass(element: Element, className: string): void;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// ============================================================================
|
|
350
|
+
// DOM Matchers (for Vitest/Jest)
|
|
351
|
+
// ============================================================================
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Custom DOM matchers
|
|
355
|
+
*/
|
|
123
356
|
export interface CustomMatchers<R = void> {
|
|
124
357
|
toHaveHTML(html: string): R;
|
|
125
358
|
toContainHTML(html: string): R;
|
|
@@ -130,35 +363,42 @@ export interface CustomMatchers<R = void> {
|
|
|
130
363
|
toBeVisible(): R;
|
|
131
364
|
toBeDisabled(): R;
|
|
132
365
|
toBeEnabled(): R;
|
|
133
|
-
toHaveValue(value:
|
|
134
|
-
toHaveStyle(style: Record<string,
|
|
366
|
+
toHaveValue(value: unknown): R;
|
|
367
|
+
toHaveStyle(style: Record<string, unknown>): R;
|
|
135
368
|
toHaveFocus(): R;
|
|
136
369
|
toBeChecked(): R;
|
|
137
370
|
toBeValid(): R;
|
|
138
371
|
toBeInvalid(): R;
|
|
139
372
|
}
|
|
140
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Custom matchers object
|
|
376
|
+
*/
|
|
141
377
|
export const customMatchers: CustomMatchers;
|
|
142
378
|
|
|
143
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Extend test framework expect
|
|
381
|
+
*/
|
|
382
|
+
export function extendExpect(matchers: Record<string, (...args: unknown[]) => unknown>): void;
|
|
144
383
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
384
|
+
// ============================================================================
|
|
385
|
+
// Vitest/Jest Module Extensions
|
|
386
|
+
// ============================================================================
|
|
387
|
+
|
|
388
|
+
// Extend Vitest matchers
|
|
389
|
+
declare module 'vitest' {
|
|
390
|
+
interface Assertion<T = unknown> extends CoherentMatchers<T>, CustomMatchers<T> {}
|
|
391
|
+
interface AsymmetricMatchersContaining extends CoherentMatchers, CustomMatchers {}
|
|
392
|
+
}
|
|
153
393
|
|
|
154
|
-
// Extend Jest
|
|
394
|
+
// Extend Jest matchers (for users using Jest)
|
|
155
395
|
declare global {
|
|
156
396
|
namespace Vi {
|
|
157
|
-
interface Matchers<R = void> extends CustomMatchers<R> {}
|
|
158
|
-
interface AsymmetricMatchers extends CustomMatchers {}
|
|
397
|
+
interface Matchers<R = void> extends CustomMatchers<R>, CoherentMatchers<R> {}
|
|
398
|
+
interface AsymmetricMatchers extends CustomMatchers, CoherentMatchers {}
|
|
159
399
|
}
|
|
160
400
|
namespace jest {
|
|
161
|
-
interface Matchers<R = void> extends CustomMatchers<R> {}
|
|
162
|
-
interface Expect extends CustomMatchers {}
|
|
401
|
+
interface Matchers<R = void> extends CustomMatchers<R>, CoherentMatchers<R> {}
|
|
402
|
+
interface Expect extends CustomMatchers, CoherentMatchers {}
|
|
163
403
|
}
|
|
164
404
|
}
|