Adds the ability to completely omit `name` from group fields now so that
they're entirely presentational.
New config:
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'posts',
fields: [
{
label: 'Page header',
type: 'group', // required
fields: [
{
name: 'title',
type: 'text',
required: true,
},
],
},
],
}
```
will create
<img width="332" alt="image"
src="https://github.com/user-attachments/assets/10b4315e-92d6-439e-82dd-7c815a844035"
/>
but the data response will still be
```
{
"createdAt": "2025-05-05T13:42:20.326Z",
"updatedAt": "2025-05-05T13:42:20.326Z",
"title": "example post",
"id": "6818c03ce92b7f92be1540f0"
}
```
Checklist:
- [x] Added int tests
- [x] Modify mongo, drizzle and graphql packages
- [x] Add type tests
- [x] Add e2e tests
162 lines
4.6 KiB
TypeScript
162 lines
4.6 KiB
TypeScript
import type {
|
|
BulkOperationResult,
|
|
JoinQuery,
|
|
PaginatedDocs,
|
|
SelectType,
|
|
TypeWithVersion,
|
|
Where,
|
|
} from 'payload'
|
|
|
|
import payload from 'payload'
|
|
import { describe, expect, test } from 'tstyche'
|
|
|
|
import type {
|
|
Menu,
|
|
MyRadioOptions,
|
|
MySelectOptions,
|
|
Post,
|
|
SupportedTimezones,
|
|
User,
|
|
} from './payload-types.js'
|
|
|
|
const asType = <T>() => {
|
|
return '' as T
|
|
}
|
|
|
|
describe('Types testing', () => {
|
|
test('payload.find', () => {
|
|
expect(payload.find({ collection: 'users' })).type.toBe<Promise<PaginatedDocs<User>>>()
|
|
})
|
|
|
|
test('payload.findByID', () => {
|
|
expect(payload.findByID({ id: 1, collection: 'users' })).type.toBe<Promise<User>>()
|
|
})
|
|
|
|
test('payload.findByID with disableErrors: true', () => {
|
|
expect(payload.findByID({ id: 1, collection: 'users', disableErrors: true })).type.toBe<
|
|
Promise<null | User>
|
|
>()
|
|
})
|
|
|
|
test('payload.create', () => {
|
|
expect(payload.create({ collection: 'users', data: { email: 'user@email.com' } })).type.toBe<
|
|
Promise<User>
|
|
>()
|
|
})
|
|
|
|
test('payload.update by ID', () => {
|
|
expect(payload.update({ id: 1, collection: 'users', data: {} })).type.toBe<Promise<User>>()
|
|
})
|
|
|
|
test('payload.update many', () => {
|
|
expect(payload.update({ where: {}, collection: 'users', data: {} })).type.toBe<
|
|
Promise<BulkOperationResult<'users', SelectType>>
|
|
>()
|
|
})
|
|
|
|
test('payload.delete by ID', () => {
|
|
expect(payload.delete({ id: 1, collection: 'users' })).type.toBe<Promise<User>>()
|
|
})
|
|
|
|
test('payload.delete many', () => {
|
|
expect(payload.delete({ where: {}, collection: 'users' })).type.toBe<
|
|
Promise<BulkOperationResult<'users', SelectType>>
|
|
>()
|
|
})
|
|
|
|
test('payload.findGlobal', () => {
|
|
expect(payload.findGlobal({ slug: 'menu' })).type.toBe<Promise<Menu>>()
|
|
})
|
|
|
|
test('payload.updateGlobal', () => {
|
|
expect(payload.updateGlobal({ data: {}, slug: 'menu' })).type.toBe<Promise<Menu>>()
|
|
})
|
|
|
|
test('payload.findVersions', () => {
|
|
expect(payload.findVersions({ collection: 'posts' })).type.toBe<
|
|
Promise<PaginatedDocs<TypeWithVersion<Post>>>
|
|
>()
|
|
})
|
|
|
|
test('payload.findVersionByID', () => {
|
|
expect(payload.findVersionByID({ id: 'id', collection: 'posts' })).type.toBe<
|
|
Promise<TypeWithVersion<Post>>
|
|
>()
|
|
})
|
|
|
|
test('payload.findGlobalVersions', () => {
|
|
expect(payload.findGlobalVersions({ slug: 'menu' })).type.toBe<
|
|
Promise<PaginatedDocs<TypeWithVersion<Menu>>>
|
|
>()
|
|
})
|
|
|
|
test('payload.findGlobalVersionByID', () => {
|
|
expect(payload.findGlobalVersionByID({ id: 'id', slug: 'menu' })).type.toBe<
|
|
Promise<TypeWithVersion<Menu>>
|
|
>()
|
|
})
|
|
|
|
describe('select', () => {
|
|
test('should include only ID if select is an empty object', () => {
|
|
expect(payload.findByID({ collection: 'posts', id: 'id', select: {} })).type.toBe<
|
|
Promise<{ id: Post['id'] }>
|
|
>()
|
|
})
|
|
|
|
test('should include only title and ID', () => {
|
|
expect(
|
|
payload.findByID({ collection: 'posts', id: 'id', select: { title: true } }),
|
|
).type.toBe<Promise<{ id: Post['id']; title?: Post['title'] }>>()
|
|
})
|
|
|
|
test('should exclude title', () => {
|
|
expect(
|
|
payload.findByID({ collection: 'posts', id: 'id', select: { title: false } }),
|
|
).type.toBe<Promise<Omit<Post, 'title'>>>()
|
|
})
|
|
})
|
|
|
|
describe('joins', () => {
|
|
test('join query for pages should have type never as pages does not define any joins', () => {
|
|
expect(asType<JoinQuery<'pages'>>()).type.toBe<never>()
|
|
})
|
|
|
|
test('join query for pages-categories should be defined with the relatedPages key', () => {
|
|
expect(asType<JoinQuery<'pages-categories'>>()).type.toBeAssignableWith<{
|
|
relatedPages?: {
|
|
limit?: number
|
|
sort?: string
|
|
where?: Where
|
|
}
|
|
}>()
|
|
})
|
|
})
|
|
|
|
describe('generated types', () => {
|
|
test('has SupportedTimezones', () => {
|
|
expect<SupportedTimezones>().type.toBeAssignableTo<string>()
|
|
})
|
|
|
|
test('has global generated options interface based on select field', () => {
|
|
expect(asType<Post['selectField']>()).type.toBe<MySelectOptions>()
|
|
})
|
|
|
|
test('has global generated options interface based on radio field', () => {
|
|
expect(asType<Post['radioField']>()).type.toBe<MyRadioOptions>()
|
|
})
|
|
})
|
|
|
|
describe('fields', () => {
|
|
describe('Group', () => {
|
|
test('correctly ignores unnamed group', () => {
|
|
expect<Post>().type.toHaveProperty('insideUnnamedGroup')
|
|
})
|
|
|
|
test('generates nested group name', () => {
|
|
expect<Post>().type.toHaveProperty('namedGroup')
|
|
expect<NonNullable<Post['namedGroup']>>().type.toHaveProperty('insideNamedGroup')
|
|
})
|
|
})
|
|
})
|
|
})
|