@kiwa-test/nextjs 1.0.1 → 1.0.5

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.cjs CHANGED
@@ -20,8 +20,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ FORBIDDEN_SYMBOL: () => FORBIDDEN_SYMBOL,
24
+ MIDDLEWARE_ACTION_SYMBOL: () => MIDDLEWARE_ACTION_SYMBOL,
25
+ NOT_FOUND_SYMBOL: () => NOT_FOUND_SYMBOL,
26
+ PARALLEL_INTERCEPTION_SYMBOL: () => PARALLEL_INTERCEPTION_SYMBOL,
23
27
  REDIRECT_SYMBOL: () => REDIRECT_SYMBOL,
24
- invokeServerAction: () => invokeServerAction
28
+ RSC_REDIRECT_SYMBOL: () => RSC_REDIRECT_SYMBOL,
29
+ findAll: () => findAll,
30
+ invokeMiddleware: () => invokeMiddleware,
31
+ invokeParallelRoutes: () => invokeParallelRoutes,
32
+ invokeServerAction: () => invokeServerAction,
33
+ middlewareActions: () => middlewareActions,
34
+ renderServerComponent: () => renderServerComponent,
35
+ textContent: () => textContent
25
36
  });
26
37
  module.exports = __toCommonJS(index_exports);
27
38
 
@@ -72,9 +83,216 @@ async function invokeServerAction(opts) {
72
83
  }
73
84
  return { result, error, env };
74
85
  }
86
+
87
+ // src/invoke-middleware.ts
88
+ var MIDDLEWARE_ACTION_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.middleware.action");
89
+ function buildRequest(opts) {
90
+ const url = new URL(opts.url);
91
+ const headers = /* @__PURE__ */ new Map();
92
+ for (const [name, value] of Object.entries(opts.headers ?? {})) {
93
+ headers.set(name.toLowerCase(), value);
94
+ }
95
+ const cookies = new Map(Object.entries(opts.cookies ?? {}));
96
+ return {
97
+ url: opts.url,
98
+ method: opts.method ?? "GET",
99
+ headers,
100
+ cookies,
101
+ nextUrl: {
102
+ pathname: url.pathname,
103
+ search: url.search,
104
+ searchParams: url.searchParams
105
+ },
106
+ geo: opts.geo ?? {}
107
+ };
108
+ }
109
+ var middlewareActions = {
110
+ next() {
111
+ return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "next" };
112
+ },
113
+ redirect(url, status = 307) {
114
+ return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "redirect", url, status };
115
+ },
116
+ rewrite(url) {
117
+ return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "rewrite", url };
118
+ },
119
+ json(body, status = 200) {
120
+ return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "json", body, status };
121
+ }
122
+ };
123
+ async function invokeMiddleware(opts) {
124
+ const req = buildRequest(opts);
125
+ const responseHeaders = /* @__PURE__ */ new Map();
126
+ const responseCookies = /* @__PURE__ */ new Map();
127
+ const seam = {
128
+ setHeader(name, value) {
129
+ responseHeaders.set(name.toLowerCase(), value);
130
+ },
131
+ setCookie(name, value) {
132
+ responseCookies.set(name, value);
133
+ }
134
+ };
135
+ let action = middlewareActions.next();
136
+ let error;
137
+ try {
138
+ const result = await opts.middleware(req, seam);
139
+ action = result;
140
+ } catch (caught) {
141
+ error = caught;
142
+ action = { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "noop" };
143
+ }
144
+ return {
145
+ env: {
146
+ responseHeaders,
147
+ responseCookies,
148
+ action
149
+ },
150
+ error
151
+ };
152
+ }
153
+
154
+ // src/render-server-component.ts
155
+ var NOT_FOUND_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.rsc.notFound");
156
+ var FORBIDDEN_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.rsc.forbidden");
157
+ var RSC_REDIRECT_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.rsc.redirect");
158
+ function isRscElement(node) {
159
+ if (typeof node !== "object" || node === null) return false;
160
+ const candidate = node;
161
+ return typeof candidate.type !== "undefined" && typeof candidate.props === "object" && candidate.props !== null;
162
+ }
163
+ function isNotFound(value) {
164
+ return typeof value === "object" && value !== null && value[NOT_FOUND_SYMBOL] === true;
165
+ }
166
+ function isForbidden(value) {
167
+ return typeof value === "object" && value !== null && value[FORBIDDEN_SYMBOL] === true;
168
+ }
169
+ function isRscRedirect(value) {
170
+ return typeof value === "object" && value !== null && value[RSC_REDIRECT_SYMBOL] === true;
171
+ }
172
+ function findAll(tree, predicate) {
173
+ const out = [];
174
+ function visit(node) {
175
+ if (Array.isArray(node)) {
176
+ node.forEach(visit);
177
+ return;
178
+ }
179
+ if (!isRscElement(node)) return;
180
+ if (predicate(node)) out.push(node);
181
+ const children = node.props.children;
182
+ if (typeof children !== "undefined") {
183
+ visit(children);
184
+ }
185
+ }
186
+ visit(tree);
187
+ return out;
188
+ }
189
+ function textContent(tree) {
190
+ const parts = [];
191
+ function visit(node) {
192
+ if (Array.isArray(node)) {
193
+ node.forEach(visit);
194
+ return;
195
+ }
196
+ if (typeof node === "string" || typeof node === "number") {
197
+ parts.push(String(node));
198
+ return;
199
+ }
200
+ if (isRscElement(node)) {
201
+ const children = node.props.children;
202
+ if (typeof children !== "undefined") visit(children);
203
+ }
204
+ }
205
+ visit(tree);
206
+ return parts.filter((p) => p.length > 0).join(" ");
207
+ }
208
+ async function renderServerComponent(opts) {
209
+ let tree = null;
210
+ let signal = null;
211
+ let error;
212
+ const props = opts.props ?? {};
213
+ try {
214
+ const result = await opts.component(props);
215
+ tree = result;
216
+ } catch (caught) {
217
+ if (isNotFound(caught) || isForbidden(caught) || isRscRedirect(caught)) {
218
+ signal = caught;
219
+ } else {
220
+ error = caught;
221
+ }
222
+ }
223
+ return { tree, signal, error };
224
+ }
225
+
226
+ // src/invoke-parallel-routes.ts
227
+ var PARALLEL_INTERCEPTION_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.parallel.interception");
228
+ async function renderSlot(input) {
229
+ let tree = null;
230
+ let usedDefault = false;
231
+ let error;
232
+ const interception = input.intercepting ? {
233
+ [PARALLEL_INTERCEPTION_SYMBOL]: true,
234
+ slot: input.slot,
235
+ variant: input.intercepting.variant,
236
+ url: input.intercepting.url,
237
+ distance: input.intercepting.distance ?? "sibling"
238
+ } : null;
239
+ const useDefault = input.component === null || interception?.variant === "default";
240
+ try {
241
+ if (useDefault) {
242
+ if (typeof input.defaultFallback !== "function") {
243
+ throw new Error(`slot ${input.slot}: no default.tsx fallback supplied`);
244
+ }
245
+ tree = await input.defaultFallback();
246
+ usedDefault = true;
247
+ } else if (input.component !== null) {
248
+ tree = await input.component(input.props ?? {});
249
+ }
250
+ } catch (caught) {
251
+ error = caught;
252
+ }
253
+ return { slot: input.slot, tree, usedDefault, interception, error };
254
+ }
255
+ async function invokeParallelRoutes(opts) {
256
+ let childrenTree = null;
257
+ let childrenError;
258
+ let layoutError;
259
+ try {
260
+ childrenTree = await opts.children(opts.childrenProps ?? {});
261
+ } catch (caught) {
262
+ childrenError = caught;
263
+ }
264
+ const slotResults = await Promise.all(opts.slots.map((input) => renderSlot(input)));
265
+ const slotMap = {};
266
+ for (const result of slotResults) {
267
+ slotMap[result.slot] = result.tree;
268
+ }
269
+ let tree = null;
270
+ try {
271
+ const props = {
272
+ ...opts.layoutProps ?? {},
273
+ children: childrenTree,
274
+ slots: slotMap
275
+ };
276
+ tree = await opts.layout(props);
277
+ } catch (caught) {
278
+ layoutError = caught;
279
+ }
280
+ return { tree, slotResults, childrenError, layoutError };
281
+ }
75
282
  // Annotate the CommonJS export names for ESM import in node:
76
283
  0 && (module.exports = {
284
+ FORBIDDEN_SYMBOL,
285
+ MIDDLEWARE_ACTION_SYMBOL,
286
+ NOT_FOUND_SYMBOL,
287
+ PARALLEL_INTERCEPTION_SYMBOL,
77
288
  REDIRECT_SYMBOL,
78
- invokeServerAction
289
+ RSC_REDIRECT_SYMBOL,
290
+ findAll,
291
+ invokeMiddleware,
292
+ invokeParallelRoutes,
293
+ invokeServerAction,
294
+ middlewareActions,
295
+ renderServerComponent,
296
+ textContent
79
297
  });
