Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify error message in isSerializableProps #22856

Merged
merged 7 commits into from
Jul 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/next/lib/is-serializable-props.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
const regexpPlainIdentifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/

function getObjectClassLabel(value: any): string {
return Object.prototype.toString.call(value)
}

function isPlainObject(value: any): boolean {
if (Object.prototype.toString.call(value) !== '[object Object]') {
if (getObjectClassLabel(value) !== '[object Object]') {
return false
}

Expand All @@ -19,7 +23,9 @@ export function isSerializableProps(
page,
method,
'',
`Props must be returned as a plain object from ${method}: \`{ props: { ... } }\`.`
`Props must be returned as a plain object from ${method}: \`{ props: { ... } }\` (received: \`${getObjectClassLabel(
input
)}\`).`
)
}

Expand Down
140 changes: 80 additions & 60 deletions test/unit/is-serializable-props.unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,35 @@ describe('isSerializableProps', () => {
it('handles null and undefined props', () => {
expect(() => isSerializableProps('/', 'test', null))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing props returned from \`test\` in \\"/\\".
Reason: Props must be returned as a plain object from test: \`{ props: { ... } }\`."
`)
"Error serializing props returned from \`test\` in \\"/\\".
Reason: Props must be returned as a plain object from test: \`{ props: { ... } }\` (received: \`[object Null]\`)."
`)

expect(() => isSerializableProps('/', 'test', undefined))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing props returned from \`test\` in \\"/\\".
Reason: Props must be returned as a plain object from test: \`{ props: { ... } }\`."
`)
"Error serializing props returned from \`test\` in \\"/\\".
Reason: Props must be returned as a plain object from test: \`{ props: { ... } }\` (received: \`[object Undefined]\`)."
`)
})

it('handles non-plain object props', () => {
expect(() => isSerializableProps('/', 'test', []))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing props returned from \`test\` in \\"/\\".
Reason: Props must be returned as a plain object from test: \`{ props: { ... } }\` (received: \`[object Array]\`)."
`)

expect(() => isSerializableProps('/', 'test', class Foobar {}))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing props returned from \`test\` in \\"/\\".
Reason: Props must be returned as a plain object from test: \`{ props: { ... } }\` (received: \`[object Function]\`)."
`)

expect(() => isSerializableProps('/', 'test', function Foobar() {}))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing props returned from \`test\` in \\"/\\".
Reason: Props must be returned as a plain object from test: \`{ props: { ... } }\` (received: \`[object Function]\`)."
`)
})

it('allows empty props', () => {
Expand Down Expand Up @@ -88,70 +108,70 @@ Reason: Props must be returned as a plain object from test: \`{ props: { ... } }
it('disallows top-level non-serializable types', () => {
expect(() => isSerializableProps('/', 'test', { toplevel: new Date() }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.toplevel\` returned from \`test\` in \\"/\\".
Reason: \`object\` (\\"[object Date]\\") cannot be serialized as JSON. Please only return JSON serializable data types."
`)
"Error serializing \`.toplevel\` returned from \`test\` in \\"/\\".
Reason: \`object\` (\\"[object Date]\\") cannot be serialized as JSON. Please only return JSON serializable data types."
`)

expect(() => isSerializableProps('/', 'test', { toplevel: class A {} }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.toplevel\` returned from \`test\` in \\"/\\".
Reason: \`function\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)
"Error serializing \`.toplevel\` returned from \`test\` in \\"/\\".
Reason: \`function\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)

expect(() => isSerializableProps('/', 'test', { toplevel: undefined }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.toplevel\` returned from \`test\` in \\"/\\".
Reason: \`undefined\` cannot be serialized as JSON. Please use \`null\` or omit this value."
`)
"Error serializing \`.toplevel\` returned from \`test\` in \\"/\\".
Reason: \`undefined\` cannot be serialized as JSON. Please use \`null\` or omit this value."
`)

expect(() =>
isSerializableProps('/', 'test', { toplevel: Symbol('FOOBAR') })
).toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.toplevel\` returned from \`test\` in \\"/\\".
Reason: \`symbol\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)
"Error serializing \`.toplevel\` returned from \`test\` in \\"/\\".
Reason: \`symbol\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)

expect(() => isSerializableProps('/', 'test', { toplevel: function () {} }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.toplevel\` returned from \`test\` in \\"/\\".
Reason: \`function\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)
"Error serializing \`.toplevel\` returned from \`test\` in \\"/\\".
Reason: \`function\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)
})

it('diallows nested non-serializable types', () => {
expect(() =>
isSerializableProps('/', 'test', { k: { a: [1, { n: new Date() }] } })
).toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k.a[1].n\` returned from \`test\` in \\"/\\".
Reason: \`object\` (\\"[object Date]\\") cannot be serialized as JSON. Please only return JSON serializable data types."
`)
"Error serializing \`.k.a[1].n\` returned from \`test\` in \\"/\\".
Reason: \`object\` (\\"[object Date]\\") cannot be serialized as JSON. Please only return JSON serializable data types."
`)

