Files
cloudflare__vinext/tests/pages-serializable-props.test.ts
T
James Anderson f6e1525e22 fix(pages-router): throw clear error on non-serializable gSP/gSSP props (#1478) (#1642)
When `getStaticProps` or `getServerSideProps` returned props containing
non-JSON-serializable values (e.g. `Date`, `Map`, class instances), vinext
silently rendered an empty page instead of surfacing an error. This ports
Next.js's `isSerializableProps` check from `packages/next/src/lib/is-serializable-props.ts`
and invokes it from `pages-page-data.ts` and `dev-server.ts` after the
redirect/notFound short-circuits, mirroring `render.tsx` (~lines 982 and
1200). The thrown `SerializableError` is caught by the existing per-request
error boundary, which returns a 500 with a Next.js-style message naming the
offending key.
2026-05-28 15:44:13 +01:00

76 lines
2.7 KiB
TypeScript

// Ported from Next.js: test/unit/is-serializable-props.test.ts
// https://github.com/vercel/next.js/blob/canary/test/unit/is-serializable-props.test.ts
//
// vinext implements its own copy of `isSerializableProps` so Pages Router
// `getStaticProps` / `getServerSideProps` surface a clear error (instead of
// rendering an empty page) when they return non-JSON-serializable values.
// Tracked in vinext#1478.
import { describe, expect, it } from "vite-plus/test";
import {
isSerializableProps,
SerializableError,
} from "../packages/vinext/src/server/pages-serializable-props.js";
describe("isSerializableProps", () => {
it("allows empty props", () => {
expect(isSerializableProps("/", "getStaticProps", {})).toBe(true);
});
it("allows nested JSON-safe values", () => {
expect(
isSerializableProps("/", "getStaticProps", {
str: "foo",
num: 0,
bool: true,
nul: null,
arr: [1, "two", null, { a: 1 }],
obj: { nested: { deep: "ok" } },
}),
).toBe(true);
});
it("rejects top-level non-plain values", () => {
expect(() => isSerializableProps("/", "getStaticProps", null)).toThrow(SerializableError);
expect(() => isSerializableProps("/", "getStaticProps", undefined)).toThrow(SerializableError);
expect(() => isSerializableProps("/", "getStaticProps", [])).toThrow(
/received: `\[object Array\]`/,
);
});
it("rejects Date inside props", () => {
expect(() => isSerializableProps("/non-json", "getStaticProps", { date: new Date(0) })).toThrow(
/Error serializing `\.date` returned from `getStaticProps` in "\/non-json"/,
);
expect(() => isSerializableProps("/non-json", "getStaticProps", { date: new Date(0) })).toThrow(
/`object` \("\[object Date\]"\) cannot be serialized as JSON/,
);
});
it("rejects functions inside props", () => {
expect(() => isSerializableProps("/", "getServerSideProps", { fn: () => 1 })).toThrow(
/`function` cannot be serialized as JSON/,
);
});
it("rejects undefined nested values with a helpful message", () => {
expect(() => isSerializableProps("/", "getStaticProps", { foo: undefined })).toThrow(
/`undefined` cannot be serialized as JSON\. Please use `null` or omit this value\./,
);
});
it("detects circular references", () => {
const a: Record<string, unknown> = {};
a.self = a;
expect(() => isSerializableProps("/", "getStaticProps", a)).toThrow(
/Circular references cannot be expressed in JSON/,
);
});
it("uses bracket notation for non-identifier keys in the path", () => {
expect(() => isSerializableProps("/", "getStaticProps", { "weird key": new Date() })).toThrow(
/Error serializing `\["weird key"\]`/,
);
});
});