80
298
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/invoke-server-action.ts"],"sourcesContent":["export {\n invokeServerAction,\n type ServerActionFunction,\n type ServerActionResult,\n type ServerActionInvocation,\n type ServerActionEnv,\n type CookieJar,\n REDIRECT_SYMBOL,\n type RedirectSignal,\n} from './invoke-server-action.js';\n","// Next.js Server Action invocation helper for kiwa tests.\n//\n// Wraps an `'use server'` async function and captures Next.js side-effects\n// (redirect / revalidatePath / cookies set) without requiring a real Next.js\n// runtime. The test calls `invokeServerAction({ action, formData, ... })`\n// and asserts on the returned `ServerActionResult` instead of relying on\n// integration-level behavior.\n//\n// Out of scope on purpose:\n// - real `next/server` middleware semantics (use #495 helper instead)\n// - RSC payload format (use #494 helper instead)\n// - rendering a page after the action (use Playwright + `/kiwa-e2e` for that)\n\nexport const REDIRECT_SYMBOL = Symbol.for('kiwa.next.redirect');\n\nexport interface RedirectSignal {\n readonly [REDIRECT_SYMBOL]: true;\n readonly url: string;\n readonly type: 'replace' | 'push';\n}\n\nexport interface CookieJar {\n get(name: string): string | undefined;\n set(name: string, value: string, options?: Record<string, unknown>): void;\n delete(name: string): void;\n entries(): Array<[string, string]>;\n}\n\nexport interface ServerActionEnv {\n readonly cookies: CookieJar;\n readonly headers: Map<string, string>;\n readonly revalidated: { paths: string[]; tags: string[] };\n readonly redirect: RedirectSignal | null;\n}\n\n// We deliberately accept `any[]` here so callers can pass typed actions like\n// `(fd: FormData) => Promise<T>` or `(prev: State, fd: FormData) => Promise<T>`\n// without contortions. The runtime always invokes `action(formData, ...args)`.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ServerActionFunction<TResult> = (...args: any[]) => Promise<TResult> | TResult;\n\nexport interface ServerActionInvocation<TResult> {\n /** The `'use server'` async function under test. */\n readonly action: ServerActionFunction<TResult>;\n /** Optional FormData first argument (default empty). */\n readonly formData?: FormData;\n /** Extra positional args appended after FormData (e.g. previous state for useFormState). */\n readonly args?: unknown[];\n /** Initial cookie jar entries (name → value). */\n readonly cookies?: Record<string, string>;\n /** Initial request headers (case-insensitive). */\n readonly headers?: Record<string, string>;\n}\n\nexport interface ServerActionResult<TResult> {\n /** Resolved return value (or `undefined` if the action threw a redirect signal). */\n readonly result: TResult | undefined;\n /** Error thrown by the action (excluding redirect signals which are normalized). */\n readonly error: unknown;\n /** Side-effects captured during the invocation. */\n readonly env: ServerActionEnv;\n}\n\nfunction createCookieJar(initial: Record<string, string>): CookieJar {\n const store = new Map<string, string>(Object.entries(initial));\n return {\n get(name) {\n return store.get(name);\n },\n set(name, value) {\n store.set(name, value);\n },\n delete(name) {\n store.delete(name);\n },\n entries() {\n return Array.from(store.entries());\n },\n };\n}\n\nfunction isRedirectSignal(value: unknown): value is RedirectSignal {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { [REDIRECT_SYMBOL]?: true })[REDIRECT_SYMBOL] === true\n );\n}\n\n/**\n * Invoke a Next.js Server Action in isolation and capture its side-effects.\n *\n * The action is called as `await action(formData, ...args)`. The kiwa helper\n * does NOT monkey-patch global `next/navigation` / `next/headers` / `next/cache`\n * imports. Instead the action under test should accept its dependencies via\n * an injectable seam (a parameter or a module-level setter) so tests stay\n * deterministic. See `examples/nextjs-server-actions-poc/` for the pattern.\n */\nexport async function invokeServerAction<TResult>(\n opts: ServerActionInvocation<TResult>,\n): Promise<ServerActionResult<TResult>> {\n const headers = new Map<string, string>();\n for (const [name, value] of Object.entries(opts.headers ?? {})) {\n headers.set(name.toLowerCase(), value);\n }\n const env: ServerActionEnv = {\n cookies: createCookieJar(opts.cookies ?? {}),\n headers,\n revalidated: { paths: [], tags: [] },\n redirect: null,\n };\n const callArgs: unknown[] = [opts.formData ?? new FormData(), ...(opts.args ?? [])];\n let result: TResult | undefined;\n let error: unknown;\n try {\n result = await opts.action(...callArgs);\n } catch (caught) {\n if (isRedirectSignal(caught)) {\n (env as { redirect: RedirectSignal | null }).redirect = caught;\n } else {\n error = caught;\n }\n }\n return { result, error, env };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACaO,IAAM,kBAAkB,uBAAO,IAAI,oBAAoB;AAkD9D,SAAS,gBAAgB,SAA4C;AACnE,QAAM,QAAQ,IAAI,IAAoB,OAAO,QAAQ,OAAO,CAAC;AAC7D,SAAO;AAAA,IACL,IAAI,MAAM;AACR,aAAO,MAAM,IAAI,IAAI;AAAA,IACvB;AAAA,IACA,IAAI,MAAM,OAAO;AACf,YAAM,IAAI,MAAM,KAAK;AAAA,IACvB;AAAA,IACA,OAAO,MAAM;AACX,YAAM,OAAO,IAAI;AAAA,IACnB;AAAA,IACA,UAAU;AACR,aAAO,MAAM,KAAK,MAAM,QAAQ,CAAC;AAAA,IACnC;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,OAAyC;AACjE,SACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAuC,eAAe,MAAM;AAEjE;AAWA,eAAsB,mBACpB,MACsC;AACtC,QAAM,UAAU,oBAAI,IAAoB;AACxC,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,GAAG;AAC9D,YAAQ,IAAI,KAAK,YAAY,GAAG,KAAK;AAAA,EACvC;AACA,QAAM,MAAuB;AAAA,IAC3B,SAAS,gBAAgB,KAAK,WAAW,CAAC,CAAC;AAAA,IAC3C;AAAA,IACA,aAAa,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,EAAE;AAAA,IACnC,UAAU;AAAA,EACZ;AACA,QAAM,WAAsB,CAAC,KAAK,YAAY,IAAI,SAAS,GAAG,GAAI,KAAK,QAAQ,CAAC,CAAE;AAClF,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,KAAK,OAAO,GAAG,QAAQ;AAAA,EACxC,SAAS,QAAQ;AACf,QAAI,iBAAiB,MAAM,GAAG;AAC5B,MAAC,IAA4C,WAAW;AAAA,IAC1D,OAAO;AACL,cAAQ;AAAA,IACV;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,OAAO,IAAI;AAC9B;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/invoke-server-action.ts","../src/invoke-middleware.ts","../src/render-server-component.ts","../src/invoke-parallel-routes.ts"],"sourcesContent":["export {\n invokeServerAction,\n type ServerActionFunction,\n type ServerActionResult,\n type ServerActionInvocation,\n type ServerActionEnv,\n type CookieJar,\n REDIRECT_SYMBOL,\n type RedirectSignal,\n} from './invoke-server-action.js';\n\nexport {\n invokeMiddleware,\n middlewareActions,\n MIDDLEWARE_ACTION_SYMBOL,\n type InvokeMiddlewareOptions,\n type InvokeMiddlewareResult,\n type MiddlewareFunction,\n type MiddlewareRequest,\n type MiddlewareEnv,\n type MiddlewareAction,\n type MiddlewareActionKind,\n} from './invoke-middleware.js';\n\nexport {\n renderServerComponent,\n findAll,\n textContent,\n NOT_FOUND_SYMBOL,\n FORBIDDEN_SYMBOL,\n RSC_REDIRECT_SYMBOL,\n type RenderServerComponentOptions,\n type RenderServerComponentResult,\n type RscElement,\n type RscNode,\n type RscSignal,\n type NotFoundSignal,\n type ForbiddenSignal,\n type RscRedirectSignal,\n} from './render-server-component.js';\n\nexport {\n invokeParallelRoutes,\n PARALLEL_INTERCEPTION_SYMBOL,\n type InvokeParallelRoutesOptions,\n type InvokeParallelRoutesResult,\n type ParallelLayoutFunction,\n type ParallelLayoutChildren,\n type SlotComponent,\n type SlotInput,\n type SlotRenderResult,\n type DefaultFallbackComponent,\n type InterceptionMatch,\n} from './invoke-parallel-routes.js';\n","// Next.js Server Action invocation helper for kiwa tests.\n//\n// Wraps an `'use server'` async function and captures Next.js side-effects\n// (redirect / revalidatePath / cookies set) without requiring a real Next.js\n// runtime. The test calls `invokeServerAction({ action, formData, ... })`\n// and asserts on the returned `ServerActionResult` instead of relying on\n// integration-level behavior.\n//\n// Out of scope on purpose:\n// - real `next/server` middleware semantics (use #495 helper instead)\n// - RSC payload format (use #494 helper instead)\n// - rendering a page after the action (use Playwright + `/kiwa-e2e` for that)\n\nexport const REDIRECT_SYMBOL = Symbol.for('kiwa.next.redirect');\n\nexport interface RedirectSignal {\n readonly [REDIRECT_SYMBOL]: true;\n readonly url: string;\n readonly type: 'replace' | 'push';\n}\n\nexport interface CookieJar {\n get(name: string): string | undefined;\n set(name: string, value: string, options?: Record<string, unknown>): void;\n delete(name: string): void;\n entries(): Array<[string, string]>;\n}\n\nexport interface ServerActionEnv {\n readonly cookies: CookieJar;\n readonly headers: Map<string, string>;\n readonly revalidated: { paths: string[]; tags: string[] };\n readonly redirect: RedirectSignal | null;\n}\n\n// We deliberately accept `any[]` here so callers can pass typed actions like\n// `(fd: FormData) => Promise<T>` or `(prev: State, fd: FormData) => Promise<T>`\n// without contortions. The runtime always invokes `action(formData, ...args)`.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ServerActionFunction<TResult> = (...args: any[]) => Promise<TResult> | TResult;\n\nexport interface ServerActionInvocation<TResult> {\n /** The `'use server'` async function under test. */\n readonly action: ServerActionFunction<TResult>;\n /** Optional FormData first argument (default empty). */\n readonly formData?: FormData;\n /** Extra positional args appended after FormData (e.g. previous state for useFormState). */\n readonly args?: unknown[];\n /** Initial cookie jar entries (name → value). */\n readonly cookies?: Record<string, string>;\n /** Initial request headers (case-insensitive). */\n readonly headers?: Record<string, string>;\n}\n\nexport interface ServerActionResult<TResult> {\n /** Resolved return value (or `undefined` if the action threw a redirect signal). */\n readonly result: TResult | undefined;\n /** Error thrown by the action (excluding redirect signals which are normalized). */\n readonly error: unknown;\n /** Side-effects captured during the invocation. */\n readonly env: ServerActionEnv;\n}\n\nfunction createCookieJar(initial: Record<string, string>): CookieJar {\n const store = new Map<string, string>(Object.entries(initial));\n return {\n get(name) {\n return store.get(name);\n },\n set(name, value) {\n store.set(name, value);\n },\n delete(name) {\n store.delete(name);\n },\n entries() {\n return Array.from(store.entries());\n },\n };\n}\n\nfunction isRedirectSignal(value: unknown): value is RedirectSignal {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { [REDIRECT_SYMBOL]?: true })[REDIRECT_SYMBOL] === true\n );\n}\n\n/**\n * Invoke a Next.js Server Action in isolation and capture its side-effects.\n *\n * The action is called as `await action(formData, ...args)`. The kiwa helper\n * does NOT monkey-patch global `next/navigation` / `next/headers` / `next/cache`\n * imports. Instead the action under test should accept its dependencies via\n * an injectable seam (a parameter or a module-level setter) so tests stay\n * deterministic. See `examples/nextjs-server-actions-poc/` for the pattern.\n */\nexport async function invokeServerAction<TResult>(\n opts: ServerActionInvocation<TResult>,\n): Promise<ServerActionResult<TResult>> {\n const headers = new Map<string, string>();\n for (const [name, value] of Object.entries(opts.headers ?? {})) {\n headers.set(name.toLowerCase(), value);\n }\n const env: ServerActionEnv = {\n cookies: createCookieJar(opts.cookies ?? {}),\n headers,\n revalidated: { paths: [], tags: [] },\n redirect: null,\n };\n const callArgs: unknown[] = [opts.formData ?? new FormData(), ...(opts.args ?? [])];\n let result: TResult | undefined;\n let error: unknown;\n try {\n result = await opts.action(...callArgs);\n } catch (caught) {\n if (isRedirectSignal(caught)) {\n (env as { redirect: RedirectSignal | null }).redirect = caught;\n } else {\n error = caught;\n }\n }\n return { result, error, env };\n}\n","// Next.js middleware.ts invocation helper for kiwa tests (Issue #495).\n//\n// Middleware in App Router is `(req: NextRequest) => NextResponse | Response | void`.\n// It can set response headers / cookies, throw redirect via NextResponse.redirect(),\n// rewrite via NextResponse.rewrite(), short-circuit with NextResponse.json(), or\n// pass through with NextResponse.next(). kiwa wraps the call so unit tests can\n// assert on the captured action + outgoing headers/cookies without a running\n// Next.js server.\n//\n// Out of scope on purpose:\n// - matcher config evaluation (the test already knows which middleware to call)\n// - revalidate / cache header semantics\n// - Edge runtime polyfill (use Node test environment + the helper's simulated request)\n\nexport const MIDDLEWARE_ACTION_SYMBOL = Symbol.for('kiwa.next.middleware.action');\n\nexport type MiddlewareActionKind = 'next' | 'redirect' | 'rewrite' | 'json' | 'noop';\n\nexport interface MiddlewareAction {\n readonly [MIDDLEWARE_ACTION_SYMBOL]: true;\n readonly kind: MiddlewareActionKind;\n readonly url?: string;\n readonly body?: unknown;\n readonly status?: number;\n}\n\nexport interface MiddlewareRequest {\n readonly url: string;\n readonly method: string;\n readonly headers: ReadonlyMap<string, string>;\n readonly cookies: ReadonlyMap<string, string>;\n readonly nextUrl: {\n readonly pathname: string;\n readonly search: string;\n readonly searchParams: URLSearchParams;\n };\n readonly geo: {\n readonly country?: string;\n readonly region?: string;\n readonly city?: string;\n };\n}\n\nexport interface MiddlewareEnv {\n readonly responseHeaders: Map<string, string>;\n readonly responseCookies: Map<string, string>;\n readonly action: MiddlewareAction;\n}\n\nexport type MiddlewareFunction = (\n req: MiddlewareRequest,\n env: { setHeader: (name: string, value: string) => void; setCookie: (name: string, value: string) => void },\n) => MiddlewareAction | Promise<MiddlewareAction>;\n\nexport interface InvokeMiddlewareOptions {\n readonly middleware: MiddlewareFunction;\n readonly url: string;\n readonly method?: string;\n readonly headers?: Record<string, string>;\n readonly cookies?: Record<string, string>;\n readonly geo?: {\n readonly country?: string;\n readonly region?: string;\n readonly city?: string;\n };\n}\n\nexport interface InvokeMiddlewareResult {\n readonly env: MiddlewareEnv;\n readonly error: unknown;\n}\n\nfunction buildRequest(opts: InvokeMiddlewareOptions): MiddlewareRequest {\n const url = new URL(opts.url);\n const headers = new Map<string, string>();\n for (const [name, value] of Object.entries(opts.headers ?? {})) {\n headers.set(name.toLowerCase(), value);\n }\n const cookies = new Map<string, string>(Object.entries(opts.cookies ?? {}));\n return {\n url: opts.url,\n method: opts.method ?? 'GET',\n headers,\n cookies,\n nextUrl: {\n pathname: url.pathname,\n search: url.search,\n searchParams: url.searchParams,\n },\n geo: opts.geo ?? {},\n };\n}\n\n/**\n * Helpers your `middleware.ts` returns instead of constructing NextResponse\n * directly. Keep the production code shape close by re-exporting these from\n * a shared module; the helper expects the returned value to be a\n * MiddlewareAction shaped object.\n */\nexport const middlewareActions = {\n next(): MiddlewareAction {\n return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: 'next' };\n },\n redirect(url: string, status = 307): MiddlewareAction {\n return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: 'redirect', url, status };\n },\n rewrite(url: string): MiddlewareAction {\n return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: 'rewrite', url };\n },\n json(body: unknown, status = 200): MiddlewareAction {\n return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: 'json', body, status };\n },\n};\n\n/**\n * Invoke a middleware function in isolation and capture its outgoing\n * response shape + headers + cookies. Mirrors the kiwa style of\n * invokeServerAction: no globals, no real Next.js runtime.\n */\nexport async function invokeMiddleware(opts: InvokeMiddlewareOptions): Promise<InvokeMiddlewareResult> {\n const req = buildRequest(opts);\n const responseHeaders = new Map<string, string>();\n const responseCookies = new Map<string, string>();\n const seam = {\n setHeader(name: string, value: string) {\n responseHeaders.set(name.toLowerCase(), value);\n },\n setCookie(name: string, value: string) {\n responseCookies.set(name, value);\n },\n };\n let action: MiddlewareAction = middlewareActions.next();\n let error: unknown;\n try {\n const result = await opts.middleware(req, seam);\n action = result;\n } catch (caught) {\n error = caught;\n action = { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: 'noop' };\n }\n return {\n env: {\n responseHeaders,\n responseCookies,\n action,\n },\n error,\n };\n}\n","// Next.js async React Server Component (RSC) test helper for kiwa (Issue #494).\n//\n// Real RSC rendering involves a streaming runtime + a flight payload format\n// that requires `vitest-environment-rsc` or a Next.js dev server. kiwa takes\n// a lighter approach: treat the async server component as `async (props) =>\n// JSX.Element` and await it directly. The returned React element tree is\n// inspected with a small finder API instead of a full DOM.\n//\n// Out of scope:\n// - flight payload serialization\n// - client component boundary (`'use client'`)\n// - suspense streaming (synchronous resolution only)\n// - dangerous HTML rendering — tests assert on element shape, not strings\n\nexport const NOT_FOUND_SYMBOL = Symbol.for('kiwa.next.rsc.notFound');\nexport const FORBIDDEN_SYMBOL = Symbol.for('kiwa.next.rsc.forbidden');\nexport const RSC_REDIRECT_SYMBOL = Symbol.for('kiwa.next.rsc.redirect');\n\nexport interface NotFoundSignal {\n readonly [NOT_FOUND_SYMBOL]: true;\n}\nexport interface ForbiddenSignal {\n readonly [FORBIDDEN_SYMBOL]: true;\n}\nexport interface RscRedirectSignal {\n readonly [RSC_REDIRECT_SYMBOL]: true;\n readonly url: string;\n readonly type: 'replace' | 'push';\n}\n\nexport type RscSignal = NotFoundSignal | ForbiddenSignal | RscRedirectSignal;\n\nexport interface RscElement {\n readonly type: string | symbol | ((props: Record<string, unknown>) => unknown);\n readonly props: Record<string, unknown>;\n readonly key: string | null;\n}\n\nexport type RscNode = RscElement | string | number | boolean | null | undefined | RscNode[];\n\nexport interface RenderServerComponentOptions<TProps> {\n readonly component: (props: TProps) => Promise<RscNode> | RscNode;\n readonly props?: TProps;\n}\n\nexport interface RenderServerComponentResult {\n readonly tree: RscNode;\n readonly signal: RscSignal | null;\n readonly error: unknown;\n}\n\nfunction isRscElement(node: unknown): node is RscElement {\n if (typeof node !== 'object' || node === null) return false;\n const candidate = node as { type?: unknown; props?: unknown };\n return (\n typeof candidate.type !== 'undefined' &&\n typeof candidate.props === 'object' &&\n candidate.props !== null\n );\n}\n\nfunction isNotFound(value: unknown): value is NotFoundSignal {\n return typeof value === 'object' && value !== null && (value as { [NOT_FOUND_SYMBOL]?: true })[NOT_FOUND_SYMBOL] === true;\n}\nfunction isForbidden(value: unknown): value is ForbiddenSignal {\n return typeof value === 'object' && value !== null && (value as { [FORBIDDEN_SYMBOL]?: true })[FORBIDDEN_SYMBOL] === true;\n}\nfunction isRscRedirect(value: unknown): value is RscRedirectSignal {\n return typeof value === 'object' && value !== null && (value as { [RSC_REDIRECT_SYMBOL]?: true })[RSC_REDIRECT_SYMBOL] === true;\n}\n\n/**\n * Recursively walk an RSC tree and collect every node that satisfies the\n * predicate. Children are read from `props.children` and are normalized to a\n * flat array regardless of how the component spelled them.\n */\nexport function findAll(tree: RscNode, predicate: (node: RscElement) => boolean): RscElement[] {\n const out: RscElement[] = [];\n function visit(node: RscNode): void {\n if (Array.isArray(node)) {\n node.forEach(visit);\n return;\n }\n if (!isRscElement(node)) return;\n if (predicate(node)) out.push(node);\n const children = node.props.children;\n if (typeof children !== 'undefined') {\n visit(children as RscNode);\n }\n }\n visit(tree);\n return out;\n}\n\n/**\n * Concatenate every string/number leaf of an RSC tree, joined by a single\n * space. Useful for `expect(textContent(tree)).toContain('hello')` style\n * assertions where the exact element structure does not matter.\n */\nexport function textContent(tree: RscNode): string {\n const parts: string[] = [];\n function visit(node: RscNode): void {\n if (Array.isArray(node)) {\n node.forEach(visit);\n return;\n }\n if (typeof node === 'string' || typeof node === 'number') {\n parts.push(String(node));\n return;\n }\n if (isRscElement(node)) {\n const children = node.props.children;\n if (typeof children !== 'undefined') visit(children as RscNode);\n }\n }\n visit(tree);\n return parts.filter((p) => p.length > 0).join(' ');\n}\n\n/**\n * Invoke an async server component in isolation and capture its return tree.\n * Throws of `notFound() / forbidden() / redirect()` from `next/navigation`\n * should be replaced with the kiwa signals below (Pattern A from the\n * server-action seam doc); the helper normalizes them into `result.signal`\n * instead of leaving them as `result.error`.\n */\nexport async function renderServerComponent<TProps = Record<string, unknown>>(\n opts: RenderServerComponentOptions<TProps>,\n): Promise<RenderServerComponentResult> {\n let tree: RscNode = null;\n let signal: RscSignal | null = null;\n let error: unknown;\n const props = (opts.props ?? ({} as TProps)) as TProps;\n try {\n const result = await opts.component(props);\n tree = result;\n } catch (caught) {\n if (isNotFound(caught) || isForbidden(caught) || isRscRedirect(caught)) {\n signal = caught;\n } else {\n error = caught;\n }\n }\n return { tree, signal, error };\n}\n","// Next.js App Router Parallel Routes test helper for kiwa (Issue #523).\n//\n// Parallel Routes let an App Router layout render multiple async components in\n// named slots: `layout({ children, @modal, @sidebar })`. Each slot may also\n// provide a `default.tsx` fallback when no matching segment is present, and\n// the URL pattern `(.)`/`(..)`/`(...)` enables Intercepting Routes that swap\n// the slot rendering based on soft-vs-hard navigation.\n//\n// kiwa renders the layout in isolation with synthetic slot trees. The helper\n// awaits all slot promises in parallel (mirroring the React parallel-render\n// semantics), supplies optional Intercepting-Route resolution, and captures\n// errors per slot so a single failing slot does not collapse the whole tree.\n//\n// Out of scope on purpose:\n// - real flight payload / React renderer (use `renderServerComponent` for\n// leaf-level RSC introspection)\n// - matcher / `loading.tsx` / `error.tsx` evaluation\n// - client component boundary (`'use client'`)\n\nexport const PARALLEL_INTERCEPTION_SYMBOL = Symbol.for('kiwa.next.parallel.interception');\n\nexport interface InterceptionMatch<TSlot extends string> {\n readonly [PARALLEL_INTERCEPTION_SYMBOL]: true;\n readonly slot: TSlot;\n readonly variant: 'intercepted' | 'default';\n readonly url: string;\n readonly distance: 'sibling' | 'parent' | 'root';\n}\n\nexport type SlotComponent<TProps = Record<string, unknown>, TNode = unknown> = (\n props: TProps,\n) => Promise<TNode> | TNode;\n\nexport type DefaultFallbackComponent<TNode = unknown> = () => Promise<TNode> | TNode;\n\nexport interface ParallelLayoutChildren<TSlots extends string, TNode = unknown> {\n readonly children: TNode;\n readonly slots: Readonly<Record<TSlots, TNode>>;\n}\n\nexport type ParallelLayoutFunction<TSlots extends string, TLayoutProps, TNode = unknown> = (\n props: TLayoutProps & ParallelLayoutChildren<TSlots, TNode>,\n) => Promise<TNode> | TNode;\n\nexport interface SlotInput<TSlots extends string, TNode = unknown> {\n readonly slot: TSlots;\n readonly component: SlotComponent<Record<string, unknown>, TNode> | null;\n readonly props?: Record<string, unknown>;\n readonly defaultFallback?: DefaultFallbackComponent<TNode>;\n readonly intercepting?: {\n readonly variant: 'intercepted' | 'default';\n readonly url: string;\n readonly distance?: 'sibling' | 'parent' | 'root';\n };\n}\n\nexport interface InvokeParallelRoutesOptions<TSlots extends string, TLayoutProps, TNode = unknown> {\n readonly layout: ParallelLayoutFunction<TSlots, TLayoutProps, TNode>;\n readonly children: SlotComponent<Record<string, unknown>, TNode>;\n readonly childrenProps?: Record<string, unknown>;\n readonly slots: ReadonlyArray<SlotInput<TSlots, TNode>>;\n readonly layoutProps?: TLayoutProps;\n}\n\nexport interface SlotRenderResult<TSlots extends string, TNode = unknown> {\n readonly slot: TSlots;\n readonly tree: TNode | null;\n readonly usedDefault: boolean;\n readonly interception: InterceptionMatch<TSlots> | null;\n readonly error: unknown;\n}\n\nexport interface InvokeParallelRoutesResult<TSlots extends string, TNode = unknown> {\n readonly tree: TNode | null;\n readonly slotResults: ReadonlyArray<SlotRenderResult<TSlots, TNode>>;\n readonly childrenError: unknown;\n readonly layoutError: unknown;\n}\n\nasync function renderSlot<TSlots extends string, TNode>(\n input: SlotInput<TSlots, TNode>,\n): Promise<SlotRenderResult<TSlots, TNode>> {\n let tree: TNode | null = null;\n let usedDefault = false;\n let error: unknown;\n const interception: InterceptionMatch<TSlots> | null = input.intercepting\n ? {\n [PARALLEL_INTERCEPTION_SYMBOL]: true,\n slot: input.slot,\n variant: input.intercepting.variant,\n url: input.intercepting.url,\n distance: input.intercepting.distance ?? 'sibling',\n }\n : null;\n const useDefault =\n input.component === null ||\n interception?.variant === 'default';\n try {\n if (useDefault) {\n if (typeof input.defaultFallback !== 'function') {\n throw new Error(`slot ${input.slot}: no default.tsx fallback supplied`);\n }\n tree = await input.defaultFallback();\n usedDefault = true;\n } else if (input.component !== null) {\n tree = await input.component(input.props ?? {});\n }\n } catch (caught) {\n error = caught;\n }\n return { slot: input.slot, tree, usedDefault, interception, error };\n}\n\n/**\n * Invoke an App Router parallel-routes layout in isolation. All slot\n * components are rendered in parallel (Promise.all) so a slow slot cannot\n * block fast siblings; per-slot errors are captured into `slotResults`\n * without aborting the layout render.\n */\nexport async function invokeParallelRoutes<\n TSlots extends string,\n TLayoutProps = Record<string, unknown>,\n TNode = unknown,\n>(\n opts: InvokeParallelRoutesOptions<TSlots, TLayoutProps, TNode>,\n): Promise<InvokeParallelRoutesResult<TSlots, TNode>> {\n let childrenTree: TNode | null = null;\n let childrenError: unknown;\n let layoutError: unknown;\n try {\n childrenTree = await opts.children(opts.childrenProps ?? {});\n } catch (caught) {\n childrenError = caught;\n }\n const slotResults = await Promise.all(opts.slots.map((input) => renderSlot(input)));\n const slotMap: Record<string, TNode | null> = {};\n for (const result of slotResults) {\n slotMap[result.slot] = result.tree;\n }\n let tree: TNode | null = null;\n try {\n const props = {\n ...((opts.layoutProps ?? {}) as TLayoutProps),\n children: childrenTree as TNode,\n slots: slotMap as Readonly<Record<TSlots, TNode>>,\n } as TLayoutProps & ParallelLayoutChildren<TSlots, TNode>;\n tree = await opts.layout(props);\n } catch (caught) {\n layoutError = caught;\n }\n return { tree, slotResults, childrenError, layoutError };\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -51,4 +51,178 @@ interface ServerActionResult<TResult> {
51
51
  */
