@kiwa-test/nextjs 1.0.5 → 1.2.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.ts CHANGED
@@ -225,4 +225,255 @@ interface InvokeParallelRoutesResult<TSlots extends string, TNode = unknown> {
225
225
  */
226
226
  declare function invokeParallelRoutes<TSlots extends string, TLayoutProps = Record<string, unknown>, TNode = unknown>(opts: InvokeParallelRoutesOptions<TSlots, TLayoutProps, TNode>): Promise<InvokeParallelRoutesResult<TSlots, TNode>>;
227
227
 
228
- export { type CookieJar, type DefaultFallbackComponent, FORBIDDEN_SYMBOL, type ForbiddenSignal, type InterceptionMatch, type InvokeMiddlewareOptions, type InvokeMiddlewareResult, type InvokeParallelRoutesOptions, type InvokeParallelRoutesResult, MIDDLEWARE_ACTION_SYMBOL, type MiddlewareAction, type MiddlewareActionKind, type MiddlewareEnv, type MiddlewareFunction, type MiddlewareRequest, NOT_FOUND_SYMBOL, type NotFoundSignal, PARALLEL_INTERCEPTION_SYMBOL, type ParallelLayoutChildren, type ParallelLayoutFunction, REDIRECT_SYMBOL, RSC_REDIRECT_SYMBOL, type RedirectSignal, type RenderServerComponentOptions, type RenderServerComponentResult, type RscElement, type RscNode, type RscRedirectSignal, type RscSignal, type ServerActionEnv, type ServerActionFunction, type ServerActionInvocation, type ServerActionResult, type SlotComponent, type SlotInput, type SlotRenderResult, findAll, invokeMiddleware, invokeParallelRoutes, invokeServerAction, middlewareActions, renderServerComponent, textContent };
228
+ declare const RSC_ERROR_BOUNDARY_SYMBOL: unique symbol;
229
+ interface RscErrorBoundarySignal {
230
+ readonly [RSC_ERROR_BOUNDARY_SYMBOL]: true;
231
+ readonly error: unknown;
232
+ }
233
+ /**
234
+ * An async source the helper consumes chunk-by-chunk. Each yielded value is
235
+ * one streaming frame; the helper appends it to `env.chunks` in arrival order
236
+ * and uses the last chunk as `env.resolved` once the source completes.
237
+ *
238
+ * Use a plain async generator for most cases:
239
+ *
240
+ * async function* source() {
241
+ * yield <Spinner />; // initial chunk
242
+ * yield <Skeleton rows={3} />; // partial data
243
+ * yield <Items list={data} />; // final resolved chunk
244
+ * }
245
+ */
246
+ type RscStreamSource = AsyncIterable<RscNode>;
247
+ interface SetupNextRscEnvOptions {
248
+ /**
249
+ * The async server component under test. If `dataSource` is omitted, the
250
+ * helper awaits this function once and treats its return value as the only
251
+ * (resolved) chunk — equivalent to a synchronous resolution.
252
+ *
253
+ * The component may throw to trigger the error boundary path. See
254
+ * `injectError` for the test-side variant.
255
+ */
256
+ readonly component?: (props: Record<string, unknown>) => Promise<RscNode> | RscNode;
257
+ /**
258
+ * Optional props forwarded to `component`. Defaults to `{}`.
259
+ */
260
+ readonly props?: Record<string, unknown>;
261
+ /**
262
+ * Explicit streaming source. When provided, the helper iterates this and
263
+ * ignores `component`. Useful when the production code already produces a
264
+ * stream and the test wants to feed a deterministic sequence.
265
+ */
266
+ readonly dataSource?: RscStreamSource;
267
+ /**
268
+ * Markup shown while the (first) chunk is pending. Captured as
269
+ * `env.fallback` so tests can assert that `<Suspense fallback={...}>`
270
+ * surfaces the right loading state before the data arrives.
271
+ */
272
+ readonly suspenseFallback?: RscNode;
273
+ /**
274
+ * Hard timeout (ms) for the whole stream. If the source has not completed
275
+ * by this deadline, the helper resolves with `env.timedOut = true` and the
276
+ * chunks collected so far. Default 5000ms.
277
+ */
278
+ readonly streamingTimeout?: number;
279
+ /**
280
+ * Test-side error injection. When set, the helper short-circuits before
281
+ * iterating the source and routes the error into `env.errorBoundary` —
282
+ * the same shape a production `error.tsx` boundary would see.
283
+ */
284
+ readonly injectError?: unknown;
285
+ }
286
+ interface SetupNextRscEnvResult {
287
+ /**
288
+ * Streaming chunks in arrival order. For a Suspense boundary, the first
289
+ * chunk is typically the fallback markup and the last chunk is the
290
+ * resolved subtree.
291
+ */
292
+ readonly chunks: RscNode[];
293
+ /**
294
+ * The fallback markup captured before the source produced its first
295
+ * non-fallback chunk. `null` when the test did not pass `suspenseFallback`
296
+ * or when the source resolved synchronously without an explicit fallback.
297
+ */
298
+ readonly fallback: RscNode | null;
299
+ /**
300
+ * The last chunk yielded by the source — the markup a real Next.js page
301
+ * would settle on after streaming finishes. `null` when the source threw
302
+ * or timed out before producing any chunk.
303
+ */
304
+ readonly resolved: RscNode | null;
305
+ /**
306
+ * Set when the component or source threw, or when `injectError` was
307
+ * provided. Mirrors the value a production `error.tsx` boundary receives.
308
+ * `null` for happy-path streams.
309
+ */
310
+ readonly errorBoundary: RscErrorBoundarySignal | null;
311
+ /**
312
+ * `true` when `streamingTimeout` elapsed before the source completed.
313
+ * `chunks` still contains any chunks that arrived before the deadline.
314
+ */
315
+ readonly timedOut: boolean;
316
+ }
317
+ /**
318
+ * Drive an async RSC stream through a single Suspense boundary and capture
319
+ * every chunk + the fallback + the resolved subtree + any error-boundary
320
+ * trigger. The helper is deterministic — chunks arrive in the order the
321
+ * source yields them, and the timeout is wall-clock-bounded so tests cannot
322
+ * hang on a stuck stream.
323
+ *
324
+ * Typical usage:
325
+ *
326
+ * const env = await setupNextRscEnv({
327
+ * dataSource: streamItems(),
328
+ * suspenseFallback: <Skeleton />,
329
+ * streamingTimeout: 1000,
330
+ * });
331
+ * expect(env.fallback).toEqual(<Skeleton />);
332
+ * expect(env.chunks).toHaveLength(3);
333
+ * expect(env.resolved).toEqual(<ItemList items={items} />);
334
+ * expect(env.errorBoundary).toBeNull();
335
+ * expect(env.timedOut).toBe(false);
336
+ */
337
+ declare function setupNextRscEnv(opts?: SetupNextRscEnvOptions): Promise<SetupNextRscEnvResult>;
338
+
339
+ /**
340
+ * Advanced Next.js semantics — target-neutral axis SSOT.
341
+ *
342
+ * The helpers model App Router, Pages Router, and Edge Runtime behavior as
343
+ * pure state machines. Tests can assert the neutral event while still seeing
344
+ * a target-specific dialect through providerEventName.
345
+ */
346
+ type NextTarget = 'app-router' | 'pages-router' | 'edge-runtime';
347
+ type NextAxis = 'server-action-advanced' | 'partial-prerendering' | 'interception-routes' | 'parallel-routes-advanced';
348
+ type NeutralEventName = 'action.form_submitted' | 'action.revalidate_path' | 'action.revalidate_tag' | 'action.redirected' | 'ppr.static_shell_rendered' | 'ppr.dynamic_hole_opened' | 'ppr.streaming_boundary_flushed' | 'ppr.completed' | 'intercept.current_segment' | 'intercept.parent_segment' | 'intercept.root_catchall' | 'intercept.modal_opened' | 'parallel.default_rendered' | 'parallel.loading_rendered' | 'parallel.error_boundary_captured' | 'parallel.slot_navigated';
349
+ interface AxisStep<TState extends string> {
350
+ neutralEvent: NeutralEventName;
351
+ providerEvent: string;
352
+ state: TState;
353
+ amountCents: number;
354
+ metadata: Record<string, string | number | boolean>;
355
+ }
356
+ declare function providerEventName(target: NextTarget, neutral: NeutralEventName): string;
357
+
358
+ type ServerActionAdvancedState = 'idle' | 'submitted' | 'path-revalidated' | 'tag-revalidated' | 'redirected';
359
+ interface ServerActionAdvancedSession {
360
+ target: NextTarget;
361
+ actionId: string;
362
+ state: ServerActionAdvancedState;
363
+ form: Record<string, string>;
364
+ revalidatedPaths: string[];
365
+ revalidatedTags: string[];
366
+ redirectUrl: string | null;
367
+ history: AxisStep<ServerActionAdvancedState>[];
368
+ }
369
+ declare function startServerActionAdvanced(input: {
370
+ target: NextTarget;
371
+ actionId: string;
372
+ }): ServerActionAdvancedSession;
373
+ declare function submitFormAction(session: ServerActionAdvancedSession, form: Record<string, string>): AxisStep<ServerActionAdvancedState>;
374
+ declare function revalidateActionPath(session: ServerActionAdvancedSession, path: string): AxisStep<ServerActionAdvancedState>;
375
+ declare function revalidateActionTag(session: ServerActionAdvancedSession, tag: string): AxisStep<ServerActionAdvancedState>;
376
+ declare function redirectAction(session: ServerActionAdvancedSession, url: string): AxisStep<ServerActionAdvancedState>;
377
+
378
+ type PartialPrerenderingState = 'idle' | 'static-shell' | 'dynamic-hole' | 'streaming' | 'completed';
379
+ interface PartialPrerenderingSession {
380
+ target: NextTarget;
381
+ routeId: string;
382
+ state: PartialPrerenderingState;
383
+ shellHtml: string | null;
384
+ dynamicHoles: Map<string, string>;
385
+ streamedBoundaries: string[];
386
+ history: AxisStep<PartialPrerenderingState>[];
387
+ }
388
+ declare function startPartialPrerendering(input: {
389
+ target: NextTarget;
390
+ routeId: string;
391
+ }): PartialPrerenderingSession;
392
+ declare function renderStaticShell(session: PartialPrerenderingSession, html: string): AxisStep<PartialPrerenderingState>;
393
+ declare function openDynamicHole(session: PartialPrerenderingSession, input: {
394
+ holeId: string;
395
+ fallback: string;
396
+ }): AxisStep<PartialPrerenderingState>;
397
+ declare function flushStreamingBoundary(session: PartialPrerenderingSession, input: {
398
+ holeId: string;
399
+ html: string;
400
+ }): AxisStep<PartialPrerenderingState>;
401
+ declare function completePartialPrerendering(session: PartialPrerenderingSession): AxisStep<PartialPrerenderingState>;
402
+
403
+ type InterceptionRoutesState = 'idle' | 'current' | 'parent' | 'root' | 'modal-open';
404
+ type InterceptionMatcher = '(.)' | '(..)' | '(...)';
405
+ interface InterceptionRoutesSession {
406
+ target: NextTarget;
407
+ routeId: string;
408
+ state: InterceptionRoutesState;
409
+ matches: Array<{
410
+ matcher: InterceptionMatcher;
411
+ from: string;
412
+ to: string;
413
+ }>;
414
+ modalRoute: string | null;
415
+ history: AxisStep<InterceptionRoutesState>[];
416
+ }
417
+ declare function startInterceptionRoutes(input: {
418
+ target: NextTarget;
419
+ routeId: string;
420
+ }): InterceptionRoutesSession;
421
+ declare function interceptCurrentSegment(session: InterceptionRoutesSession, from: string, to: string): AxisStep<InterceptionRoutesState>;
422
+ declare function interceptParentSegment(session: InterceptionRoutesSession, from: string, to: string): AxisStep<InterceptionRoutesState>;
423
+ declare function interceptRootCatchall(session: InterceptionRoutesSession, from: string, to: string): AxisStep<InterceptionRoutesState>;
424
+ declare function openInterceptedModal(session: InterceptionRoutesSession, modalRoute: string): AxisStep<InterceptionRoutesState>;
425
+
426
+ type ParallelRoutesAdvancedState = 'idle' | 'default-rendered' | 'loading-rendered' | 'error-captured' | 'slot-navigated';
427
+ interface ParallelRoutesAdvancedSession {
428
+ target: NextTarget;
429
+ layoutId: string;
430
+ state: ParallelRoutesAdvancedState;
431
+ slots: Map<string, string>;
432
+ loadingSlots: Set<string>;
433
+ errors: Array<{
434
+ slot: string;
435
+ message: string;
436
+ }>;
437
+ history: AxisStep<ParallelRoutesAdvancedState>[];
438
+ }
439
+ declare function startParallelRoutesAdvanced(input: {
440
+ target: NextTarget;
441
+ layoutId: string;
442
+ }): ParallelRoutesAdvancedSession;
443
+ declare function renderDefaultSlot(session: ParallelRoutesAdvancedSession, slot: string, html: string): AxisStep<ParallelRoutesAdvancedState>;
444
+ declare function renderLoadingState(session: ParallelRoutesAdvancedSession, slot: string): AxisStep<ParallelRoutesAdvancedState>;
445
+ declare function captureParallelError(session: ParallelRoutesAdvancedSession, input: {
446
+ slot: string;
447
+ error: Error | string;
448
+ }): AxisStep<ParallelRoutesAdvancedState>;
449
+ declare function navigateSlot(session: ParallelRoutesAdvancedSession, input: {
450
+ slot: string;
451
+ from: string;
452
+ to: string;
453
+ }): AxisStep<ParallelRoutesAdvancedState>;
454
+
455
+ interface FidelityRow {
456
+ provider: NextTarget;
457
+ axis: NextAxis;
458
+ neutralEvents: NeutralEventName[];
459
+ providerEvents: string[];
460
+ }
461
+ interface FidelityCoverage {
462
+ providers: NextTarget[];
463
+ axes: NextAxis[];
464
+ rows: FidelityRow[];
465
+ }
466
+ declare const NEXT_AXIS_TO_EVENTS: Record<NextAxis, NeutralEventName[]>;
467
+ declare function collectFidelityCoverage(providers?: NextTarget[]): FidelityCoverage;
468
+
469
+ type KiwaTestMode = 'mock' | 'real';
470
+ interface ResolvedMode {
471
+ mode: KiwaTestMode;
472
+ provider: NextTarget;
473
+ reason: 'default-mock' | 'kiwa-mode-real' | 'missing-key' | 'invalid-mode';
474
+ }
475
+ declare function resolveMode(provider: NextTarget, env?: Record<string, string | undefined>): ResolvedMode;
476
+ declare function resolveAllModes(env?: Record<string, string | undefined>): ResolvedMode[];
477
+ declare function assertMode(provider: NextTarget, expected: KiwaTestMode, env?: Record<string, string | undefined>): void;
478
+
479
+ export { type AxisStep, type CookieJar, type DefaultFallbackComponent, FORBIDDEN_SYMBOL, type FidelityCoverage, type FidelityRow, type ForbiddenSignal, type InterceptionMatch, type InterceptionMatcher, type InterceptionRoutesSession, type InterceptionRoutesState, type InvokeMiddlewareOptions, type InvokeMiddlewareResult, type InvokeParallelRoutesOptions, type InvokeParallelRoutesResult, type KiwaTestMode, MIDDLEWARE_ACTION_SYMBOL, type MiddlewareAction, type MiddlewareActionKind, type MiddlewareEnv, type MiddlewareFunction, type MiddlewareRequest, NEXT_AXIS_TO_EVENTS, NOT_FOUND_SYMBOL, type NeutralEventName, type NextAxis, type NextTarget, type NotFoundSignal, PARALLEL_INTERCEPTION_SYMBOL, type ParallelLayoutChildren, type ParallelLayoutFunction, type ParallelRoutesAdvancedSession, type ParallelRoutesAdvancedState, type PartialPrerenderingSession, type PartialPrerenderingState, REDIRECT_SYMBOL, RSC_ERROR_BOUNDARY_SYMBOL, RSC_REDIRECT_SYMBOL, type RedirectSignal, type RenderServerComponentOptions, type RenderServerComponentResult, type ResolvedMode, type RscElement, type RscErrorBoundarySignal, type RscNode, type RscRedirectSignal, type RscSignal, type RscStreamSource, type ServerActionAdvancedSession, type ServerActionAdvancedState, type ServerActionEnv, type ServerActionFunction, type ServerActionInvocation, type ServerActionResult, type SetupNextRscEnvOptions, type SetupNextRscEnvResult, type SlotComponent, type SlotInput, type SlotRenderResult, assertMode, captureParallelError, collectFidelityCoverage, completePartialPrerendering, findAll, flushStreamingBoundary, interceptCurrentSegment, interceptParentSegment, interceptRootCatchall, invokeMiddleware, invokeParallelRoutes, invokeServerAction, middlewareActions, navigateSlot, openDynamicHole, openInterceptedModal, providerEventName, redirectAction, renderDefaultSlot, renderLoadingState, renderServerComponent, renderStaticShell, resolveAllModes, resolveMode, revalidateActionPath, revalidateActionTag, setupNextRscEnv, startInterceptionRoutes, startParallelRoutesAdvanced, startPartialPrerendering, startServerActionAdvanced, submitFormAction, textContent };