@kiwa-test/nextjs 1.0.1 → 1.0.3
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 +159 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -1
- package/dist/index.d.ts +121 -1
- package/dist/index.js +149 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,8 +20,17 @@ 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,
|
|
23
26
|
REDIRECT_SYMBOL: () => REDIRECT_SYMBOL,
|
|
24
|
-
|
|
27
|
+
RSC_REDIRECT_SYMBOL: () => RSC_REDIRECT_SYMBOL,
|
|
28
|
+
findAll: () => findAll,
|
|
29
|
+
invokeMiddleware: () => invokeMiddleware,
|
|
30
|
+
invokeServerAction: () => invokeServerAction,
|
|
31
|
+
middlewareActions: () => middlewareActions,
|
|
32
|
+
renderServerComponent: () => renderServerComponent,
|
|
33
|
+
textContent: () => textContent
|
|
25
34
|
});
|
|
26
35
|
module.exports = __toCommonJS(index_exports);
|
|
27
36
|
|
|
@@ -72,9 +81,157 @@ async function invokeServerAction(opts) {
|
|
|
72
81
|
}
|
|
73
82
|
return { result, error, env };
|
|
74
83
|
}
|
|
84
|
+
|
|
85
|
+
// src/invoke-middleware.ts
|
|
86
|
+
var MIDDLEWARE_ACTION_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.middleware.action");
|
|
87
|
+
function buildRequest(opts) {
|
|
88
|
+
const url = new URL(opts.url);
|
|
89
|
+
const headers = /* @__PURE__ */ new Map();
|
|
90
|
+
for (const [name, value] of Object.entries(opts.headers ?? {})) {
|
|
91
|
+
headers.set(name.toLowerCase(), value);
|
|
92
|
+
}
|
|
93
|
+
const cookies = new Map(Object.entries(opts.cookies ?? {}));
|
|
94
|
+
return {
|
|
95
|
+
url: opts.url,
|
|
96
|
+
method: opts.method ?? "GET",
|
|
97
|
+
headers,
|
|
98
|
+
cookies,
|
|
99
|
+
nextUrl: {
|
|
100
|
+
pathname: url.pathname,
|
|
101
|
+
search: url.search,
|
|
102
|
+
searchParams: url.searchParams
|
|
103
|
+
},
|
|
104
|
+
geo: opts.geo ?? {}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
var middlewareActions = {
|
|
108
|
+
next() {
|
|
109
|
+
return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "next" };
|
|
110
|
+
},
|
|
111
|
+
redirect(url, status = 307) {
|
|
112
|
+
return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "redirect", url, status };
|
|
113
|
+
},
|
|
114
|
+
rewrite(url) {
|
|
115
|
+
return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "rewrite", url };
|
|
116
|
+
},
|
|
117
|
+
json(body, status = 200) {
|
|
118
|
+
return { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "json", body, status };
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
async function invokeMiddleware(opts) {
|
|
122
|
+
const req = buildRequest(opts);
|
|
123
|
+
const responseHeaders = /* @__PURE__ */ new Map();
|
|
124
|
+
const responseCookies = /* @__PURE__ */ new Map();
|
|
125
|
+
const seam = {
|
|
126
|
+
setHeader(name, value) {
|
|
127
|
+
responseHeaders.set(name.toLowerCase(), value);
|
|
128
|
+
},
|
|
129
|
+
setCookie(name, value) {
|
|
130
|
+
responseCookies.set(name, value);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
let action = middlewareActions.next();
|
|
134
|
+
let error;
|
|
135
|
+
try {
|
|
136
|
+
const result = await opts.middleware(req, seam);
|
|
137
|
+
action = result;
|
|
138
|
+
} catch (caught) {
|
|
139
|
+
error = caught;
|
|
140
|
+
action = { [MIDDLEWARE_ACTION_SYMBOL]: true, kind: "noop" };
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
env: {
|
|
144
|
+
responseHeaders,
|
|
145
|
+
responseCookies,
|
|
146
|
+
action
|
|
147
|
+
},
|
|
148
|
+
error
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/render-server-component.ts
|
|
153
|
+
var NOT_FOUND_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.rsc.notFound");
|
|
154
|
+
var FORBIDDEN_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.rsc.forbidden");
|
|
155
|
+
var RSC_REDIRECT_SYMBOL = /* @__PURE__ */ Symbol.for("kiwa.next.rsc.redirect");
|
|
156
|
+
function isRscElement(node) {
|
|
157
|
+
if (typeof node !== "object" || node === null) return false;
|
|
158
|
+
const candidate = node;
|
|
159
|
+
return typeof candidate.type !== "undefined" && typeof candidate.props === "object" && candidate.props !== null;
|
|
160
|
+
}
|
|
161
|
+
function isNotFound(value) {
|
|
162
|
+
return typeof value === "object" && value !== null && value[NOT_FOUND_SYMBOL] === true;
|
|
163
|
+
}
|
|
164
|
+
function isForbidden(value) {
|
|
165
|
+
return typeof value === "object" && value !== null && value[FORBIDDEN_SYMBOL] === true;
|
|
166
|
+
}
|
|
167
|
+
function isRscRedirect(value) {
|
|
168
|
+
return typeof value === "object" && value !== null && value[RSC_REDIRECT_SYMBOL] === true;
|
|
169
|
+
}
|
|
170
|
+
function findAll(tree, predicate) {
|
|
171
|
+
const out = [];
|
|
172
|
+
function visit(node) {
|
|
173
|
+
if (Array.isArray(node)) {
|
|
174
|
+
node.forEach(visit);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (!isRscElement(node)) return;
|
|
178
|
+
if (predicate(node)) out.push(node);
|
|
179
|
+
const children = node.props.children;
|
|
180
|
+
if (typeof children !== "undefined") {
|
|
181
|
+
visit(children);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
visit(tree);
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
function textContent(tree) {
|
|
188
|
+
const parts = [];
|
|
189
|
+
function visit(node) {
|
|
190
|
+
if (Array.isArray(node)) {
|
|
191
|
+
node.forEach(visit);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (typeof node === "string" || typeof node === "number") {
|
|
195
|
+
parts.push(String(node));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (isRscElement(node)) {
|
|
199
|
+
const children = node.props.children;
|
|
200
|
+
if (typeof children !== "undefined") visit(children);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
visit(tree);
|
|
204
|
+
return parts.filter((p) => p.length > 0).join(" ");
|
|
205
|
+
}
|
|
206
|
+
async function renderServerComponent(opts) {
|
|
207
|
+
let tree = null;
|
|
208
|
+
let signal = null;
|
|
209
|
+
let error;
|
|
210
|
+
const props = opts.props ?? {};
|
|
211
|
+
try {
|
|
212
|
+
const result = await opts.component(props);
|
|
213
|
+
tree = result;
|
|
214
|
+
} catch (caught) {
|
|
215
|
+
if (isNotFound(caught) || isForbidden(caught) || isRscRedirect(caught)) {
|
|
216
|
+
signal = caught;
|
|
217
|
+
} else {
|
|
218
|
+
error = caught;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return { tree, signal, error };
|
|
222
|
+
}
|
|
75
223
|
// Annotate the CommonJS export names for ESM import in node:
|
|
76
224
|
0 && (module.exports = {
|
|
225
|
+
FORBIDDEN_SYMBOL,
|
|
226
|
+
MIDDLEWARE_ACTION_SYMBOL,
|
|
227
|
+
NOT_FOUND_SYMBOL,
|
|
77
228
|
REDIRECT_SYMBOL,
|
|
78
|
-
|
|
229
|
+
RSC_REDIRECT_SYMBOL,
|
|
230
|
+
findAll,
|
|
231
|
+
invokeMiddleware,
|
|
232
|
+
invokeServerAction,
|
|
233
|
+
middlewareActions,
|
|
234
|
+
renderServerComponent,
|
|
235
|
+
textContent
|
|
79
236
|
});
|
|
80
237
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -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"],"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","// 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"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -51,4 +51,124 @@ interface ServerActionResult<TResult> {
|
|
|
51
51
|
*/
|
|
52
52
|
declare function invokeServerAction<TResult>(opts: ServerActionInvocation<TResult>): Promise<ServerActionResult<TResult>>;
|
|
53
53
|
|
|
54
|
-
|
|
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
|
+
export { type CookieJar, FORBIDDEN_SYMBOL, type ForbiddenSignal, type InvokeMiddlewareOptions, type InvokeMiddlewareResult, MIDDLEWARE_ACTION_SYMBOL, type MiddlewareAction, type MiddlewareActionKind, type MiddlewareEnv, type MiddlewareFunction, type MiddlewareRequest, NOT_FOUND_SYMBOL, type NotFoundSignal, 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, findAll, invokeMiddleware, invokeServerAction, middlewareActions, renderServerComponent, textContent };
|
package/dist/index.d.ts
CHANGED
|
@@ -51,4 +51,124 @@ interface ServerActionResult<TResult> {
|
|
|
51
51
|
*/
|
|
52
52
|
declare function invokeServerAction<TResult>(opts: ServerActionInvocation<TResult>): Promise<ServerActionResult<TResult>>;
|
|
53
53
|
|
|
54
|
-
|
|
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
|
+
export { type CookieJar, FORBIDDEN_SYMBOL, type ForbiddenSignal, type InvokeMiddlewareOptions, type InvokeMiddlewareResult, MIDDLEWARE_ACTION_SYMBOL, type MiddlewareAction, type MiddlewareActionKind, type MiddlewareEnv, type MiddlewareFunction, type MiddlewareRequest, NOT_FOUND_SYMBOL, type NotFoundSignal, 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, findAll, invokeMiddleware, invokeServerAction, middlewareActions, renderServerComponent, textContent };
|
package/dist/index.js
CHANGED
|
@@ -45,8 +45,156 @@ 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
|
+
}
|
|
48
187
|
export {
|
|
188
|
+
FORBIDDEN_SYMBOL,
|
|
189
|
+
MIDDLEWARE_ACTION_SYMBOL,
|
|
190
|
+
NOT_FOUND_SYMBOL,
|
|
49
191
|
REDIRECT_SYMBOL,
|
|
50
|
-
|
|
192
|
+
RSC_REDIRECT_SYMBOL,
|
|
193
|
+
findAll,
|
|
194
|
+
invokeMiddleware,
|
|
195
|
+
invokeServerAction,
|
|
196
|
+
middlewareActions,
|
|
197
|
+
renderServerComponent,
|
|
198
|
+
textContent
|
|
51
199
|
};
|
|
52
200
|
//# 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"],"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"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kiwa-test/nextjs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
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",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"scripts": {
|
|
74
74
|
"build": "tsup",
|
|
75
75
|
"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'",
|
|
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.include='.vitest-dist/src/invoke-middleware.js' --coverage.include='.vitest-dist/src/render-server-component.js' --coverage.exclude='.vitest-dist/tests/**' --coverage.exclude='.vitest-dist/src/index.js'",
|
|
77
77
|
"test:mutation": "stryker run",
|
|
78
78
|
"typecheck": "tsc --noEmit"
|
|
79
79
|
}
|