52
52
  declare function invokeServerAction<TResult>(opts: ServerActionInvocation<TResult>): Promise<ServerActionResult<TResult>>;
53
53
 
54
- export { type CookieJar, REDIRECT_SYMBOL, type RedirectSignal, type ServerActionEnv, type ServerActionFunction, type ServerActionInvocation, type ServerActionResult, invokeServerAction };
54
+ declare const MIDDLEWARE_ACTION_SYMBOL: unique symbol;
55
+ type MiddlewareActionKind = 'next' | 'redirect' | 'rewrite' | 'json' | 'noop';
56
+ interface MiddlewareAction {
57
+ readonly [MIDDLEWARE_ACTION_SYMBOL]: true;
58
+ readonly kind: MiddlewareActionKind;
59
+ readonly url?: string;
60
+ readonly body?: unknown;
61
+ readonly status?: number;
62
+ }
63
+ interface MiddlewareRequest {
64
+ readonly url: string;
65
+ readonly method: string;
66
+ readonly headers: ReadonlyMap<string, string>;
67
+ readonly cookies: ReadonlyMap<string, string>;
68
+ readonly nextUrl: {
69
+ readonly pathname: string;
70
+ readonly search: string;
71
+ readonly searchParams: URLSearchParams;
72
+ };
73
+ readonly geo: {
74
+ readonly country?: string;
75
+ readonly region?: string;
76
+ readonly city?: string;
77
+ };
78
+ }
79
+ interface MiddlewareEnv {
80
+ readonly responseHeaders: Map<string, string>;
81
+ readonly responseCookies: Map<string, string>;
82
+ readonly action: MiddlewareAction;
83
+ }
84
+ type MiddlewareFunction = (req: MiddlewareRequest, env: {
85
+ setHeader: (name: string, value: string) => void;
86
+ setCookie: (name: string, value: string) => void;
87
+ }) => MiddlewareAction | Promise<MiddlewareAction>;
88
+ interface InvokeMiddlewareOptions {
89
+ readonly middleware: MiddlewareFunction;
90
+ readonly url: string;
91
+ readonly method?: string;
92
+ readonly headers?: Record<string, string>;
93
+ readonly cookies?: Record<string, string>;
94
+ readonly geo?: {
95
+ readonly country?: string;
96
+ readonly region?: string;
97
+ readonly city?: string;
98
+ };
99
+ }
100
+ interface InvokeMiddlewareResult {
101
+ readonly env: MiddlewareEnv;
102
+ readonly error: unknown;
103
+ }
104
+ /**
105
+ * Helpers your `middleware.ts` returns instead of constructing NextResponse
106
+ * directly. Keep the production code shape close by re-exporting these from
107
+ * a shared module; the helper expects the returned value to be a
108
+ * MiddlewareAction shaped object.
109
+ */
110
+ declare const middlewareActions: {
111
+ next(): MiddlewareAction;
112
+ redirect(url: string, status?: number): MiddlewareAction;
113
+ rewrite(url: string): MiddlewareAction;
114
+ json(body: unknown, status?: number): MiddlewareAction;
115
+ };
116
+ /**
117
+ * Invoke a middleware function in isolation and capture its outgoing
118
+ * response shape + headers + cookies. Mirrors the kiwa style of
119
+ * invokeServerAction: no globals, no real Next.js runtime.
120
+ */
121
+ declare function invokeMiddleware(opts: InvokeMiddlewareOptions): Promise<InvokeMiddlewareResult>;
122
+
123
+ declare const NOT_FOUND_SYMBOL: unique symbol;
124
+ declare const FORBIDDEN_SYMBOL: unique symbol;
125
+ declare const RSC_REDIRECT_SYMBOL: unique symbol;
126
+ interface NotFoundSignal {
127
+ readonly [NOT_FOUND_SYMBOL]: true;
128
+ }
129
+ interface ForbiddenSignal {
130
+ readonly [FORBIDDEN_SYMBOL]: true;
131
+ }
132
+ interface RscRedirectSignal {
133
+ readonly [RSC_REDIRECT_SYMBOL]: true;
134
+ readonly url: string;
135
+ readonly type: 'replace' | 'push';
136
+ }
137
+ type RscSignal = NotFoundSignal | ForbiddenSignal | RscRedirectSignal;
138
+ interface RscElement {
139
+ readonly type: string | symbol | ((props: Record<string, unknown>) => unknown);
140
+ readonly props: Record<string, unknown>;
141
+ readonly key: string | null;
142
+ }
143
+ type RscNode = RscElement | string | number | boolean | null | undefined | RscNode[];
144
+ interface RenderServerComponentOptions<TProps> {
145
+ readonly component: (props: TProps) => Promise<RscNode> | RscNode;
146
+ readonly props?: TProps;
147
+ }
148
+ interface RenderServerComponentResult {
149
+ readonly tree: RscNode;
150
+ readonly signal: RscSignal | null;
151
+ readonly error: unknown;
152
+ }
153
+ /**
154
+ * Recursively walk an RSC tree and collect every node that satisfies the
155
+ * predicate. Children are read from `props.children` and are normalized to a
156
+ * flat array regardless of how the component spelled them.
157
+ */
158
+ declare function findAll(tree: RscNode, predicate: (node: RscElement) => boolean): RscElement[];
159
+ /**
160
+ * Concatenate every string/number leaf of an RSC tree, joined by a single
161
+ * space. Useful for `expect(textContent(tree)).toContain('hello')` style
162
+ * assertions where the exact element structure does not matter.
163
+ */
164
+ declare function textContent(tree: RscNode): string;
165
+ /**
166
+ * Invoke an async server component in isolation and capture its return tree.
167
+ * Throws of `notFound() / forbidden() / redirect()` from `next/navigation`
168
+ * should be replaced with the kiwa signals below (Pattern A from the
169
+ * server-action seam doc); the helper normalizes them into `result.signal`
170
+ * instead of leaving them as `result.error`.
171
+ */
172
+ declare function renderServerComponent<TProps = Record<string, unknown>>(opts: RenderServerComponentOptions<TProps>): Promise<RenderServerComponentResult>;
173
+
174
+ declare const PARALLEL_INTERCEPTION_SYMBOL: unique symbol;
175
+ interface InterceptionMatch<TSlot extends string> {
176
+ readonly [PARALLEL_INTERCEPTION_SYMBOL]: true;
177
+ readonly slot: TSlot;
178
+ readonly variant: 'intercepted' | 'default';
179
+ readonly url: string;
180
+ readonly distance: 'sibling' | 'parent' | 'root';
181
+ }
182
+ type SlotComponent<TProps = Record<string, unknown>, TNode = unknown> = (props: TProps) => Promise<TNode> | TNode;
183
+ type DefaultFallbackComponent<TNode = unknown> = () => Promise<TNode> | TNode;
184
+ interface ParallelLayoutChildren<TSlots extends string, TNode = unknown> {
185
+ readonly children: TNode;
186
+ readonly slots: Readonly<Record<TSlots, TNode>>;
187
+ }
188
+ type ParallelLayoutFunction<TSlots extends string, TLayoutProps, TNode = unknown> = (props: TLayoutProps & ParallelLayoutChildren<TSlots, TNode>) => Promise<TNode> | TNode;
189
+ interface SlotInput<TSlots extends string, TNode = unknown> {
190
+ readonly slot: TSlots;
191
+ readonly component: SlotComponent<Record<string, unknown>, TNode> | null;
192
+ readonly props?: Record<string, unknown>;
193
+ readonly defaultFallback?: DefaultFallbackComponent<TNode>;
194
+ readonly intercepting?: {
195
+ readonly variant: 'intercepted' | 'default';
196
+ readonly url: string;
197
+ readonly distance?: 'sibling' | 'parent' | 'root';
198
+ };
199
+ }
200
+ interface InvokeParallelRoutesOptions<TSlots extends string, TLayoutProps, TNode = unknown> {
201
+ readonly layout: ParallelLayoutFunction<TSlots, TLayoutProps, TNode>;
202
+ readonly children: SlotComponent<Record<string, unknown>, TNode>;
203
+ readonly childrenProps?: Record<string, unknown>;
204
+ readonly slots: ReadonlyArray<SlotInput<TSlots, TNode>>;
205
+ readonly layoutProps?: TLayoutProps;
206
+ }
207
+ interface SlotRenderResult<TSlots extends string, TNode = unknown> {
208
+ readonly slot: TSlots;
209
+ readonly tree: TNode | null;
210
+ readonly usedDefault: boolean;
211
+ readonly interception: InterceptionMatch<TSlots> | null;
212
+ readonly error: unknown;
213
+ }
214
+ interface InvokeParallelRoutesResult<TSlots extends string, TNode = unknown> {
215
+ readonly tree: TNode | null;
216
+ readonly slotResults: ReadonlyArray<SlotRenderResult<TSlots, TNode>>;
217
+ readonly childrenError: unknown;
218
+ readonly layoutError: unknown;
219
+ }
220
+ /**
221
+ * Invoke an App Router parallel-routes layout in isolation. All slot
222
+ * components are rendered in parallel (Promise.all) so a slow slot cannot
223
+ * block fast siblings; per-slot errors are captured into `slotResults`
224
+ * without aborting the layout render.
225
+ */
226
+ declare function invokeParallelRoutes<TSlots extends string, TLayoutProps = Record<string, unknown>, TNode = unknown>(opts: InvokeParallelRoutesOptions<TSlots, TLayoutProps, TNode>): Promise<InvokeParallelRoutesResult<TSlots, TNode>>;
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 };
package/dist/index.d.ts CHANGED
@@ -51,4 +51,178 @@ interface ServerActionResult<TResult> {
51
51
  */
52
52
  declare function invokeServerAction<TResult>(opts: ServerActionInvocation<TResult>): Promise<ServerActionResult<TResult>>;
53
53
 
54
- export { type CookieJar, REDIRECT_SYMBOL, type RedirectSignal, type ServerActionEnv, type ServerActionFunction, type ServerActionInvocation, type ServerActionResult, invokeServerAction };
54
+ declare const MIDDLEWARE_ACTION_SYMBOL: unique symbol;
55
+ type MiddlewareActionKind = 'next' | 'redirect' | 'rewrite' | 'json' | 'noop';
56
+ interface MiddlewareAction {
57
+ readonly [MIDDLEWARE_ACTION_SYMBOL]: true;
58
+ readonly kind: MiddlewareActionKind;
59
+ readonly url?: string;
60
+ readonly body?: unknown;
61
+ readonly status?: number;
62
+ }
63
+ interface MiddlewareRequest {
64
+ readonly url: string;
65
+ readonly method: string;
66
+ readonly headers: ReadonlyMap<string, string>;
67
+ readonly cookies: ReadonlyMap<string, string>;
68
+ readonly nextUrl: {
69
+ readonly pathname: string;
70
+ readonly search: string;
71
+ readonly searchParams: URLSearchParams;
72
+ };
73
+ readonly geo: {
74
+ readonly country?: string;
75
+ readonly region?: string;
76
+ readonly city?: string;
77
+ };
78
+ }
79
+ interface MiddlewareEnv {
80
+ readonly responseHeaders: Map<string, string>;
81
+ readonly responseCookies: Map<string, string>;
82
+ readonly action: MiddlewareAction;
83
+ }
84
+ type MiddlewareFunction = (req: MiddlewareRequest, env: {
85
+ setHeader: (name: string, value: string) => void;
86
+ setCookie: (name: string, value: string) => void;
87
+ }) => MiddlewareAction | Promise<MiddlewareAction>;
88
+ interface InvokeMiddlewareOptions {
89
+ readonly middleware: MiddlewareFunction;
90
+ readonly url: string;
91
+ readonly method?: string;
92
+ readonly headers?: Record<string, string>;
93
+ readonly cookies?: Record<string, string>;
94
+ readonly geo?: {
95
+ readonly country?: string;
96
+ readonly region?: string;
97
+ readonly city?: string;
98
+ };
99
+ }
100
+ interface InvokeMiddlewareResult {
101
+ readonly env: MiddlewareEnv;
102
+ readonly error: unknown;
103
+ }
104
+ /**
105
+ * Helpers your `middleware.ts` returns instead of constructing NextResponse
106
+ * directly. Keep the production code shape close by re-exporting these from
107
+ * a shared module; the helper expects the returned value to be a
108
+ * MiddlewareAction shaped object.
109
+ */
110
+ declare const middlewareActions: {
111
+ next(): MiddlewareAction;
112
+ redirect(url: string, status?: number): MiddlewareAction;
113
+ rewrite(url: string): MiddlewareAction;
114
+ json(body: unknown, status?: number): MiddlewareAction;
115
+ };
116
+ /**
117
+ * Invoke a middleware function in isolation and capture its outgoing
118
+ * response shape + headers + cookies. Mirrors the kiwa style of
119
+ * invokeServerAction: no globals, no real Next.js runtime.
120
+ */
121
+ declare function invokeMiddleware(opts: InvokeMiddlewareOptions): Promise<InvokeMiddlewareResult>;
122
+
123
+ declare const NOT_FOUND_SYMBOL: unique symbol;
124
+ declare const FORBIDDEN_SYMBOL: unique symbol;
125
+ declare const RSC_REDIRECT_SYMBOL: unique symbol;
126
+ interface NotFoundSignal {
127
+ readonly [NOT_FOUND_SYMBOL]: true;
128
+ }
129
+ interface ForbiddenSignal {
130
+ readonly [FORBIDDEN_SYMBOL]: true;
131
+ }
132
+ interface RscRedirectSignal {
133
+ readonly [RSC_REDIRECT_SYMBOL]: true;
134
+ readonly url: string;
135
+ readonly type: 'replace' | 'push';
136
+ }
137
+ type RscSignal = NotFoundSignal | ForbiddenSignal | RscRedirectSignal;
138
+ interface RscElement {
139
+ readonly type: string | symbol | ((props: Record<string, unknown>) => unknown);
140
+ readonly props: Record<string, unknown>;
141
+ readonly key: string | null;
142
+ }
143
+ type RscNode = RscElement | string | number | boolean | null | undefined | RscNode[];
144
+ interface RenderServerComponentOptions<TProps> {
145
+ readonly component: (props: TProps) => Promise<RscNode> | RscNode;
146
+ readonly props?: TProps;
147
+ }
148
+ interface RenderServerComponentResult {
149
+ readonly tree: RscNode;
150
+ readonly signal: RscSignal | null;
151
+ readonly error: unknown;
152
+ }
153
+ /**
154
+ * Recursively walk an RSC tree and collect every node that satisfies the
155
+ * predicate. Children are read from `props.children` and are normalized to a
156
+ * flat array regardless of how the component spelled them.
157
+ */
158
+ declare function findAll(tree: RscNode, predicate: (node: RscElement) => boolean): RscElement[];
159
+ /**
160
+ * Concatenate every string/number leaf of an RSC tree, joined by a single
161
+ * space. Useful for `expect(textContent(tree)).toContain('hello')` style
162
+ * assertions where the exact element structure does not matter.
163
+ */
164
+ declare function textContent(tree: RscNode): string;
165
+ /**
166
+ * Invoke an async server component in isolation and capture its return tree.
167
+ * Throws of `notFound() / forbidden() / redirect()` from `next/navigation`
168
+ * should be replaced with the kiwa signals below (Pattern A from the
169
+ * server-action seam doc); the helper normalizes them into `result.signal`
170
+ * instead of leaving them as `result.error`.
171
+ */
172
+ declare function renderServerComponent<TProps = Record<string, unknown>>(opts: RenderServerComponentOptions<TProps>): Promise<RenderServerComponentResult>;
173
+
174
+ declare const PARALLEL_INTERCEPTION_SYMBOL: unique symbol;
175
+ interface InterceptionMatch<TSlot extends string> {
176
+ readonly [PARALLEL_INTERCEPTION_SYMBOL]: true;
177
+ readonly slot: TSlot;
178
+ readonly variant: 'intercepted' | 'default';
179
+ readonly url: string;
180
+ readonly distance: 'sibling' | 'parent' | 'root';
181
+ }
182
+ type SlotComponent<TProps = Record<string, unknown>, TNode = unknown> = (props: TProps) => Promise<TNode> | TNode;
183
+ type DefaultFallbackComponent<TNode = unknown> = () => Promise<TNode> | TNode;
184
+ interface ParallelLayoutChildren<TSlots extends string, TNode = unknown> {
185
+ readonly children: TNode;
186
+ readonly slots: Readonly<Record<TSlots, TNode>>;
187
+ }
188
+ type ParallelLayoutFunction<TSlots extends string, TLayoutProps, TNode = unknown> = (props: TLayoutProps & ParallelLayoutChildren<TSlots, TNode>) => Promise<TNode> | TNode;
189
+ interface SlotInput<TSlots extends string, TNode = unknown> {
190
+ readonly slot: TSlots;
191
+ readonly component: SlotComponent<Record<string, unknown>, TNode> | null;
192
+ readonly props?: Record<string, unknown>;
193
+ readonly defaultFallback?: DefaultFallbackComponent<TNode>;
194
+ readonly intercepting?: {
195
+ readonly variant: 'intercepted' | 'default';
196
+ readonly url: string;
197
+ readonly distance?: 'sibling' | 'parent' | 'root';
198
+ };
199
+ }
200
+ interface InvokeParallelRoutesOptions<TSlots extends string, TLayoutProps, TNode = unknown> {
201
+ readonly layout: ParallelLayoutFunction<TSlots, TLayoutProps, TNode>;
202
+ readonly children: SlotComponent<Record<string, unknown>, TNode>;
203
+ readonly childrenProps?: Record<string, unknown>;
204
+ readonly slots: ReadonlyArray<SlotInput<TSlots, TNode>>;
205
+ readonly layoutProps?: TLayoutProps;
206
+ }
207
+ interface SlotRenderResult<TSlots extends string, TNode = unknown> {
208
+ readonly slot: TSlots;
209
+ readonly tree: TNode | null;
210
+ readonly usedDefault: boolean;
211
+ readonly interception: InterceptionMatch<TSlots> | null;
212
+ readonly error: unknown;
213
+ }
214
+ interface InvokeParallelRoutesResult<TSlots extends string, TNode = unknown> {
215
+ readonly tree: TNode | null;
216
+ readonly slotResults: ReadonlyArray<SlotRenderResult<TSlots, TNode>>;
217
+ readonly childrenError: unknown;
218
+ readonly layoutError: unknown;
219
+ }
220
+ /**
221
+ * Invoke an App Router parallel-routes layout in isolation. All slot
222
+ * components are rendered in parallel (Promise.all) so a slow slot cannot
223
+ * block fast siblings; per-slot errors are captured into `slotResults`
224
+ * without aborting the layout render.
225
+ */
226
+ declare function invokeParallelRoutes<TSlots extends string, TLayoutProps = Record<string, unknown>, TNode = unknown>(opts: InvokeParallelRoutesOptions<TSlots, TLayoutProps, TNode>): Promise<InvokeParallelRoutesResult<TSlots, TNode>>;
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 };
package/dist/index.js CHANGED
@@ -45,8 +45,215 @@ async function invokeServerAction(opts) {
45
45
  }
