@fictjs/testing-library 0.2.3 → 0.3.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/dist/index.d.cts CHANGED
@@ -1,2 +1,492 @@
1
+ import { Queries, queries, BoundFunctions, prettyFormat } from '@testing-library/dom';
2
+ export * from '@testing-library/dom';
3
+ export { prettyDOM, queries } from '@testing-library/dom';
4
+ import { FictNode, Component } from '@fictjs/runtime';
1
5
 
2
- export { }
6
+ /**
7
+ * @fileoverview Type definitions for @fictjs/testing-library
8
+ *
9
+ * This module provides TypeScript type definitions for the Fict testing library.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+
14
+ /**
15
+ * Internal reference to a mounted container for cleanup tracking.
16
+ */
17
+ interface MountedRef {
18
+ container?: HTMLElement;
19
+ baseElement?: HTMLElement;
20
+ ownedContainer?: boolean;
21
+ teardown: () => void;
22
+ }
23
+ /**
24
+ * A Fict view function that returns a FictNode.
25
+ */
26
+ type View = () => FictNode;
27
+ /**
28
+ * Options for the render function.
29
+ */
30
+ interface RenderOptions<Q extends Queries = typeof queries> {
31
+ /**
32
+ * The container element to render into. If not provided, a div will be created.
33
+ */
34
+ container?: HTMLElement;
35
+ /**
36
+ * The base element for queries. Defaults to the container when provided,
37
+ * otherwise defaults to document.body.
38
+ */
39
+ baseElement?: HTMLElement;
40
+ /**
41
+ * Custom queries to use instead of the default queries.
42
+ */
43
+ queries?: Q;
44
+ /**
45
+ * A wrapper component to wrap the rendered view with.
46
+ * Useful for providing context or other providers.
47
+ */
48
+ wrapper?: Component<{
49
+ children: FictNode;
50
+ }>;
51
+ }
52
+ /**
53
+ * Debug function type for pretty-printing DOM.
54
+ */
55
+ type DebugFn = (baseElement?: HTMLElement | HTMLElement[], maxLength?: number, options?: prettyFormat.OptionsReceived) => void;
56
+ /**
57
+ * Result returned from render().
58
+ */
59
+ type RenderResult<Q extends Queries = typeof queries> = BoundFunctions<Q> & {
60
+ /**
61
+ * Returns the innerHTML of the container as a string.
62
+ */
63
+ asFragment: () => string;
64
+ /**
65
+ * The container element the view was rendered into.
66
+ */
67
+ container: HTMLElement;
68
+ /**
69
+ * The base element for queries.
70
+ */
71
+ baseElement: HTMLElement;
72
+ /**
73
+ * Pretty-print the DOM for debugging.
74
+ */
75
+ debug: DebugFn;
76
+ /**
77
+ * Unmount the rendered view and clean up.
78
+ */
79
+ unmount: () => void;
80
+ /**
81
+ * Re-render with a new view.
82
+ */
83
+ rerender: (newView: View) => void;
84
+ };
85
+ /**
86
+ * Options for renderHook function.
87
+ */
88
+ interface RenderHookOptions<Props extends unknown[]> {
89
+ /**
90
+ * Initial props to pass to the hook.
91
+ */
92
+ initialProps?: Props;
93
+ /**
94
+ * A wrapper component to wrap the hook with.
95
+ */
96
+ wrapper?: Component<{
97
+ children: FictNode;
98
+ }>;
99
+ }
100
+ /**
101
+ * Result returned from renderHook().
102
+ */
103
+ interface RenderHookResult<Result, Props extends unknown[]> {
104
+ /**
105
+ * Container holding the result of calling the hook.
106
+ * Access via `result.current`. Updated when the hook re-runs.
107
+ */
108
+ result: {
109
+ current: Result;
110
+ };
111
+ /**
112
+ * Re-render the hook with new props.
113
+ * Note: this disposes the previous root and mounts a new one; hook state resets.
114
+ */
115
+ rerender: (newProps?: Props) => void;
116
+ /**
117
+ * Clean up the hook and dispose of the root.
118
+ */
119
+ cleanup: () => void;
120
+ /**
121
+ * Alias for cleanup.
122
+ */
123
+ unmount: () => void;
124
+ }
125
+ /**
126
+ * Callback function signature for testEffect.
127
+ * @param done - Call this function with the result when the effect completes.
128
+ */
129
+ type TestEffectCallback<T> = (done: (result: T) => void) => void;
130
+ /**
131
+ * Result returned from renderWithErrorBoundary().
132
+ */
133
+ type ErrorBoundaryRenderResult<Q extends Queries = typeof queries> = RenderResult<Q> & {
134
+ /**
135
+ * Trigger an error in the error boundary by rendering a component that throws.
136
+ */
137
+ triggerError: (error: Error) => void;
138
+ /**
139
+ * Reset the error boundary to re-render children.
140
+ */
141
+ resetErrorBoundary: () => void;
142
+ /**
143
+ * Check if the error boundary is currently showing the fallback.
144
+ */
145
+ isShowingFallback: () => boolean;
146
+ };
147
+ /**
148
+ * Options for renderWithErrorBoundary().
149
+ */
150
+ type ErrorBoundaryRenderOptions<Q extends Queries = typeof queries> = RenderOptions<Q> & {
151
+ /**
152
+ * The fallback UI to show when an error is caught.
153
+ * Can be a FictNode or a function that receives the error and reset function.
154
+ */
155
+ fallback?: FictNode | ((err: unknown, reset?: () => void) => FictNode);
156
+ /**
157
+ * Callback called when an error is caught.
158
+ */
159
+ onError?: (err: unknown) => void;
160
+ /**
161
+ * Keys that trigger a reset when changed.
162
+ */
163
+ resetKeys?: unknown | (() => unknown);
164
+ };
165
+ /**
166
+ * Result returned from renderWithSuspense().
167
+ */
168
+ type SuspenseRenderResult<Q extends Queries = typeof queries> = RenderResult<Q> & {
169
+ /**
170
+ * Check if the suspense boundary is currently showing the fallback.
171
+ */
172
+ isShowingFallback: () => boolean;
173
+ /**
174
+ * Wait for the suspense to resolve.
175
+ */
176
+ waitForResolution: (options?: {
177
+ timeout?: number;
178
+ }) => Promise<void>;
179
+ };
180
+ /**
181
+ * Options for renderWithSuspense().
182
+ */
183
+ type SuspenseRenderOptions<Q extends Queries = typeof queries> = RenderOptions<Q> & {
184
+ /**
185
+ * The fallback UI to show while suspended.
186
+ */
187
+ fallback?: FictNode | ((err?: unknown) => FictNode);
188
+ /**
189
+ * Callback called when the suspense resolves.
190
+ */
191
+ onResolve?: () => void;
192
+ /**
193
+ * Callback called when the suspense rejects.
194
+ *
195
+ * Note: providing onReject treats the rejection as handled in the test root
196
+ * to avoid unhandled promise rejections.
197
+ */
198
+ onReject?: (err: unknown) => void;
199
+ /**
200
+ * Keys that trigger a reset when changed.
201
+ */
202
+ resetKeys?: unknown | (() => unknown);
203
+ };
204
+ /**
205
+ * Handle returned from createTestSuspenseToken().
206
+ */
207
+ interface TestSuspenseHandle {
208
+ /**
209
+ * The suspense token to throw in a component.
210
+ */
211
+ token: {
212
+ then: PromiseLike<void>['then'];
213
+ };
214
+ /**
215
+ * Resolve the suspense, allowing children to render.
216
+ */
217
+ resolve: () => void;
218
+ /**
219
+ * Reject the suspense with an error.
220
+ */
221
+ reject: (err: unknown) => void;
222
+ }
223
+
224
+ /**
225
+ * @fileoverview @fictjs/testing-library - Testing utilities for Fict components
226
+ *
227
+ * This library provides utilities for testing Fict components in a manner
228
+ * similar to @testing-library/react and @solidjs/testing-library.
229
+ *
230
+ * ## Key Features
231
+ *
232
+ * - `render()` - Render a Fict component and get queries
233
+ * - `cleanup()` - Clean up rendered components
234
+ * - `renderHook()` - Test custom reactive code in isolation
235
+ * - `testEffect()` - Test effects with async assertions
236
+ *
237
+ * ## Example Usage
238
+ *
239
+ * ```tsx
240
+ * import { render, screen, cleanup } from '@fictjs/testing-library'
241
+ *
242
+ * test('renders greeting', () => {
243
+ * render(() => <Greeting name="World" />)
244
+ * expect(screen.getByText('Hello, World!')).toBeInTheDocument()
245
+ * })
246
+ * ```
247
+ *
248
+ * @packageDocumentation
249
+ */
250
+
251
+ /**
252
+ * Render a Fict component for testing.
253
+ *
254
+ * @param view - A function that returns the component to render
255
+ * @param options - Render options (container, baseElement, queries, wrapper)
256
+ * @returns A render result with queries and utilities
257
+ *
258
+ * @example
259
+ * ```tsx
260
+ * // Basic usage
261
+ * const { getByText } = render(() => <MyComponent />)
262
+ * expect(getByText('Hello')).toBeInTheDocument()
263
+ *
264
+ * // With wrapper (for context providers)
265
+ * const { getByText } = render(() => <MyComponent />, {
266
+ * wrapper: ({ children }) => <ThemeProvider>{children}</ThemeProvider>
267
+ * })
268
+ *
269
+ * // With custom container
270
+ * const container = document.createElement('div')
271
+ * const { getByText } = render(() => <MyComponent />, { container })
272
+ * ```
273
+ */
274
+ declare function render<Q extends Queries = typeof queries>(view: View, options?: RenderOptions<Q>): RenderResult<Q>;
275
+ /**
276
+ * Clean up all rendered components.
277
+ *
278
+ * This is called automatically after each test when using Vitest/Jest.
279
+ * Can be called manually if needed.
280
+ *
281
+ * @example
282
+ * ```ts
283
+ * afterEach(() => {
284
+ * cleanup()
285
+ * })
286
+ * ```
287
+ */
288
+ declare function cleanup(): void;
289
+ /**
290
+ * Render a hook/reactive code for testing.
291
+ *
292
+ * This is useful for testing custom reactive logic that uses
293
+ * createEffect, createMemo, onMount, etc.
294
+ *
295
+ * @param hookFn - A function that contains the reactive code to test
296
+ * @param options - Options for rendering the hook
297
+ * @returns Result with the hook's return value and cleanup utilities
298
+ *
299
+ * @note
300
+ * renderHook().rerender() disposes the previous root and mounts a new one.
301
+ * Hook state does not persist across rerenders.
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * // Test a counter hook
306
+ * function useCounter(initial: number) {
307
+ * let count = $state(initial)
308
+ * const increment = () => count++
309
+ * return { count: () => count, increment }
310
+ * }
311
+ *
312
+ * test('counter increments', () => {
313
+ * const { result, cleanup } = renderHook(() => useCounter(0))
314
+ * expect(result.current.count()).toBe(0)
315
+ * result.current.increment()
316
+ * expect(result.current.count()).toBe(1)
317
+ * cleanup()
318
+ * })
319
+ *
320
+ * // With initial props
321
+ * const { result, rerender } = renderHook(
322
+ * (initial) => useCounter(initial),
323
+ * { initialProps: [10] }
324
+ * )
325
+ * expect(result.current.count()).toBe(10)
326
+ * rerender([20]) // Re-run with new props
327
+ * ```
328
+ */
329
+ declare function renderHook<Result, Props extends unknown[] = []>(hookFn: (...args: Props) => Result, options?: RenderHookOptions<Props> | Props): RenderHookResult<Result, Props>;
330
+ /**
331
+ * Test an effect asynchronously.
332
+ *
333
+ * This is useful for testing effects that need to wait for async operations
334
+ * or reactive updates.
335
+ *
336
+ * @param fn - A function that receives a `done` callback to signal completion
337
+ * @param owner - Optional root context to run the effect in
338
+ * @returns A promise that resolves with the result passed to `done`
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * // Test an async effect
343
+ * test('fetches data', async () => {
344
+ * const result = await testEffect((done) => {
345
+ * let data = $state(null)
346
+ *
347
+ * createEffect(() => {
348
+ * if (data !== null) {
349
+ * done(data)
350
+ * }
351
+ * })
352
+ *
353
+ * // Simulate async data fetch
354
+ * setTimeout(() => {
355
+ * data = 'Hello'
356
+ * }, 100)
357
+ * })
358
+ *
359
+ * expect(result).toBe('Hello')
360
+ * })
361
+ *
362
+ * // Test reactive updates
363
+ * test('derived value updates', async () => {
364
+ * const result = await testEffect<number>((done) => {
365
+ * let count = $state(0)
366
+ * const doubled = count * 2
367
+ *
368
+ * createEffect(() => {
369
+ * if (doubled === 4) {
370
+ * done(doubled)
371
+ * }
372
+ * })
373
+ *
374
+ * count = 2
375
+ * })
376
+ *
377
+ * expect(result).toBe(4)
378
+ * })
379
+ * ```
380
+ */
381
+ declare function testEffect<T = void>(fn: TestEffectCallback<T>): Promise<T>;
382
+ /**
383
+ * Wait for a condition to be true.
384
+ * This is a simple wrapper that can be used alongside testEffect.
385
+ *
386
+ * @param condition - A function that returns true when the condition is met
387
+ * @param options - Options for waiting (timeout, interval)
388
+ * @returns A promise that resolves when the condition is true
389
+ *
390
+ * @example
391
+ * ```ts
392
+ * await waitForCondition(() => element.textContent === 'Loaded')
393
+ * ```
394
+ */
395
+ declare function waitForCondition(condition: () => boolean, options?: {
396
+ timeout?: number;
397
+ interval?: number;
398
+ }): Promise<void>;
399
+ /**
400
+ * Flush pending microtasks and effects.
401
+ * Useful for ensuring all reactive updates have completed.
402
+ *
403
+ * @returns A promise that resolves after microtasks are flushed
404
+ */
405
+ declare function flush(): Promise<void>;
406
+ /**
407
+ * Run updates and flush pending microtasks/effects.
408
+ * Similar to React Testing Library's act().
409
+ *
410
+ * @param fn - A function that triggers updates (can be async)
411
+ * @returns The result of the function after flush
412
+ */
413
+ declare function act<T>(fn: () => T | Promise<T>): Promise<T>;
414
+ /**
415
+ * Render a view wrapped in an ErrorBoundary for testing error handling.
416
+ *
417
+ * The ErrorBoundary catches errors thrown during child component rendering
418
+ * and displays a fallback UI. Use this to test error handling behavior.
419
+ *
420
+ * @param view - A function that returns the component to render
421
+ * @param options - Render options including ErrorBoundary props
422
+ * @returns A render result with error boundary utilities
423
+ *
424
+ * @example
425
+ * ```tsx
426
+ * // Test error catching - component throws during render
427
+ * const onError = vi.fn()
428
+ * const { container, isShowingFallback } = renderWithErrorBoundary(
429
+ * () => <ComponentThatMightThrow />,
430
+ * {
431
+ * fallback: (err) => <div data-testid="error">{err.message}</div>,
432
+ * onError,
433
+ * }
434
+ * )
435
+ *
436
+ * // If component threw, fallback is shown
437
+ * if (isShowingFallback()) {
438
+ * expect(container.querySelector('[data-testid="error"]')).toBeTruthy()
439
+ * }
440
+ * ```
441
+ */
442
+ declare function renderWithErrorBoundary<Q extends Queries = typeof queries>(view: View, options?: ErrorBoundaryRenderOptions<Q>): ErrorBoundaryRenderResult<Q>;
443
+ /**
444
+ * Create a test suspense token for controlling suspense in tests.
445
+ *
446
+ * @returns A handle with token, resolve, and reject functions
447
+ *
448
+ * @example
449
+ * ```tsx
450
+ * const { token, resolve, reject } = createTestSuspenseToken()
451
+ *
452
+ * const { isShowingFallback, waitForResolution } = renderWithSuspense(
453
+ * () => {
454
+ * // Throw token to trigger suspense
455
+ * throw token
456
+ * },
457
+ * { fallback: <div>Loading...</div> }
458
+ * )
459
+ *
460
+ * expect(isShowingFallback()).toBe(true)
461
+ *
462
+ * // Resolve the suspense
463
+ * resolve()
464
+ * await waitForResolution()
465
+ *
466
+ * expect(isShowingFallback()).toBe(false)
467
+ * ```
468
+ */
469
+ declare function createTestSuspenseToken(): TestSuspenseHandle;
470
+ /**
471
+ * Render a view wrapped in a Suspense boundary for testing async loading states.
472
+ *
473
+ * Note: Suspense in Fict catches async tokens thrown during child rendering.
474
+ * To test suspense, use createTestSuspenseToken() and throw the token in your component.
475
+ *
476
+ * @param view - A function that returns the component to render
477
+ * @param options - Render options including Suspense props
478
+ * @returns A render result with suspense utilities
479
+ *
480
+ * @example
481
+ * ```tsx
482
+ * // Test non-suspending component
483
+ * const { container } = renderWithSuspense(
484
+ * () => <LoadedComponent />,
485
+ * { fallback: <div>Loading...</div> }
486
+ * )
487
+ * expect(container.textContent).toBe('Loaded')
488
+ * ```
489
+ */
490
+ declare function renderWithSuspense<Q extends Queries = typeof queries>(view: View, options?: SuspenseRenderOptions<Q>): SuspenseRenderResult<Q>;
491
+
492
+ export { type DebugFn, type ErrorBoundaryRenderOptions, type ErrorBoundaryRenderResult, type MountedRef, type RenderHookOptions, type RenderHookResult, type RenderOptions, type RenderResult, type SuspenseRenderOptions, type SuspenseRenderResult, type TestEffectCallback, type TestSuspenseHandle, type View, act, cleanup, createTestSuspenseToken, flush, render, renderHook, renderWithErrorBoundary, renderWithSuspense, testEffect, waitForCondition };