Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import type { ClientField } from 'payload'

import { describe, expect, it } from 'vitest'

import { reduceFields } from './reduceFields.js'

const values = (result: ReturnType<typeof reduceFields>) => result.map((f) => f.value)

describe('reduceFields', () => {
describe('excludeUnsortable', () => {
it('should include array and blocks fields by default', () => {
const fields: ClientField[] = [
{ name: 'title', type: 'text' },
{ name: 'items', type: 'array', fields: [{ name: 'text', type: 'text' }] },
{ name: 'layout', type: 'blocks', blocks: [] },
]

const result = values(reduceFields({ fields }))

expect(result).toContain('title')
expect(result).toContain('items')
expect(result).toContain('layout')
})

it('should exclude array fields when excludeUnsortable is true', () => {
const fields: ClientField[] = [
{ name: 'title', type: 'text' },
{ name: 'items', type: 'array', fields: [{ name: 'text', type: 'text' }] },
]

const result = values(reduceFields({ excludeUnsortable: true, fields }))

expect(result).toContain('title')
expect(result).not.toContain('items')
})

it('should exclude blocks fields when excludeUnsortable is true', () => {
const fields: ClientField[] = [
{ name: 'title', type: 'text' },
{ name: 'layout', type: 'blocks', blocks: [] },
]

const result = values(reduceFields({ excludeUnsortable: true, fields }))

expect(result).toContain('title')
expect(result).not.toContain('layout')
})
})

describe('disabledFields', () => {
it('should include all fields when disabledFields is empty', () => {
const fields: ClientField[] = [
{ name: 'title', type: 'text' },
{ name: 'slug', type: 'text' },
]

const result = values(reduceFields({ disabledFields: [], fields }))

expect(result).toContain('title')
expect(result).toContain('slug')
})

it('should exclude a field whose path is in disabledFields', () => {
const fields: ClientField[] = [
{ name: 'title', type: 'text' },
{ name: 'slug', type: 'text' },
]

const result = values(reduceFields({ disabledFields: ['slug'], fields }))

expect(result).toContain('title')
expect(result).not.toContain('slug')
})

it('should exclude nested fields whose paths start with a disabled parent path', () => {
const fields: ClientField[] = [
{
name: 'meta',
type: 'group',
fields: [
{ name: 'title', type: 'text' },
{ name: 'description', type: 'text' },
],
},
]

const result = values(reduceFields({ disabledFields: ['meta.description'], fields }))

expect(result).toContain('meta.title')
expect(result).not.toContain('meta.description')
})
})

describe('combined excludeUnsortable and disabledFields', () => {
it('should apply both filters simultaneously', () => {
const fields: ClientField[] = [
{ name: 'title', type: 'text' },
{ name: 'slug', type: 'text' },
{ name: 'items', type: 'array', fields: [{ name: 'text', type: 'text' }] },
]

const result = values(
reduceFields({ disabledFields: ['slug'], excludeUnsortable: true, fields }),
)

expect(result).toContain('title')
expect(result).not.toContain('slug')
expect(result).not.toContain('items')
})
})

describe('recursive propagation through group sub-fields', () => {
it('should propagate excludeUnsortable into group sub-fields', () => {
const fields: ClientField[] = [
{
name: 'meta',
type: 'group',
fields: [
{ name: 'title', type: 'text' },
{ name: 'tags', type: 'array', fields: [{ name: 'tag', type: 'text' }] },
],
},
]

const result = values(reduceFields({ excludeUnsortable: true, fields }))

expect(result).toContain('meta.title')
expect(result).not.toContain('meta.tags')
})

it('should propagate disabledFields into group sub-fields', () => {
const fields: ClientField[] = [
{
name: 'meta',
type: 'group',
fields: [
{ name: 'title', type: 'text' },
{ name: 'description', type: 'text' },
],
},
]

const result = values(reduceFields({ disabledFields: ['meta.description'], fields }))

expect(result).toContain('meta.title')
expect(result).not.toContain('meta.description')
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ const combineLabel = ({

export const reduceFields = ({
disabledFields = [],
excludeUnsortable = false,
fields,
labelPrefix = null,
path = '',
}: {
disabledFields?: string[]
excludeUnsortable?: boolean
fields: ClientField[]
labelPrefix?: React.ReactNode
path?: string
Expand All @@ -59,16 +61,19 @@ export const reduceFields = ({

return fields.reduce<{ id: string; label: React.ReactNode; value: string }[]>(
(fieldsToUse, field) => {
const isArrayOrBlocks = field.type === 'array' || field.type === 'blocks'

// escape for a variety of reasons, include ui fields as they have `name`.
if (field.type === 'ui') {
if (field.type === 'ui' || (excludeUnsortable && isArrayOrBlocks)) {
return fieldsToUse
}

if (!(field.type === 'array' || field.type === 'blocks') && fieldHasSubFields(field)) {
if (!isArrayOrBlocks && fieldHasSubFields(field)) {
return [
...fieldsToUse,
...reduceFields({
disabledFields,
excludeUnsortable,
fields: field.fields,
labelPrefix: combineLabel({ field, prefix: labelPrefix }),
path: createNestedClientFieldPath(path, field),
Expand All @@ -90,6 +95,7 @@ export const reduceFields = ({
...tabFields,
...reduceFields({
disabledFields,
excludeUnsortable,
fields: tab.fields,
labelPrefix: isNamedTab
? combineLabel({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const SortBy: SelectFieldClientComponent = (props) => {

const collectionConfig = getEntityConfig({ collectionSlug: collectionSlug ?? collection })
const fieldOptions = useMemo(
() => reduceFields({ fields: collectionConfig?.fields }),
() => reduceFields({ excludeUnsortable: true, fields: collectionConfig?.fields }),
[collectionConfig?.fields],
)

Expand Down
Loading