46
46
  return { result, error, env };
47
47
  }
48
+
49
+ // src/invoke-middleware.ts
50
+ var MIDDLEWARE_ACTION_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.middleware.action");
51
+ function buildRequest(opts) {
52
+ const url = new URL(opts.url);
53
+ const headers = /* @__PURE__ */ new Map();
54
+ for (const [name, value] of Object.entries(opts.headers ?? {})) {
55
+ headers.set(name.toLowerCase(), value);
56
+ }
57
+ const cookies = new Map(Object.entries(opts.cookies ?? {}));
58
+ return {
59
+ url: opts.url,
60
+ method: opts.method ?? "GET",
61
+ headers,
62
+ cookies,
63
+ nextUrl: {
64
+ pathname: url.pathname,
65
+ search: url.search,
66
+ searchParams: url.searchParams
67
+ },
68
+ geo: opts.geo ?? {}
69
+ };
70
+ }
71
+ var middlewareActions = {
72
+ next() {
73
+ return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "next" };
74
+ },
75
+ redirect(url, status = 307) {
76
+ return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "redirect", url, status };
77
+ },
78
+ rewrite(url) {
79
+ return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "rewrite", url };
80
+ },
81
+ json(body, status = 200) {
82
+ return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "json", body, status };
83
+ }
84
+ };
85
+ async function invokeMiddleware(opts) {
86
+ const req = buildRequest(opts);
87
+ const responseHeaders = /* @__PURE__ */ new Map();
88
+ const responseCookies = /* @__PURE__ */ new Map();
89
+ const seam = {
90
+ setHeader(name, value) {
91
+ responseHeaders.set(name.toLowerCase(), value);
92
+ },
93
+ setCookie(name, value) {
94
+ responseCookies.set(name, value);
95
+ }
96
+ };
97
+ let action = middlewareActions.next();
98
+ let error;
99
+ try {
100
+ const result = await opts.middleware(req, seam);
101
+ action = result;
102
+ } catch (caught) {
103
+ error = caught;
104
+ action = { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "noop" };
105
+ }
106
+ return {
107
+ env: {
108
+ responseHeaders,
109
+ responseCookies,
110
+ action
111
+ },
112
+ error
113
+ };
114
+ }
115
+
116
+ // src/render-server-component.ts
117
+ var NOT_FOUND_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.rsc.notFound");
118
+ var FORBIDDEN_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.rsc.forbidden");
119
+ var RSC_REDIRECT_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.rsc.redirect");
120
+ function isRscElement(node) {
121
+ if (typeof node !== "object" || node === null) return false;
122
+ const candidate = node;
123
+ return typeof candidate.type !== "undefined" && typeof candidate.props === "object" && candidate.props !== null;
124
+ }
125
+ function isNotFound(value) {
126
+ return typeof value === "object" && value !== null && value[NOT_FOUND_SYMBOL] === true;
127
+ }
128
+ function isForbidden(value) {
129
+ return typeof value === "object" && value !== null && value[FORBIDDEN_SYMBOL] === true;
130
+ }
131
+ function isRscRedirect(value) {
132
+ return typeof value === "object" && value !== null && value[RSC_REDIRECT_SYMBOL] === true;
133
+ }
134
+ function findAll(tree, predicate) {
135
+ const out = [];
136
+ function visit(node) {
137
+ if (Array.isArray(node)) {
138
+ node.forEach(visit);
139
+ return;
140
+ }
141
+ if (!isRscElement(node)) return;
142
+ if (predicate(node)) out.push(node);
143
+ const children = node.props.children;
144
+ if (typeof children !== "undefined") {
145
+ visit(children);
146
+ }
147
+ }
148
+ visit(tree);
149
+ return out;
150
+ }
151
+ function textContent(tree) {
152
+ const parts = [];
153
+ function visit(node) {
154
+ if (Array.isArray(node)) {
155
+ node.forEach(visit);
156
+ return;
157
+ }
158
+ if (typeof node === "string" || typeof node === "number") {
159
+ parts.push(String(node));
160
+ return;
161
+ }
162
+ if (isRscElement(node)) {
163
+ const children = node.props.children;
164
+ if (typeof children !== "undefined") visit(children);
165
+ }
166
+ }
167
+ visit(tree);
168
+ return parts.filter((p) => p.length > 0).join(" ");
169
+ }
170
+ async function renderServerComponent(opts) {
171
+ let tree = null;
172
+ let signal = null;
173
+ let error;
174
+ const props = opts.props ?? {};
175
+ try {
176
+ const result = await opts.component(props);
177
+ tree = result;
178
+ } catch (caught) {
179
+ if (isNotFound(caught) || isForbidden(caught) || isRscRedirect(caught)) {
180
+ signal = caught;
181
+ } else {
182
+ error = caught;
183
+ }
184
+ }
185
+ return { tree, signal, error };
186
+ }
187
+
188
+ // src/invoke-parallel-routes.ts
189
+ var PARALLEL_INTERCEPTION_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.parallel.interception");
190
+ async function renderSlot(input) {
191
+ let tree = null;
192
+ let usedDefault = false;
193
+ let error;
194
+ const interception = input.intercepting ? {
195
+ [PARALLEL_INTERCEPTION_SYMBOL]: true,
196
+ slot: input.slot,
197
+ variant: input.intercepting.variant,
198
+ url: input.intercepting.url,
199
+ distance: input.intercepting.distance ?? "sibling"
200
+ } : null;
201
+ const useDefault = input.component === null || interception?.variant === "default";
202
+ try {
203
+ if (useDefault) {
204
+ if (typeof input.defaultFallback !== "function") {
205
+ throw new Error(`slot ${input.slot}: no default.tsx fallback supplied`);
206
+ }
207
+ tree = await input.defaultFallback();
208
+ usedDefault = true;
209
+ } else if (input.component !== null) {
210
+ tree = await input.component(input.props ?? {});
211
+ }
212
+ } catch (caught) {
213
+ error = caught;
214
+ }
215
+ return { slot: input.slot, tree, usedDefault, interception, error };
216
+ }
217
+ async function invokeParallelRoutes(opts) {
218
+ let childrenTree = null;
219
+ let childrenError;
220
+ let layoutError;
221
+ try {
222
+ childrenTree = await opts.children(opts.childrenProps ?? {});
223
+ } catch (caught) {
224
+ childrenError = caught;
225
+ }
226
+ const slotResults = await Promise.all(opts.slots.map((input) => renderSlot(input)));
227
+ const slotMap = {};
228
+ for (const result of slotResults) {
229
+ slotMap[result.slot] = result.tree;
230
+ }
231
+ let tree = null;
232
+ try {
233
+ const props = {
234
+ ...opts.layoutProps ?? {},
235
+ children: childrenTree,
236
+ slots: slotMap
237
+ };
238
+ tree = await opts.layout(props);
239
+ } catch (caught) {
240
+ layoutError = caught;
241
+ }
242
+ return { tree, slotResults, childrenError, layoutError };
243
+ }
48
244
  export {
245
+ FORBIDDEN_SYMBOL,
246
+ MIDDLEWARE_ACTION_SYMBOL,
247
+ NOT_FOUND_SYMBOL,
248
+ PARALLEL_INTERCEPTION_SYMBOL,
49
249
  REDIRECT_SYMBOL,
50
- invokeServerAction
250
+ RSC_REDIRECT_SYMBOL,
251
+ findAll,
252
+ invokeMiddleware,
253
+ invokeParallelRoutes,
254
+ invokeServerAction,
255
+ middlewareActions,
256
+ renderServerComponent,
257
+ textContent
51
258
  };
