diff --git a/packages/payload/src/fields/hooks/beforeChange/promise.ts b/packages/payload/src/fields/hooks/beforeChange/promise.ts index 3aac685049..01c00171a2 100644 --- a/packages/payload/src/fields/hooks/beforeChange/promise.ts +++ b/packages/payload/src/fields/hooks/beforeChange/promise.ts @@ -13,6 +13,7 @@ import { getLabelFromPath } from '../../../utilities/getLabelFromPath.js' import { getTranslatedLabel } from '../../../utilities/getTranslatedLabel.js' import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from '../../config/types.js' import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js' +import { getParentLabels } from '../../utilities/getParentLabels.js' import { getExistingRowDoc } from './getExistingRowDoc.js' import { traverseFields } from './traverseFields.js' @@ -174,35 +175,13 @@ export const promise = async ({ }) if (typeof validationResult === 'string') { const label = getTranslatedLabel(field?.label || field?.name, req.i18n) + const parentPathSegments = parentPath ? parentPath.split('.') : [] + const parentLabels = getParentLabels(parentPathSegments, collection.fields) - const findLabelInFields = (fields, pathSegments) => { - if (pathSegments.length === 0) {return undefined} - - const [currentSegment, ...remainingSegments] = pathSegments - - for (const field of fields) { - if (field.name === currentSegment) { - if (remainingSegments.length === 0) { - return field.label - } - - if (field.fields && field.fields.length > 0) { - return findLabelInFields(field.fields, remainingSegments) - } - } - } - } - - const parentLabels = parentPath.split('.').map((segment, index, array) => { - const fullPath = array.slice(0, index + 1).join('.') - const pathSegments = fullPath.split('.') - - const parentLabel = findLabelInFields(collection.flattenedFields, pathSegments) || segment - - return getTranslatedLabel(parentLabel, req.i18n) - }) - - const fieldLabel = parentLabels.length > 0 ? [...parentLabels, label].join(' > ') : label + const fieldLabel = + Array.isArray(parentLabels) && parentLabels.length > 0 + ? getLabelFromPath(parentLabels.concat(label)) + : label errors.push({ label: fieldLabel, diff --git a/packages/payload/src/fields/utilities/getParentLabels.ts b/packages/payload/src/fields/utilities/getParentLabels.ts new file mode 100644 index 0000000000..0f7b134e2f --- /dev/null +++ b/packages/payload/src/fields/utilities/getParentLabels.ts @@ -0,0 +1,37 @@ +export function getParentLabels( + parentPathSegments: (number | string)[], + collectionFields: any[], +): (number | string)[] { + let currentFields = collectionFields + const labels: (number | string)[] = [] + + for (const segment of parentPathSegments) { + // If it is a number, it's an index and should be returned + if (!isNaN(Number(segment))) { + labels.push(segment) + continue + } + + // Find the field that matches the current path segment + const field = currentFields.find((f) => f.name === segment) + if (!field) { + break + } + + let fieldLabel = field.label ?? field.name + if (field.labels && typeof field.labels === 'object' && field.labels.singular) { + fieldLabel = field.labels.singular + } + + labels.push(fieldLabel) + + // Loop the nested fields if they exist + if (field.fields) { + currentFields = field.fields + } else { + break + } + } + + return labels +} diff --git a/test/fields/collections/Array/e2e.spec.ts b/test/fields/collections/Array/e2e.spec.ts index 01572933c0..1f92a32599 100644 --- a/test/fields/collections/Array/e2e.spec.ts +++ b/test/fields/collections/Array/e2e.spec.ts @@ -134,6 +134,34 @@ describe('Array', () => { await expect(page.locator('#field-items #items-row-0 .row-label')).toContainText('Item 01') }) + test('should show correct labels for array rows in toast error', async () => { + await page.goto(url.create) + const nestedArrayField = page.locator('#field-nestedArrayWithLabels >> .array-field__add-row') + await nestedArrayField.click() + const firstChildArrayField = page.locator( + '#field-nestedArrayWithLabels__0__firstChildArray >> .array-field__add-row', + ) + await firstChildArrayField.click() + const secondChildArrayField = page.locator( + '#field-nestedArrayWithLabels__0__firstChildArray__0__secondChildArray >> .array-field__add-row', + ) + await secondChildArrayField.click() + const childWithoutLabelField = page.locator( + '#field-nestedArrayWithLabels__0__firstChildArray__0__secondChildArray__0__childWithoutLabel >> .array-field__add-row', + ) + await childWithoutLabelField.click() + + const customTextLabel = page.locator( + '#field-nestedArrayWithLabels__0__firstChildArray__0__secondChildArray__0__childWithoutLabel__0__text', + ) + await customTextLabel.fill('') + + await page.click('#action-save') + await expect(page.locator('.payload-toast-container')).toContainText( + 'The following field is invalid: Nested Array With Labels 1 > First Child Array 1 > Second Child Array 1 > Child Without Label 1 > Custom Text Label', + ) + }) + test('ensure functions passed to array field labels property are respected', async () => { await page.goto(url.create)