feat: add validation for useAsTitle to throw an error if it's an invalid or nested field (#8122)

This commit is contained in:
Paul
2024-09-08 18:53:12 -06:00
committed by GitHub
parent 08fdbcacc0
commit 638382e7fd
6 changed files with 250 additions and 9 deletions

View File

@@ -14,6 +14,7 @@ import baseVersionFields from '../../versions/baseFields.js'
import { versionDefaults } from '../../versions/defaults.js'
import { authDefaults, defaults, loginWithUsernameDefaults } from './defaults.js'
import { sanitizeAuthFields, sanitizeUploadFields } from './reservedFieldNames.js'
import { validateUseAsTitle } from './useAsTitle.js'
export const sanitizeCollection = async (
config: Config,
@@ -44,6 +45,8 @@ export const sanitizeCollection = async (
validRelationships,
})
validateUseAsTitle(sanitized)
if (sanitized.timestamps !== false) {
// add default timestamps fields only as needed
let hasUpdatedAt = null

View File

@@ -0,0 +1,204 @@
import type { Config } from '../../config/types.js'
import type { CollectionConfig } from '../../index.js'
import { InvalidConfiguration } from '../../errors/InvalidConfiguration.js'
import { sanitizeCollection } from './sanitize.js'
describe('sanitize - collections -', () => {
const config = {
collections: [],
globals: [],
} as Partial<Config>
describe('validate useAsTitle -', () => {
const defaultCollection: CollectionConfig = {
slug: 'collection-with-defaults',
fields: [
{
name: 'title',
type: 'text',
},
],
}
it('should throw on invalid field', async () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
admin: {
useAsTitle: 'invalidField',
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).rejects.toThrow(InvalidConfiguration)
})
it('should not throw on valid field', async () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
admin: {
useAsTitle: 'title',
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).not.toThrow()
})
it('should not throw on valid field inside tabs', async () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
admin: {
useAsTitle: 'title',
},
fields: [
{
type: 'tabs',
tabs: [
{
label: 'General',
fields: [
{
name: 'title',
type: 'text',
},
],
},
],
},
],
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).not.toThrow()
})
it('should not throw on valid field inside collapsibles', async () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
admin: {
useAsTitle: 'title',
},
fields: [
{
type: 'collapsible',
label: 'Collapsible',
fields: [
{
name: 'title',
type: 'text',
},
],
},
],
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).not.toThrow()
})
it('should throw on nested useAsTitle', async () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
admin: {
useAsTitle: 'content.title',
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).rejects.toThrow(InvalidConfiguration)
})
it('should not throw on default field: id', async () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
admin: {
useAsTitle: 'id',
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).not.toThrow()
})
it('should not throw on default field: email if auth is enabled', async () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
auth: true,
admin: {
useAsTitle: 'email',
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).not.toThrow()
})
it('should throw on default field: email if auth is not enabled', async () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
admin: {
useAsTitle: 'email',
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).rejects.toThrow(InvalidConfiguration)
})
})
})

View File

@@ -0,0 +1,43 @@
import type { CollectionConfig } from '../../index.js'
import { InvalidConfiguration } from '../../errors/InvalidConfiguration.js'
import { fieldAffectsData } from '../../fields/config/types.js'
import flattenFields from '../../utilities/flattenTopLevelFields.js'
/**
* Validate useAsTitle for collections.
*/
export const validateUseAsTitle = (config: CollectionConfig) => {
if (config.admin.useAsTitle.includes('.')) {
throw new InvalidConfiguration(
`"useAsTitle" cannot be a nested field. Please specify a top-level field in the collection "${config.slug}"`,
)
}
if (config?.admin && config.admin?.useAsTitle && config.admin.useAsTitle !== 'id') {
const fields = flattenFields(config.fields)
const useAsTitleField = fields.find((field) => {
if (fieldAffectsData(field) && config.admin) {
return field.name === config.admin.useAsTitle
}
return false
})
// If auth is enabled then we don't need to
if (config.auth) {
if (config.admin.useAsTitle !== 'email') {
if (!useAsTitleField) {
throw new InvalidConfiguration(
`The field "${config.admin.useAsTitle}" specified in "admin.useAsTitle" does not exist in the collection "${config.slug}"`,
)
}
}
} else {
if (!useAsTitleField) {
throw new InvalidConfiguration(
`The field "${config.admin.useAsTitle}" specified in "admin.useAsTitle" does not exist in the collection "${config.slug}"`,
)
}
}
}
}

View File

@@ -220,9 +220,6 @@ export default buildConfigWithDefaults({
slug: relationWithTitleSlug,
},
{
admin: {
useAsTitle: 'name',
},
fields: [
{
fields: [

View File

@@ -4,9 +4,6 @@ import { defaultEmail, emailFieldsSlug } from './shared.js'
const EmailFields: CollectionConfig = {
slug: emailFieldsSlug,
admin: {
useAsTitle: 'text',
},
defaultSort: 'id',
fields: [
{

View File

@@ -3,8 +3,5 @@ import type { CollectionConfig } from 'payload'
export const Users: CollectionConfig = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'title',
},
fields: [],
}