52
259
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/invoke-server-action.ts"],"sourcesContent":["// Next.js Server Action invocation helper for kiwa tests.\n//\n// Wraps an `'use server'` async function and captures Next.js side-effects\n// (redirect / revalidatePath / cookies set) without requiring a real Next.js\n// runtime. The test calls `invokeServerAction({ action, formData, ... })`\n// and asserts on the returned `ServerActionResult` instead of relying on\n// integration-level behavior.\n//\n// Out of scope on purpose:\n// - real `next/server` middleware semantics (use #495 helper instead)\n// - RSC payload format (use #494 helper instead)\n// - rendering a page after the action (use Playwright + `/kiwa-e2e` for that)\n\nexport const REDIRECT_SYMBOL = Symbol.for('kiwa.next.redirect');\n\nexport interface RedirectSignal {\n readonly [REDIRECT_SYMBOL]: true;\n readonly url: string;\n readonly type: 'replace' | 'push';\n}\n\nexport interface CookieJar {\n get(name: string): string | undefined;\n set(name: string, value: string, options?: Record<string, unknown>): void;\n delete(name: string): void;\n entries(): Array<[string, string]>;\n}\n\nexport interface ServerActionEnv {\n readonly cookies: CookieJar;\n readonly headers: Map<string, string>;\n readonly revalidated: { paths: string[]; tags: string[] };\n readonly redirect: RedirectSignal | null;\n}\n\n// We deliberately accept `any[]` here so callers can pass typed actions like\n// `(fd: FormData) => Promise<T>` or `(prev: State, fd: FormData) => Promise<T>`\n// without contortions. The runtime always invokes `action(formData, ...args)`.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ServerActionFunction<TResult> = (...args: any[]) => Promise<TResult> | TResult;\n\nexport interface ServerActionInvocation<TResult> {\n /** The `'use server'` async function under test. */\n readonly action: ServerActionFunction<TResult>;\n /** Optional FormData first argument (default empty). */\n readonly formData?: FormData;\n /** Extra positional args appended after FormData (e.g. previous state for useFormState). */\n readonly args?: unknown[];\n /** Initial cookie jar entries (name → value). */\n readonly cookies?: Record<string, string>;\n /** Initial request headers (case-insensitive). */\n readonly headers?: Record<string, string>;\n}\n\nexport interface ServerActionResult<TResult> {\n /** Resolved return value (or `undefined` if the action threw a redirect signal). */\n readonly result: TResult | undefined;\n /** Error thrown by the action (excluding redirect signals which are normalized). */\n readonly error: unknown;\n /** Side-effects captured during the invocation. */\n readonly env: ServerActionEnv;\n}\n\nfunction createCookieJar(initial: Record<string, string>): CookieJar {\n const store = new Map<string, string>(Object.entries(initial));\n return {\n get(name) {\n return store.get(name);\n },\n set(name, value) {\n store.set(name, value);\n },\n delete(name) {\n store.delete(name);\n },\n entries() {\n return Array.from(store.entries());\n },\n };\n}\n\nfunction isRedirectSignal(value: unknown): value is RedirectSignal {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { [REDIRECT_SYMBOL]?: true })[REDIRECT_SYMBOL] === true\n );\n}\n\n/**\n * Invoke a Next.js Server Action in isolation and capture its side-effects.\n *\n * The action is called as `await action(formData, ...args)`. The kiwa helper\n * does NOT monkey-patch global `next/navigation` / `next/headers` / `next/cache`\n * imports. Instead the action under test should accept its dependencies via\n * an injectable seam (a parameter or a module-level setter) so tests stay\n * deterministic. See `examples/nextjs-server-actions-poc/` for the pattern.\n */\nexport async function invokeServerAction<TResult>(\n opts: ServerActionInvocation<TResult>,\n): Promise<ServerActionResult<TResult>> {\n const headers = new Map<string, string>();\n for (const [name, value] of Object.entries(opts.headers ?? {})) {\n headers.set(name.toLowerCase(), value);\n }\n const env: ServerActionEnv = {\n cookies: createCookieJar(opts.cookies ?? {}),\n headers,\n revalidated: { paths: [], tags: [] },\n redirect: null,\n };\n const callArgs: unknown[] = [opts.formData ?? new FormData(), ...(opts.args ?? [])];\n let result: TResult | undefined;\n let error: unknown;\n try {\n result = await opts.action(...callArgs);\n } catch (caught) {\n if (isRedirectSignal(caught)) {\n (env as { redirect: RedirectSignal | null }).redirect = caught;\n } else {\n error = caught;\n }\n }\n return { result, error, env };\n}\n"],"mappings":";AAaO,IAAM,kBAAkB,uBAAO,IAAI,oBAAoB;AAkD9D,SAAS,gBAAgB,SAA4C;AACnE,QAAM,QAAQ,IAAI,IAAoB,OAAO,QAAQ,OAAO,CAAC;AAC7D,SAAO;AAAA,IACL,IAAI,MAAM;AACR,aAAO,MAAM,IAAI,IAAI;AAAA,IACvB;AAAA,IACA,IAAI,MAAM,OAAO;AACf,YAAM,IAAI,MAAM,KAAK;AAAA,IACvB;AAAA,IACA,OAAO,MAAM;AACX,YAAM,OAAO,IAAI;AAAA,IACnB;AAAA,IACA,UAAU;AACR,aAAO,MAAM,KAAK,MAAM,QAAQ,CAAC;AAAA,IACnC;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,OAAyC;AACjE,SACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAuC,eAAe,MAAM;AAEjE;AAWA,eAAsB,mBACpB,MACsC;AACtC,QAAM,UAAU,oBAAI,IAAoB;AACxC,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,GAAG;AAC9D,YAAQ,IAAI,KAAK,YAAY,GAAG,KAAK;AAAA,EACvC;AACA,QAAM,MAAuB;AAAA,IAC3B,SAAS,gBAAgB,KAAK,WAAW,CAAC,CAAC;AAAA,IAC3C;AAAA,IACA,aAAa,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,EAAE;AAAA,IACnC,UAAU;AAAA,EACZ;AACA,QAAM,WAAsB,CAAC,KAAK,YAAY,IAAI,SAAS,GAAG,GAAI,KAAK,QAAQ,CAAC,CAAE;AAClF,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,KAAK,OAAO,GAAG,QAAQ;AAAA,EACxC,SAAS,QAAQ;AACf,QAAI,iBAAiB,MAAM,GAAG;AAC5B,MAAC,IAA4C,WAAW;AAAA,IAC1D,OAAO;AACL,cAAQ;AAAA,IACV;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,OAAO,IAAI;AAC9B;","names":[]}
1
+ {"version":3,"sources":["../src/invoke-server-action.ts","../src/invoke-middleware.ts","../src/render-server-component.ts","../src/invoke-parallel-routes.ts"],"sourcesContent":["// Next.js Server Action invocation helper for kiwa tests.\n//\n// Wraps an `'use server'` async function and captures Next.js side-effects\n// (redirect / revalidatePath / cookies set) without requiring a real Next.js\n// runtime. The test calls `invokeServerAction({ action, formData, ... })`\n// and asserts on the returned `ServerActionResult` instead of relying on\n// integration-level behavior.\n//\n// Out of scope on purpose:\n// - real `next/server` middleware semantics (use #495 helper instead)\n// - RSC payload format (use #494 helper instead)\n// - rendering a page after the action (use Playwright + `/kiwa-e2e` for that)\n\nexport const REDIRECT_SYMBOL = Symbol.for('kiwa.next.redirect');\n\nexport interface RedirectSignal {\n readonly [REDIRECT_SYMBOL]: true;\n readonly url: string;\n readonly type: 'replace' | 'push';\n}\n\nexport interface CookieJar {\n get(name: string): string | undefined;\n set(name: string, value: string, options?: Record<string, unknown>): void;\n delete(name: string): void;\n entries(): Array<[string, string]>;\n}\n\nexport interface ServerActionEnv {\n readonly cookies: CookieJar;\n readonly headers: Map<string, string>;\n readonly revalidated: { paths: string[]; tags: string[] };\n readonly redirect: RedirectSignal | null;\n}\n\n// We deliberately accept `any[]` here so callers can pass typed actions like\n// `(fd: FormData) => Promise<T>` or `(prev: State, fd: FormData) => Promise<T>`\n// without contortions. The runtime always invokes `action(formData, ...args)`.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ServerActionFunction<TResult> = (...args: any[]) => Promise<TResult> | TResult;\n\nexport interface ServerActionInvocation<TResult> {\n /** The `'use server'` async function under test. */\n readonly action: ServerActionFunction<TResult>;\n /** Optional FormData first argument (default empty). */\n readonly formData?: FormData;\n /** Extra positional args appended after FormData (e.g. previous state for useFormState). */\n readonly args?: unknown[];\n /** Initial cookie jar entries (name → value). */\n readonly cookies?: Record<string, string>;\n /** Initial request headers (case-insensitive). */\n readonly headers?: Record<string, string>;\n}\n\nexport interface ServerActionResult<TResult> {\n /** Resolved return value (or `undefined` if the action threw a redirect signal). */\n readonly result: TResult | undefined;\n /** Error thrown by the action (excluding redirect signals which are normalized). */\n readonly error: unknown;\n /** Side-effects captured during the invocation. */\n readonly env: ServerActionEnv;\n}\n\nfunction createCookieJar(initial: Record<string, string>): CookieJar {\n const store = new Map<string, string>(Object.entries(initial));\n return {\n get(name) {\n return store.get(name);\n },\n set(name, value) {\n store.set(name, value);\n },\n delete(name) {\n store.delete(name);\n },\n entries() {\n return Array.from(store.entries());\n },\n };\n}\n\nfunction isRedirectSignal(value: unknown): value is RedirectSignal {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { [REDIRECT_SYMBOL]?: true })[REDIRECT_SYMBOL] === true\n );\n}\n\n/**\n * Invoke a Next.js Server Action in isolation and capture its side-effects.\n *\n * The action is called as `await action(formData, ...args)`. The kiwa helper\n * does NOT monkey-patch global `next/navigation` / `next/headers` / `next/cache`\n * imports. Instead the action under test should accept its dependencies via\n * an injectable seam (a parameter or a module-level setter) so tests stay\n * deterministic. See `examples/nextjs-server-actions-poc/` for the pattern.\n */\nexport async function invokeServerAction<TResult>(\n opts: ServerActionInvocation<TResult>,\n): Promise<ServerActionResult<TResult>> {\n const headers = new Map<string, string>();\n for (const [name, value] of Object.entries(opts.headers ?? {})) {\n headers.set(name.toLowerCase(), value);\n }\n const env: ServerActionEnv = {\n cookies: createCookieJar(opts.cookies ?? {}),\n headers,\n revalidated: { paths: [], tags: [] },\n redirect: null,\n };\n const callArgs: unknown[] = [opts.formData ?? new FormData(), ...(opts.args ?? [])];\n let result: TResult | undefined;\n let error: unknown;\n try {\n result = await opts.action(...callArgs);\n } catch (caught) {\n if (isRedirectSignal(caught)) {\n (env as { redirect: RedirectSignal | null }).redirect = caught;\n } else {\n error = caught;\n }\n }\n return { result, error, env };\n}\n","// Next.js middleware.ts invocation helper for kiwa tests (Issue #495).\n//\n// Middleware in App Router is `(req: NextRequest) => NextResponse | Response | void`.\n// It can set response headers / cookies, throw redirect via NextResponse.redirect(),\n// rewrite via NextResponse.rewrite(), short-circuit with NextResponse.json(), or\n// pass through with NextResponse.next(). kiwa wraps the call so unit tests can\n// assert on the captured action + outgoing headers/cookies without a running\n// Next.js server.\n//\n// Out of scope on purpose:\n// - matcher config evaluation (the test already knows which middleware to call)\n// - revalidate / cache header semantics\n// - Edge runtime polyfill (use Node test environment + the helper's simulated request)\n\nexport const MIDDLEWARE_ACTION_SYMBOL = Symbol.for('kiwa.next.middleware.action');\n\nexport type MiddlewareActionKind = 'next' | 'redirect' | 'rewrite' | 'json' | 'noop';\n\nexport interface MiddlewareAction {\n readonly [MIDDLEWARE_ACTION_SYMBOL]: true;\n readonly kind: MiddlewareActionKind;\n readonly url?: string;\n readonly body?: unknown;\n readonly status?: number;\n}\n\nexport interface MiddlewareRequest {\n readonly url: string;\n readonly method: string;\n readonly headers: ReadonlyMap<string, string>;\n readonly cookies: ReadonlyMap<string, string>;\n readonly nextUrl: {\n readonly pathname: string;\n readonly search: string;\n readonly searchParams: URLSearchParams;\n };\n readonly geo: {\n readonly country?: string;\n readonly region?: string;\n readonly city?: string;\n };\n}\n\nexport interface MiddlewareEnv {\n readonly responseHeaders: Map<string, string>;\n readonly responseCookies: Map<string, string>;\n readonly action: MiddlewareAction;\n}\n\nexport type MiddlewareFunction = (\n req: MiddlewareRequest,\n env: { setHeader: (name: string, value: string) => void; setCookie: (name: string, value: string) => void },\n) => MiddlewareAction | Promise<MiddlewareAction>;\n\nexport interface InvokeMiddlewareOptions {\n readonly middleware: MiddlewareFunction;\n readonly url: string;\n readonly method?: string;\n readonly headers?: Record<string, string>;\n readonly cookies?: Record<string, string>;\n readonly geo?: {\n readonly country?: string;\n readonly region?: string;\n readonly city?: string;\n };\n}\n\nexport interface InvokeMiddlewareResult {\n readonly env: MiddlewareEnv;\n readonly error: unknown;\n}\n\nfunction buildRequest(opts: InvokeMiddlewareOptions): MiddlewareRequest {\n const url = new URL(opts.url);\n const headers = new Map<string, string>();\n for (const [name, value] of Object.entries(opts.headers ?? {})) {\n headers.set(name.toLowerCase(), value);\n }\n const cookies = new Map<string, string>(Object.entries(opts.cookies ?? {}));\n return {\n url: opts.url,\n method: opts.method ?? 'GET',\n headers,\n cookies,\n nextUrl: {\n pathname: url.pathname,\n search: url.search,\n searchParams: url.searchParams,\n },\n geo: opts.geo ?? {},\n };\n}\n\n/**\n * Helpers your `middleware.ts` returns instead of constructing NextResponse\n * directly. Keep the production code shape close by re-exporting these from\n * a shared module; the helper expects the returned value to be a\n * MiddlewareAction shaped object.\n */\nexport const middlewareActions = {\n next(): MiddlewareAction {\n return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: 'next' };\n },\n redirect(url: string, status = 307): MiddlewareAction {\n return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: 'redirect', url, status };\n },\n rewrite(url: string): MiddlewareAction {\n return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: 'rewrite', url };\n },\n json(body: unknown, status = 200): MiddlewareAction {\n return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: 'json', body, status };\n },\n};\n\n/**\n * Invoke a middleware function in isolation and capture its outgoing\n * response shape + headers + cookies. Mirrors the kiwa style of\n * invokeServerAction: no globals, no real Next.js runtime.\n */\nexport async function invokeMiddleware(opts: InvokeMiddlewareOptions): Promise<InvokeMiddlewareResult> {\n const req = buildRequest(opts);\n const responseHeaders = new Map<string, string>();\n const responseCookies = new Map<string, string>();\n const seam = {\n setHeader(name: string, value: string) {\n responseHeaders.set(name.toLowerCase(), value);\n },\n setCookie(name: string, value: string) {\n responseCookies.set(name, value);\n },\n };\n let action: MiddlewareAction = middlewareActions.next();\n let error: unknown;\n try {\n const result = await opts.middleware(req, seam);\n action = result;\n } catch (caught) {\n error = caught;\n action = { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: 'noop' };\n }\n return {\n env: {\n responseHeaders,\n responseCookies,\n action,\n },\n error,\n };\n}\n","// Next.js async React Server Component (RSC) test helper for kiwa (Issue #494).\n//\n// Real RSC rendering involves a streaming runtime + a flight payload format\n// that requires `vitest-environment-rsc` or a Next.js dev server. kiwa takes\n// a lighter approach: treat the async server component as `async (props) =>\n// JSX.Element` and await it directly. The returned React element tree is\n// inspected with a small finder API instead of a full DOM.\n//\n// Out of scope:\n// - flight payload serialization\n// - client component boundary (`'use client'`)\n// - suspense streaming (synchronous resolution only)\n// - dangerous HTML rendering — tests assert on element shape, not strings\n\nexport const NOT_FOUND_SYMBOL = Symbol.for('kiwa.next.rsc.notFound');\nexport const FORBIDDEN_SYMBOL = Symbol.for('kiwa.next.rsc.forbidden');\nexport const RSC_REDIRECT_SYMBOL = Symbol.for('kiwa.next.rsc.redirect');\n\nexport interface NotFoundSignal {\n readonly [NOT_FOUND_SYMBOL]: true;\n}\nexport interface ForbiddenSignal {\n readonly [FORBIDDEN_SYMBOL]: true;\n}\nexport interface RscRedirectSignal {\n readonly [RSC_REDIRECT_SYMBOL]: true;\n readonly url: string;\n readonly type: 'replace' | 'push';\n}\n\nexport type RscSignal = NotFoundSignal | ForbiddenSignal | RscRedirectSignal;\n\nexport interface RscElement {\n readonly type: string | symbol | ((props: Record<string, unknown>) => unknown);\n readonly props: Record<string, unknown>;\n readonly key: string | null;\n}\n\nexport type RscNode = RscElement | string | number | boolean | null | undefined | RscNode[];\n\nexport interface RenderServerComponentOptions<TProps> {\n readonly component: (props: TProps) => Promise<RscNode> | RscNode;\n readonly props?: TProps;\n}\n\nexport interface RenderServerComponentResult {\n readonly tree: RscNode;\n readonly signal: RscSignal | null;\n readonly error: unknown;\n}\n\nfunction isRscElement(node: unknown): node is RscElement {\n if (typeof node !== 'object' || node === null) return false;\n const candidate = node as { type?: unknown; props?: unknown };\n return (\n typeof candidate.type !== 'undefined' &&\n typeof candidate.props === 'object' &&\n candidate.props !== null\n );\n}\n\nfunction isNotFound(value: unknown): value is NotFoundSignal {\n return typeof value === 'object' && value !== null && (value as { [NOT_FOUND_SYMBOL]?: true })[NOT_FOUND_SYMBOL] === true;\n}\nfunction isForbidden(value: unknown): value is ForbiddenSignal {\n return typeof value === 'object' && value !== null && (value as { [FORBIDDEN_SYMBOL]?: true })[FORBIDDEN_SYMBOL] === true;\n}\nfunction isRscRedirect(value: unknown): value is RscRedirectSignal {\n return typeof value === 'object' && value !== null && (value as { [RSC_REDIRECT_SYMBOL]?: true })[RSC_REDIRECT_SYMBOL] === true;\n}\n\n/**\n * Recursively walk an RSC tree and collect every node that satisfies the\n * predicate. Children are read from `props.children` and are normalized to a\n * flat array regardless of how the component spelled them.\n */\nexport function findAll(tree: RscNode, predicate: (node: RscElement) => boolean): RscElement[] {\n const out: RscElement[] = [];\n function visit(node: RscNode): void {\n if (Array.isArray(node)) {\n node.forEach(visit);\n return;\n }\n if (!isRscElement(node)) return;\n if (predicate(node)) out.push(node);\n const children = node.props.children;\n if (typeof children !== 'undefined') {\n visit(children as RscNode);\n }\n }\n visit(tree);\n return out;\n}\n\n/**\n * Concatenate every string/number leaf of an RSC tree, joined by a single\n * space. Useful for `expect(textContent(tree)).toContain('hello')` style\n * assertions where the exact element structure does not matter.\n */\nexport function textContent(tree: RscNode): string {\n const parts: string[] = [];\n function visit(node: RscNode): void {\n if (Array.isArray(node)) {\n node.forEach(visit);\n return;\n }\n if (typeof node === 'string' || typeof node === 'number') {\n parts.push(String(node));\n return;\n }\n if (isRscElement(node)) {\n const children = node.props.children;\n if (typeof children !== 'undefined') visit(children as RscNode);\n }\n }\n visit(tree);\n return parts.filter((p) => p.length > 0).join(' ');\n}\n\n/**\n * Invoke an async server component in isolation and capture its return tree.\n * Throws of `notFound() / forbidden() / redirect()` from `next/navigation`\n * should be replaced with the kiwa signals below (Pattern A from the\n * server-action seam doc); the helper normalizes them into `result.signal`\n * instead of leaving them as `result.error`.\n */\nexport async function renderServerComponent<TProps = Record<string, unknown>>(\n opts: RenderServerComponentOptions<TProps>,\n): Promise<RenderServerComponentResult> {\n let tree: RscNode = null;\n let signal: RscSignal | null = null;\n let error: unknown;\n const props = (opts.props ?? ({} as TProps)) as TProps;\n try {\n const result = await opts.component(props);\n tree = result;\n } catch (caught) {\n if (isNotFound(caught) || isForbidden(caught) || isRscRedirect(caught)) {\n signal = caught;\n } else {\n error = caught;\n }\n }\n return { tree, signal, error };\n}\n","// Next.js App Router Parallel Routes test helper for kiwa (Issue #523).\n//\n// Parallel Routes let an App Router layout render multiple async components in\n// named slots: `layout({ children, @modal, @sidebar })`. Each slot may also\n// provide a `default.tsx` fallback when no matching segment is present, and\n// the URL pattern `(.)`/`(..)`/`(...)` enables Intercepting Routes that swap\n// the slot rendering based on soft-vs-hard navigation.\n//\n// kiwa renders the layout in isolation with synthetic slot trees. The helper\n// awaits all slot promises in parallel (mirroring the React parallel-render\n// semantics), supplies optional Intercepting-Route resolution, and captures\n// errors per slot so a single failing slot does not collapse the whole tree.\n//\n// Out of scope on purpose:\n// - real flight payload / React renderer (use `renderServerComponent` for\n// leaf-level RSC introspection)\n// - matcher / `loading.tsx` / `error.tsx` evaluation\n// - client component boundary (`'use client'`)\n\nexport const PARALLEL_INTERCEPTION_SYMBOL = Symbol.for('kiwa.next.parallel.interception');\n\nexport interface InterceptionMatch<TSlot extends string> {\n readonly [PARALLEL_INTERCEPTION_SYMBOL]: true;\n readonly slot: TSlot;\n readonly variant: 'intercepted' | 'default';\n readonly url: string;\n readonly distance: 'sibling' | 'parent' | 'root';\n}\n\nexport type SlotComponent<TProps = Record<string, unknown>, TNode = unknown> = (\n props: TProps,\n) => Promise<TNode> | TNode;\n\nexport type DefaultFallbackComponent<TNode = unknown> = () => Promise<TNode> | TNode;\n\nexport interface ParallelLayoutChildren<TSlots extends string, TNode = unknown> {\n readonly children: TNode;\n readonly slots: Readonly<Record<TSlots, TNode>>;\n}\n\nexport type ParallelLayoutFunction<TSlots extends string, TLayoutProps, TNode = unknown> = (\n props: TLayoutProps & ParallelLayoutChildren<TSlots, TNode>,\n) => Promise<TNode> | TNode;\n\nexport interface SlotInput<TSlots extends string, TNode = unknown> {\n readonly slot: TSlots;\n readonly component: SlotComponent<Record<string, unknown>, TNode> | null;\n readonly props?: Record<string, unknown>;\n readonly defaultFallback?: DefaultFallbackComponent<TNode>;\n readonly intercepting?: {\n readonly variant: 'intercepted' | 'default';\n readonly url: string;\n readonly distance?: 'sibling' | 'parent' | 'root';\n };\n}\n\nexport interface InvokeParallelRoutesOptions<TSlots extends string, TLayoutProps, TNode = unknown> {\n readonly layout: ParallelLayoutFunction<TSlots, TLayoutProps, TNode>;\n readonly children: SlotComponent<Record<string, unknown>, TNode>;\n readonly childrenProps?: Record<string, unknown>;\n readonly slots: ReadonlyArray<SlotInput<TSlots, TNode>>;\n readonly layoutProps?: TLayoutProps;\n}\n\nexport interface SlotRenderResult<TSlots extends string, TNode = unknown> {\n readonly slot: TSlots;\n readonly tree: TNode | null;\n readonly usedDefault: boolean;\n readonly interception: InterceptionMatch<TSlots> | null;\n readonly error: unknown;\n}\n\nexport interface InvokeParallelRoutesResult<TSlots extends string, TNode = unknown> {\n readonly tree: TNode | null;\n readonly slotResults: ReadonlyArray<SlotRenderResult<TSlots, TNode>>;\n readonly childrenError: unknown;\n readonly layoutError: unknown;\n}\n\nasync function renderSlot<TSlots extends string, TNode>(\n input: SlotInput<TSlots, TNode>,\n): Promise<SlotRenderResult<TSlots, TNode>> {\n let tree: TNode | null = null;\n let usedDefault = false;\n let error: unknown;\n const interception: InterceptionMatch<TSlots> | null = input.intercepting\n ? {\n [PARALLEL_INTERCEPTION_SYMBOL]: true,\n slot: input.slot,\n variant: input.intercepting.variant,\n url: input.intercepting.url,\n distance: input.intercepting.distance ?? 'sibling',\n }\n : null;\n const useDefault =\n input.component === null ||\n interception?.variant === 'default';\n try {\n if (useDefault) {\n if (typeof input.defaultFallback !== 'function') {\n throw new Error(`slot ${input.slot}: no default.tsx fallback supplied`);\n }\n tree = await input.defaultFallback();\n usedDefault = true;\n } else if (input.component !== null) {\n tree = await input.component(input.props ?? {});\n }\n } catch (caught) {\n error = caught;\n }\n return { slot: input.slot, tree, usedDefault, interception, error };\n}\n\n/**\n * Invoke an App Router parallel-routes layout in isolation. All slot\n * components are rendered in parallel (Promise.all) so a slow slot cannot\n * block fast siblings; per-slot errors are captured into `slotResults`\n * without aborting the layout render.\n */\nexport async function invokeParallelRoutes<\n TSlots extends string,\n TLayoutProps = Record<string, unknown>,\n TNode = unknown,\n>(\n opts: InvokeParallelRoutesOptions<TSlots, TLayoutProps, TNode>,\n): Promise<InvokeParallelRoutesResult<TSlots, TNode>> {\n let childrenTree: TNode | null = null;\n let childrenError: unknown;\n let layoutError: unknown;\n try {\n childrenTree = await opts.children(opts.childrenProps ?? {});\n } catch (caught) {\n childrenError = caught;\n }\n const slotResults = await Promise.all(opts.slots.map((input) => renderSlot(input)));\n const slotMap: Record<string, TNode | null> = {};\n for (const result of slotResults) {\n slotMap[result.slot] = result.tree;\n }\n let tree: TNode | null = null;\n try {\n const props = {\n ...((opts.layoutProps ?? {}) as TLayoutProps),\n children: childrenTree as TNode,\n slots: slotMap as Readonly<Record<TSlots, TNode>>,\n } as TLayoutProps & ParallelLayoutChildren<TSlots, TNode>;\n tree = await opts.layout(props);\n } catch (caught) {\n layoutError = caught;\n }\n return { tree, slotResults, childrenError, layoutError };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kiwa-test/nextjs",
3
- "version": "1.0.1",
4
- "description": "Next.js App Router test adapter for kiwa (Server Actions / RSC / middleware) — invoke Server Actions with FormData / headers / cookies and assert redirect / revalidate behavior",
3
+ "version": "1.0.5",
4
+ "description": "Next.js App Router test adapter for kiwa (Server Actions / RSC / middleware / Parallel Routes + Intercepting Routes) — invoke Server Actions with FormData / headers / cookies, render parallel-route layouts with named slots + intercepting URL matchers, and assert redirect / revalidate behavior",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "cardene",
@@ -16,6 +16,8 @@
16
16
  "rsc",
17
17
  "react-server-components",
18
18
  "middleware",
19
+ "parallel-routes",
20
+ "intercepting-routes",
19
21
  "testing",
20
22
  "vitest"
21
23
  ],
@@ -50,7 +52,7 @@
50
52
  ],
