fix(db-postgres): ensure deletion of numbers and texts in upsertRow (#11787)

### What?
This PR fixes an issue while using `text` & `number` fields with
`hasMany: true` where the last entry would be unreachable, and thus
undeletable, because the `transformForWrite` function did not track
these rows for deletion. This causes values that should've been deleted
to remain in the edit view form, as well as the db, after a submission.

This PR also properly threads the placeholder value from
`admin.placeholder` to `text` & `number` `hasMany: true` fields.

### Why?
To remove rows from the db when a submission is made where these fields
are empty arrays, and to properly show an appropriate placeholder when
one is set in config.

### How?
Adjusting `transformForWrite` and the `traverseFields` to keep track of
rows for deletion.

Fixes #11781

Before:


[Editing---Post-dbpg-before--Payload.webm](https://github.com/user-attachments/assets/5ba1708a-2672-4b36-ac68-05212f3aa6cb)

After:


[Editing---Post--dbpg-hasmany-after-Payload.webm](https://github.com/user-attachments/assets/1292e998-83ff-49d0-aa86-6199be319937)
This commit is contained in:
Said Akhrarov
2025-06-04 10:13:46 -04:00
committed by GitHub
parent c08cdff498
commit bd512f1eda
10 changed files with 158 additions and 17 deletions

View File

@@ -98,6 +98,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
withinArrayOrBlockLocale, withinArrayOrBlockLocale,
}: TraverseFieldsArgs): T => { }: TraverseFieldsArgs): T => {
const sanitizedPath = path ? `${path}.` : path const sanitizedPath = path ? `${path}.` : path
const localeCodes =
adapter.payload.config.localization && adapter.payload.config.localization.localeCodes
const formatted = fields.reduce((result, field) => { const formatted = fields.reduce((result, field) => {
if (fieldIsVirtual(field)) { if (fieldIsVirtual(field)) {
@@ -506,6 +508,10 @@ export const traverseFields = <T extends Record<string, unknown>>({
if (field.type === 'text' && field?.hasMany) { if (field.type === 'text' && field?.hasMany) {
const textPathMatch = texts[`${sanitizedPath}${field.name}`] const textPathMatch = texts[`${sanitizedPath}${field.name}`]
if (!textPathMatch) { if (!textPathMatch) {
result[field.name] =
isLocalized && localeCodes
? Object.fromEntries(localeCodes.map((locale) => [locale, []]))
: []
return result return result
} }
@@ -545,6 +551,10 @@ export const traverseFields = <T extends Record<string, unknown>>({
if (field.type === 'number' && field.hasMany) { if (field.type === 'number' && field.hasMany) {
const numberPathMatch = numbers[`${sanitizedPath}${field.name}`] const numberPathMatch = numbers[`${sanitizedPath}${field.name}`]
if (!numberPathMatch) { if (!numberPathMatch) {
result[field.name] =
isLocalized && localeCodes
? Object.fromEntries(localeCodes.map((locale) => [locale, []]))
: []
return result return result
} }
@@ -606,10 +616,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
} }
if (isLocalized && Array.isArray(table._locales)) { if (isLocalized && Array.isArray(table._locales)) {
if (!table._locales.length && adapter.payload.config.localization) { if (!table._locales.length && localeCodes) {
adapter.payload.config.localization.localeCodes.forEach((_locale) => localeCodes.forEach((_locale) => (table._locales as unknown[]).push({ _locale }))
(table._locales as unknown[]).push({ _locale }),
)
} }
table._locales.forEach((localeRow) => { table._locales.forEach((localeRow) => {
@@ -725,8 +733,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
} }
return result return result
return result
}, dataRef) }, dataRef)
if (Array.isArray(table._locales)) { if (Array.isArray(table._locales)) {

View File

@@ -3,7 +3,13 @@ import type { FlattenedArrayField } from 'payload'
import { fieldShouldBeLocalized } from 'payload/shared' import { fieldShouldBeLocalized } from 'payload/shared'
import type { DrizzleAdapter } from '../../types.js' import type { DrizzleAdapter } from '../../types.js'
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types.js' import type {
ArrayRowToInsert,
BlockRowToInsert,
NumberToDelete,
RelationshipToDelete,
TextToDelete,
} from './types.js'
import { isArrayOfRows } from '../../utilities/isArrayOfRows.js' import { isArrayOfRows } from '../../utilities/isArrayOfRows.js'
import { traverseFields } from './traverseFields.js' import { traverseFields } from './traverseFields.js'
@@ -20,6 +26,7 @@ type Args = {
field: FlattenedArrayField field: FlattenedArrayField
locale?: string locale?: string
numbers: Record<string, unknown>[] numbers: Record<string, unknown>[]
numbersToDelete: NumberToDelete[]
parentIsLocalized: boolean parentIsLocalized: boolean
path: string path: string
relationships: Record<string, unknown>[] relationships: Record<string, unknown>[]
@@ -28,6 +35,7 @@ type Args = {
[tableName: string]: Record<string, unknown>[] [tableName: string]: Record<string, unknown>[]
} }
texts: Record<string, unknown>[] texts: Record<string, unknown>[]
textsToDelete: TextToDelete[]
/** /**
* Set to a locale code if this set of fields is traversed within a * Set to a locale code if this set of fields is traversed within a
* localized array or block field * localized array or block field
@@ -45,12 +53,14 @@ export const transformArray = ({
field, field,
locale, locale,
numbers, numbers,
numbersToDelete,
parentIsLocalized, parentIsLocalized,
path, path,
relationships, relationships,
relationshipsToDelete, relationshipsToDelete,
selects, selects,
texts, texts,
textsToDelete,
withinArrayOrBlockLocale, withinArrayOrBlockLocale,
}: Args) => { }: Args) => {
const newRows: ArrayRowToInsert[] = [] const newRows: ArrayRowToInsert[] = []
@@ -104,6 +114,7 @@ export const transformArray = ({
insideArrayOrBlock: true, insideArrayOrBlock: true,
locales: newRow.locales, locales: newRow.locales,
numbers, numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized, parentIsLocalized: parentIsLocalized || field.localized,
parentTableName: arrayTableName, parentTableName: arrayTableName,
path: `${path || ''}${field.name}.${i}.`, path: `${path || ''}${field.name}.${i}.`,
@@ -112,6 +123,7 @@ export const transformArray = ({
row: newRow.row, row: newRow.row,
selects, selects,
texts, texts,
textsToDelete,
withinArrayOrBlockLocale, withinArrayOrBlockLocale,
}) })

View File

@@ -4,7 +4,12 @@ import { fieldShouldBeLocalized } from 'payload/shared'
import toSnakeCase from 'to-snake-case' import toSnakeCase from 'to-snake-case'
import type { DrizzleAdapter } from '../../types.js' import type { DrizzleAdapter } from '../../types.js'
import type { BlockRowToInsert, RelationshipToDelete } from './types.js' import type {
BlockRowToInsert,
NumberToDelete,
RelationshipToDelete,
TextToDelete,
} from './types.js'
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js' import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js'
import { traverseFields } from './traverseFields.js' import { traverseFields } from './traverseFields.js'
@@ -20,6 +25,7 @@ type Args = {
field: FlattenedBlocksField field: FlattenedBlocksField
locale?: string locale?: string
numbers: Record<string, unknown>[] numbers: Record<string, unknown>[]
numbersToDelete: NumberToDelete[]
parentIsLocalized: boolean parentIsLocalized: boolean
path: string path: string
relationships: Record<string, unknown>[] relationships: Record<string, unknown>[]
@@ -28,6 +34,7 @@ type Args = {
[tableName: string]: Record<string, unknown>[] [tableName: string]: Record<string, unknown>[]
} }
texts: Record<string, unknown>[] texts: Record<string, unknown>[]
textsToDelete: TextToDelete[]
/** /**
* Set to a locale code if this set of fields is traversed within a * Set to a locale code if this set of fields is traversed within a
* localized array or block field * localized array or block field
@@ -43,12 +50,14 @@ export const transformBlocks = ({
field, field,
locale, locale,
numbers, numbers,
numbersToDelete,
parentIsLocalized, parentIsLocalized,
path, path,
relationships, relationships,
relationshipsToDelete, relationshipsToDelete,
selects, selects,
texts, texts,
textsToDelete,
withinArrayOrBlockLocale, withinArrayOrBlockLocale,
}: Args) => { }: Args) => {
data.forEach((blockRow, i) => { data.forEach((blockRow, i) => {
@@ -117,6 +126,7 @@ export const transformBlocks = ({
insideArrayOrBlock: true, insideArrayOrBlock: true,
locales: newRow.locales, locales: newRow.locales,
numbers, numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized, parentIsLocalized: parentIsLocalized || field.localized,
parentTableName: blockTableName, parentTableName: blockTableName,
path: `${path || ''}${field.name}.${i}.`, path: `${path || ''}${field.name}.${i}.`,
@@ -125,6 +135,7 @@ export const transformBlocks = ({
row: newRow.row, row: newRow.row,
selects, selects,
texts, texts,
textsToDelete,
withinArrayOrBlockLocale, withinArrayOrBlockLocale,
}) })

View File

@@ -29,11 +29,13 @@ export const transformForWrite = ({
blocksToDelete: new Set(), blocksToDelete: new Set(),
locales: {}, locales: {},
numbers: [], numbers: [],
numbersToDelete: [],
relationships: [], relationships: [],
relationshipsToDelete: [], relationshipsToDelete: [],
row: {}, row: {},
selects: {}, selects: {},
texts: [], texts: [],
textsToDelete: [],
} }
// This function is responsible for building up the // This function is responsible for building up the
@@ -50,6 +52,7 @@ export const transformForWrite = ({
fields, fields,
locales: rowToInsert.locales, locales: rowToInsert.locales,
numbers: rowToInsert.numbers, numbers: rowToInsert.numbers,
numbersToDelete: rowToInsert.numbersToDelete,
parentIsLocalized, parentIsLocalized,
parentTableName: tableName, parentTableName: tableName,
path, path,
@@ -58,6 +61,7 @@ export const transformForWrite = ({
row: rowToInsert.row, row: rowToInsert.row,
selects: rowToInsert.selects, selects: rowToInsert.selects,
texts: rowToInsert.texts, texts: rowToInsert.texts,
textsToDelete: rowToInsert.textsToDelete,
}) })
return rowToInsert return rowToInsert

View File

@@ -5,7 +5,13 @@ import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
import toSnakeCase from 'to-snake-case' import toSnakeCase from 'to-snake-case'
import type { DrizzleAdapter } from '../../types.js' import type { DrizzleAdapter } from '../../types.js'
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types.js' import type {
ArrayRowToInsert,
BlockRowToInsert,
NumberToDelete,
RelationshipToDelete,
TextToDelete,
} from './types.js'
import { isArrayOfRows } from '../../utilities/isArrayOfRows.js' import { isArrayOfRows } from '../../utilities/isArrayOfRows.js'
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js' import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js'
@@ -51,6 +57,7 @@ type Args = {
[locale: string]: Record<string, unknown> [locale: string]: Record<string, unknown>
} }
numbers: Record<string, unknown>[] numbers: Record<string, unknown>[]
numbersToDelete: NumberToDelete[]
parentIsLocalized: boolean parentIsLocalized: boolean
/** /**
* This is the name of the parent table * This is the name of the parent table
@@ -64,6 +71,7 @@ type Args = {
[tableName: string]: Record<string, unknown>[] [tableName: string]: Record<string, unknown>[]
} }
texts: Record<string, unknown>[] texts: Record<string, unknown>[]
textsToDelete: TextToDelete[]
/** /**
* Set to a locale code if this set of fields is traversed within a * Set to a locale code if this set of fields is traversed within a
* localized array or block field * localized array or block field
@@ -86,6 +94,7 @@ export const traverseFields = ({
insideArrayOrBlock = false, insideArrayOrBlock = false,
locales, locales,
numbers, numbers,
numbersToDelete,
parentIsLocalized, parentIsLocalized,
parentTableName, parentTableName,
path, path,
@@ -94,6 +103,7 @@ export const traverseFields = ({
row, row,
selects, selects,
texts, texts,
textsToDelete,
withinArrayOrBlockLocale, withinArrayOrBlockLocale,
}: Args) => { }: Args) => {
if (row._uuid) { if (row._uuid) {
@@ -136,12 +146,14 @@ export const traverseFields = ({
field, field,
locale: localeKey, locale: localeKey,
numbers, numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized, parentIsLocalized: parentIsLocalized || field.localized,
path, path,
relationships, relationships,
relationshipsToDelete, relationshipsToDelete,
selects, selects,
texts, texts,
textsToDelete,
withinArrayOrBlockLocale: localeKey, withinArrayOrBlockLocale: localeKey,
}) })
@@ -159,12 +171,14 @@ export const traverseFields = ({
data: data[field.name], data: data[field.name],
field, field,
numbers, numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized, parentIsLocalized: parentIsLocalized || field.localized,
path, path,
relationships, relationships,
relationshipsToDelete, relationshipsToDelete,
selects, selects,
texts, texts,
textsToDelete,
withinArrayOrBlockLocale, withinArrayOrBlockLocale,
}) })
@@ -202,12 +216,14 @@ export const traverseFields = ({
field, field,
locale: localeKey, locale: localeKey,
numbers, numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized, parentIsLocalized: parentIsLocalized || field.localized,
path, path,
relationships, relationships,
relationshipsToDelete, relationshipsToDelete,
selects, selects,
texts, texts,
textsToDelete,
withinArrayOrBlockLocale: localeKey, withinArrayOrBlockLocale: localeKey,
}) })
} }
@@ -222,12 +238,14 @@ export const traverseFields = ({
data: fieldData, data: fieldData,
field, field,
numbers, numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized, parentIsLocalized: parentIsLocalized || field.localized,
path, path,
relationships, relationships,
relationshipsToDelete, relationshipsToDelete,
selects, selects,
texts, texts,
textsToDelete,
withinArrayOrBlockLocale, withinArrayOrBlockLocale,
}) })
} }
@@ -257,6 +275,7 @@ export const traverseFields = ({
insideArrayOrBlock, insideArrayOrBlock,
locales, locales,
numbers, numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized, parentIsLocalized: parentIsLocalized || field.localized,
parentTableName, parentTableName,
path: `${path || ''}${field.name}.`, path: `${path || ''}${field.name}.`,
@@ -265,6 +284,7 @@ export const traverseFields = ({
row, row,
selects, selects,
texts, texts,
textsToDelete,
withinArrayOrBlockLocale: localeKey, withinArrayOrBlockLocale: localeKey,
}) })
}) })
@@ -287,6 +307,7 @@ export const traverseFields = ({
insideArrayOrBlock, insideArrayOrBlock,
locales, locales,
numbers, numbers,
numbersToDelete,
parentIsLocalized: parentIsLocalized || field.localized, parentIsLocalized: parentIsLocalized || field.localized,
parentTableName, parentTableName,
path: `${path || ''}${field.name}.`, path: `${path || ''}${field.name}.`,
@@ -295,6 +316,7 @@ export const traverseFields = ({
row, row,
selects, selects,
texts, texts,
textsToDelete,
withinArrayOrBlockLocale, withinArrayOrBlockLocale,
}) })
} }
@@ -380,6 +402,11 @@ export const traverseFields = ({
if (typeof fieldData === 'object') { if (typeof fieldData === 'object') {
Object.entries(fieldData).forEach(([localeKey, localeData]) => { Object.entries(fieldData).forEach(([localeKey, localeData]) => {
if (Array.isArray(localeData)) { if (Array.isArray(localeData)) {
if (!localeData.length) {
textsToDelete.push({ locale: localeKey, path: textPath })
return
}
transformTexts({ transformTexts({
baseRow: { baseRow: {
locale: localeKey, locale: localeKey,
@@ -392,6 +419,11 @@ export const traverseFields = ({
}) })
} }
} else if (Array.isArray(fieldData)) { } else if (Array.isArray(fieldData)) {
if (!fieldData.length) {
textsToDelete.push({ locale: withinArrayOrBlockLocale, path: textPath })
return
}
transformTexts({ transformTexts({
baseRow: { baseRow: {
locale: withinArrayOrBlockLocale, locale: withinArrayOrBlockLocale,
@@ -412,6 +444,11 @@ export const traverseFields = ({
if (typeof fieldData === 'object') { if (typeof fieldData === 'object') {
Object.entries(fieldData).forEach(([localeKey, localeData]) => { Object.entries(fieldData).forEach(([localeKey, localeData]) => {
if (Array.isArray(localeData)) { if (Array.isArray(localeData)) {
if (!localeData.length) {
numbersToDelete.push({ locale: localeKey, path: numberPath })
return
}
transformNumbers({ transformNumbers({
baseRow: { baseRow: {
locale: localeKey, locale: localeKey,
@@ -424,6 +461,11 @@ export const traverseFields = ({
}) })
} }
} else if (Array.isArray(fieldData)) { } else if (Array.isArray(fieldData)) {
if (!fieldData.length) {
numbersToDelete.push({ locale: withinArrayOrBlockLocale, path: numberPath })
return
}
transformNumbers({ transformNumbers({
baseRow: { baseRow: {
locale: withinArrayOrBlockLocale, locale: withinArrayOrBlockLocale,

View File

@@ -23,6 +23,16 @@ export type RelationshipToDelete = {
path: string path: string
} }
export type TextToDelete = {
locale?: string
path: string
}
export type NumberToDelete = {
locale?: string
path: string
}
export type RowToInsert = { export type RowToInsert = {
arrays: { arrays: {
[tableName: string]: ArrayRowToInsert[] [tableName: string]: ArrayRowToInsert[]
@@ -35,6 +45,7 @@ export type RowToInsert = {
[locale: string]: Record<string, unknown> [locale: string]: Record<string, unknown>
} }
numbers: Record<string, unknown>[] numbers: Record<string, unknown>[]
numbersToDelete: NumberToDelete[]
relationships: Record<string, unknown>[] relationships: Record<string, unknown>[]
relationshipsToDelete: RelationshipToDelete[] relationshipsToDelete: RelationshipToDelete[]
row: Record<string, unknown> row: Record<string, unknown>
@@ -42,4 +53,5 @@ export type RowToInsert = {
[tableName: string]: Record<string, unknown>[] [tableName: string]: Record<string, unknown>[]
} }
texts: Record<string, unknown>[] texts: Record<string, unknown>[]
textsToDelete: TextToDelete[]
} }

View File

@@ -211,7 +211,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
parentColumnName: 'parent', parentColumnName: 'parent',
parentID: insertedRow.id, parentID: insertedRow.id,
pathColumnName: 'path', pathColumnName: 'path',
rows: textsToInsert, rows: [...textsToInsert, ...rowToInsert.textsToDelete],
tableName: textsTableName, tableName: textsTableName,
}) })
} }
@@ -238,7 +238,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
parentColumnName: 'parent', parentColumnName: 'parent',
parentID: insertedRow.id, parentID: insertedRow.id,
pathColumnName: 'path', pathColumnName: 'path',
rows: numbersToInsert, rows: [...numbersToInsert, ...rowToInsert.numbersToDelete],
tableName: numbersTableName, tableName: numbersTableName,
}) })
} }

View File

@@ -26,7 +26,7 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
admin: { admin: {
className, className,
description, description,
placeholder, placeholder: placeholderFromProps,
step = 1, step = 1,
} = {} as NumberFieldClientProps['field']['admin'], } = {} as NumberFieldClientProps['field']['admin'],
hasMany = false, hasMany = false,
@@ -126,6 +126,8 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
const styles = useMemo(() => mergeFieldStyles(field), [field]) const styles = useMemo(() => mergeFieldStyles(field), [field])
const placeholder = getTranslation(placeholderFromProps, i18n)
return ( return (
<div <div
className={[ className={[
@@ -174,7 +176,7 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
// numberOnly // numberOnly
onChange={handleHasManyChange} onChange={handleHasManyChange}
options={[]} options={[]}
placeholder={t('general:enterAValue')} placeholder={placeholder}
showError={showError} showError={showError}
value={valueToRender as Option[]} value={valueToRender as Option[]}
/> />
@@ -191,7 +193,7 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
// @ts-expect-error // @ts-expect-error
e.target.blur() e.target.blur()
}} }}
placeholder={getTranslation(placeholder, i18n)} placeholder={placeholder}
step={step} step={step}
type="number" type="number"
value={typeof value === 'number' ? value : ''} value={typeof value === 'number' ? value : ''}

View File

@@ -34,7 +34,7 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
onChange, onChange,
onKeyDown, onKeyDown,
path, path,
placeholder, placeholder: placeholderFromProps,
readOnly, readOnly,
required, required,
rtl, rtl,
@@ -91,6 +91,8 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
} }
} }
const placeholder = getTranslation(placeholderFromProps, i18n)
return ( return (
<div <div
className={[ className={[
@@ -143,7 +145,7 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
}} }}
onChange={onChange} onChange={onChange}
options={[]} options={[]}
placeholder={t('general:enterAValue')} placeholder={placeholder}
showError={showError} showError={showError}
value={valueToRender} value={valueToRender}
/> />
@@ -155,7 +157,7 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
name={path} name={path}
onChange={onChange as (e: ChangeEvent<HTMLInputElement>) => void} onChange={onChange as (e: ChangeEvent<HTMLInputElement>) => void}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
placeholder={getTranslation(placeholder, i18n)} placeholder={placeholder}
ref={inputRef} ref={inputRef}
type="text" type="text"
value={value || ''} value={value || ''}

View File

@@ -33,6 +33,7 @@ import {
checkboxFieldsSlug, checkboxFieldsSlug,
collapsibleFieldsSlug, collapsibleFieldsSlug,
groupFieldsSlug, groupFieldsSlug,
numberFieldsSlug,
relationshipFieldsSlug, relationshipFieldsSlug,
tabsFieldsSlug, tabsFieldsSlug,
textFieldsSlug, textFieldsSlug,
@@ -401,6 +402,31 @@ describe('Fields', () => {
expect(resInSecond.totalDocs).toBe(1) expect(resInSecond.totalDocs).toBe(1)
}) })
it('should delete rows when updating hasMany with empty array', async () => {
const { id: createdDocId } = await payload.create({
collection: textFieldsSlug,
data: {
text: 'hasMany deletion test',
hasMany: ['one', 'two', 'three'],
},
})
await payload.update({
collection: textFieldsSlug,
id: createdDocId,
data: {
hasMany: [],
},
})
const resultingDoc = await payload.findByID({
collection: textFieldsSlug,
id: createdDocId,
})
expect(resultingDoc.hasMany).toHaveLength(0)
})
}) })
describe('relationship', () => { describe('relationship', () => {
@@ -1042,6 +1068,30 @@ describe('Fields', () => {
expect(numbersNotExists.docs).toHaveLength(1) expect(numbersNotExists.docs).toHaveLength(1)
}) })
it('should delete rows when updating hasMany with empty array', async () => {
const { id: createdDocId } = await payload.create({
collection: numberFieldsSlug,
data: {
localizedHasMany: [1, 2, 3],
},
})
await payload.update({
collection: numberFieldsSlug,
id: createdDocId,
data: {
localizedHasMany: [],
},
})
const resultingDoc = await payload.findByID({
collection: numberFieldsSlug,
id: createdDocId,
})
expect(resultingDoc.localizedHasMany).toHaveLength(0)
})
}) })
it('should query hasMany within an array', async () => { it('should query hasMany within an array', async () => {