Files
cloudflare__vinext/tests/app-hook-warning-suppression.test.ts
T
Nathan Nguyen 763736eae0 refactor(app-rsc-entry): delegate pure helpers to typed server modules (#1017)
* refactor(app-rsc-entry): delegate pure helpers to typed server modules

The generated RSC entry template contained five inline implementations of
pure runtime helpers: post-middleware request context construction,
RSC error handler creation, request context cleanup, RSC stream preload
hint normalization, and hook warning suppression. These inline blocks
repeated runtime behavior across multiple call sites and could not be
unit-tested independently.

Move each helper to a dedicated typed module under server/ so the
generated entry imports and wires them instead of owning the logic:
- buildPostMwRequestContext → server/app-post-middleware-context.ts
- createRscOnErrorHandler → server/app-rsc-error-handler.ts
- __clearRequestContext + setNavigationContext → server/app-request-context.ts
- renderToReadableStream preload hints → server/rsc-stream-hints.ts (createRscRenderer)
- hook warning console.error patch → server/app-hook-warning-suppression.ts

Each helper module has focused unit tests. The entry template is 55 lines
shorter and now describes the app shape; the typed modules own behaviour.

* fix: remove redundant as cast and fix test module caching

app-hook-warning-suppression.ts contained an `as string` cast that was
redundant because the typeof check on the prior line already narrows the
type. The hook warning suppression tests in tests 2-4 were passing
vacuously because dynamic import() returned the cached module whose
console.error patch was no longer active after test 1 restored the
original. Use vi.resetModules() before each test to force fresh module
evaluation.

clearAppRequestContext now delegates to setAppNavigationContext(null)
instead of calling the raw navigation and root-params setters directly,
matching the DRY pattern from the original inline code.

* fix: resolve entry helper rebase conflicts

* ci: rerun checks (font-google flake)

* Update packages/vinext/src/entries/app-rsc-entry.ts

Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>

---------

Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>
2026-05-03 17:34:06 +01:00

88 lines
3.3 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from "vite-plus/test";
describe("app-hook-warning-suppression", () => {
// The module patches console.error at import time. In a vitest environment,
// dynamic imports are cached across tests within the same suite. To ensure
// each test gets a fresh module evaluation (so the side-effect runs and
// captures the test's spy as _origConsoleError), we reset the module cache
// before each test.
beforeEach(() => {
vi.resetModules();
});
it("suppresses 'Invalid hook call' messages when the ALS store is true", async () => {
const spy = vi.fn();
const prev = console.error;
console.error = spy;
try {
const { suppressHookWarningAls } =
await import("../packages/vinext/src/server/app-hook-warning-suppression.js");
// The module's patched console.error is active because import() ran the
// side-effect. suppressHookWarningAls.run(true, ...) sets the ALS store;
// the patched console.error checks the store and suppresses the message.
suppressHookWarningAls.run(true, () => {
console.error("Invalid hook call: check render method of ServerComponent");
});
expect(spy).not.toHaveBeenCalled();
} finally {
console.error = prev;
}
});
it("does NOT suppress non-hook messages even when the ALS store is true", async () => {
const spy = vi.fn();
const prev = console.error;
console.error = spy;
try {
const { suppressHookWarningAls } =
await import("../packages/vinext/src/server/app-hook-warning-suppression.js");
suppressHookWarningAls.run(true, () => {
console.error("A real error that should not be suppressed");
});
// The patched console.error should forward non-hook messages to the
// original (spy), not suppress them.
expect(spy).toHaveBeenCalledOnce();
expect(spy.mock.calls[0]?.[0]).toBe("A real error that should not be suppressed");
} finally {
console.error = prev;
}
});
it("passes 'Invalid hook call' messages through when the ALS store is NOT set", async () => {
const spy = vi.fn();
const prev = console.error;
console.error = spy;
try {
const { suppressHookWarningAls } =
await import("../packages/vinext/src/server/app-hook-warning-suppression.js");
// Outside of .run(), the ALS store is undefined. The patched
// console.error should forward the message to the original (spy).
expect(suppressHookWarningAls.getStore()).toBeUndefined();
console.error("Invalid hook call: check render method of Page");
expect(spy).toHaveBeenCalledOnce();
} finally {
console.error = prev;
}
});
it("forwards all arguments to the original console.error when not suppressing", async () => {
const spy = vi.fn();
const prev = console.error;
console.error = spy;
try {
await import("../packages/vinext/src/server/app-hook-warning-suppression.js");
// The patched console.error is active. Non-hook messages with multiple
// arguments should be forwarded exactly as received.
console.error("message", { detail: "payload" }, 42);
expect(spy).toHaveBeenCalledWith("message", { detail: "payload" }, 42);
} finally {
console.error = prev;
}
});
});