51
53
  "publishConfig": {
52
54
  "access": "public",
53
- "provenance": true
55
+ "provenance": false
54
56
  },
55
57
  "engines": {
56
58
  "node": ">=20"
@@ -73,7 +75,7 @@
73
75
  "scripts": {
74
76
  "build": "tsup",
75
77
  "test": "node -e \"require('node:fs').rmSync('.vitest-dist',{recursive:true,force:true})\" && tsc -p tsconfig.vitest.json && vitest run .vitest-dist/tests --environment node",
76
- "test:cov": "node -e \"require('node:fs').rmSync('.vitest-dist',{recursive:true,force:true})\" && tsc -p tsconfig.vitest.json && vitest run .vitest-dist/tests --environment node --coverage --coverage.provider=v8 --coverage.reporter=json --coverage.reporter=json-summary --coverage.reporter=text --coverage.reportsDirectory=coverage --coverage.include='.vitest-dist/src/invoke-server-action.js' --coverage.exclude='.vitest-dist/tests/**' --coverage.exclude='.vitest-dist/src/index.js'",
78
+ "test:cov": "node -e \"require('node:fs').rmSync('.vitest-dist',{recursive:true,force:true})\" && tsc -p tsconfig.vitest.json && vitest run .vitest-dist/tests --environment node --coverage --coverage.provider=v8 --coverage.reporter=json --coverage.reporter=json-summary --coverage.reporter=text --coverage.reportsDirectory=coverage --coverage.include='.vitest-dist/src/invoke-server-action.js' --coverage.include='.vitest-dist/src/invoke-middleware.js' --coverage.include='.vitest-dist/src/render-server-component.js' --coverage.include='.vitest-dist/src/invoke-parallel-routes.js' --coverage.exclude='.vitest-dist/tests/**' --coverage.exclude='.vitest-dist/src/index.js'",
77
79
  "test:mutation": "stryker run",
78
80
  "typecheck": "tsc --noEmit"
79
81
  }