expect(() =>
isSerializableProps('/', 'test', { k: { a: [1, { n: class A {} }] } })
).toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k.a[1].n\` returned from \`test\` in \\"/\\".
Reason: \`function\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)
"Error serializing \`.k.a[1].n\` returned from \`test\` in \\"/\\".
Reason: \`function\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)

expect(() => isSerializableProps('/', 'test', { k: { a: [1, undefined] } }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k.a[1]\` returned from \`test\` in \\"/\\".
Reason: \`undefined\` cannot be serialized as JSON. Please use \`null\` or omit this value."
`)
"Error serializing \`.k.a[1]\` returned from \`test\` in \\"/\\".
Reason: \`undefined\` cannot be serialized as JSON. Please use \`null\` or omit this value."
`)

expect(() =>
isSerializableProps('/', 'test', { k: { n: Symbol('FOOBAR') } })
).toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k.n\` returned from \`test\` in \\"/\\".
Reason: \`symbol\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)
"Error serializing \`.k.n\` returned from \`test\` in \\"/\\".
Reason: \`symbol\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)

expect(() =>
isSerializableProps('/', 'test', { k: { a: [function () {}] } })
).toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k.a[0]\` returned from \`test\` in \\"/\\".
Reason: \`function\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)
"Error serializing \`.k.a[0]\` returned from \`test\` in \\"/\\".
Reason: \`function\` cannot be serialized as JSON. Please only return JSON serializable data types."
`)
})

it('can handle obj circular refs', () => {
Expand All @@ -160,15 +180,15 @@ Reason: \`function\` cannot be serialized as JSON. Please only return JSON seria

expect(() => isSerializableProps('/', 'test', obj))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.child\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`(self)\`)."
`)
"Error serializing \`.child\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`(self)\`)."
`)

expect(() => isSerializableProps('/', 'test', { k: [obj] }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k[0].child\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.k[0]\`)."
`)
"Error serializing \`.k[0].child\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.k[0]\`)."
`)
})

it('can handle arr circular refs', () => {
Expand All @@ -177,15 +197,15 @@ Reason: Circular references cannot be expressed in JSON (references: \`.k[0]\`).

expect(() => isSerializableProps('/', 'test', { arr }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.arr[2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.arr\`)."
`)
"Error serializing \`.arr[2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.arr\`)."
`)

expect(() => isSerializableProps('/', 'test', { k: [{ arr }] }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k[0].arr[2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.k[0].arr\`)."
`)
"Error serializing \`.k[0].arr[2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.k[0].arr\`)."
`)
})

it('can handle deep obj circular refs', () => {
Expand All @@ -194,9 +214,9 @@ Reason: Circular references cannot be expressed in JSON (references: \`.k[0].arr

expect(() => isSerializableProps('/', 'test', obj))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.leve1.level2.child\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`(self)\`)."
`)
"Error serializing \`.leve1.level2.child\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`(self)\`)."
`)
})

it('can handle deep obj circular refs (with arrays)', () => {
Expand All @@ -205,9 +225,9 @@ Reason: Circular references cannot be expressed in JSON (references: \`(self)\`)

expect(() => isSerializableProps('/', 'test', obj))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.leve1.level2.child[0].another[0]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`(self)\`)."
`)
"Error serializing \`.leve1.level2.child[0].another[0]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`(self)\`)."
`)
})

it('can handle deep arr circular refs', () => {
Expand All @@ -216,9 +236,9 @@ Reason: Circular references cannot be expressed in JSON (references: \`(self)\`)

expect(() => isSerializableProps('/', 'test', { k: arr }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k[3][1][2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.k\`)."
`)
"Error serializing \`.k[3][1][2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.k\`)."
`)
})

it('can handle deep arr circular refs (with objects)', () => {
Expand All @@ -227,9 +247,9 @@ Reason: Circular references cannot be expressed in JSON (references: \`.k\`)."

expect(() => isSerializableProps('/', 'test', { k: arr }))
.toThrowErrorMatchingInlineSnapshot(`
"Error serializing \`.k[3][1].nested[2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.k\`)."
`)
"Error serializing \`.k[3][1].nested[2]\` returned from \`test\` in \\"/\\".
Reason: Circular references cannot be expressed in JSON (references: \`.k\`)."
`)
})

it('allows multi object refs', () => {
Expand Down