fix(next): version view breaking for deeply nested tabs, rows and collapsibles (#11808)

Fixes #11458 

Some complex, nested fields were receiving incorrect field paths and
schema paths, leading to a `"Error: No client field found"` error.

This PR ensures field paths are calculated correctly, by matching it to
how they're calculated in payload hooks.
This commit is contained in:
Alessio Gravili
2025-03-24 14:57:36 -06:00
committed by GitHub
parent fb01b4046d
commit 3c4b3ee527
7 changed files with 194 additions and 26 deletions

View File

@@ -84,7 +84,7 @@ export const buildVersionFields = ({
const { indexPath, path, schemaPath } = getFieldPathsModified({
field,
index: fieldIndex,
parentIndexPath: 'name' in field ? '' : parentIndexPath,
parentIndexPath,
parentPath,
parentSchemaPath,
})
@@ -253,15 +253,14 @@ const buildVersionField = ({
tabIndex++
const isNamedTab = tabHasName(tab)
const tabAsField = { ...tab, type: 'tab' }
const {
indexPath: tabIndexPath,
path: tabPath,
schemaPath: tabSchemaPath,
} = getFieldPathsModified({
field: {
...tab,
type: 'tab',
},
field: tabAsField,
index: tabIndex,
parentIndexPath: indexPath,
parentPath,
@@ -280,8 +279,8 @@ const buildVersionField = ({
modifiedOnly,
parentIndexPath: isNamedTab ? '' : tabIndexPath,
parentIsLocalized: parentIsLocalized || tab.localized,
parentPath: tabPath,
parentSchemaPath: tabSchemaPath,
parentPath: isNamedTab ? tabPath : path,
parentSchemaPath: isNamedTab ? tabSchemaPath : parentSchemaPath,
req,
selectedLocales,
versionSiblingData: 'name' in tab ? versionValue?.[tab.name] : versionValue,
@@ -289,7 +288,7 @@ const buildVersionField = ({
label: tab.label,
})
}
} // At this point, we are dealing with a `row`, etc
} // At this point, we are dealing with a `row`, `collapsible`, etc
else if ('fields' in field) {
if (field.type === 'array' && versionValue) {
const arrayValue = Array.isArray(versionValue) ? versionValue : []
@@ -328,8 +327,8 @@ const buildVersionField = ({
modifiedOnly,
parentIndexPath: 'name' in field ? '' : indexPath,
parentIsLocalized: parentIsLocalized || ('localized' in field && field.localized),
parentPath: path,
parentSchemaPath: schemaPath,
parentPath: 'name' in field ? path : parentPath,
parentSchemaPath: 'name' in field ? schemaPath : parentSchemaPath,
req,
selectedLocales,
versionSiblingData: versionValue as object,

View File

@@ -42,19 +42,11 @@ export function getFieldPathsModified({
const parentPathToUse = parentIsUnnamed ? parentWithoutIndex : parentPath
const parentSchemaPathSegments = parentSchemaPath.split('.')
const parentSchemaIsUnnamed =
parentSchemaPathSegments[parentSchemaPathSegments.length - 1].startsWith('_index-')
const parentSchemaWithoutIndex = parentSchemaIsUnnamed
? parentSchemaPathSegments.slice(0, -1).join('.')
: parentSchemaPath
const parentSchemaPathToUse = parentSchemaIsUnnamed ? parentSchemaWithoutIndex : parentSchemaPath
if ('name' in field) {
return {
indexPath: '',
path: `${parentPathToUse ? parentPathToUse + '.' : ''}${field.name}`,
schemaPath: `${parentSchemaPathToUse ? parentSchemaPathToUse + '.' : ''}${field.name}`,
schemaPath: `${parentSchemaPath ? parentSchemaPath + '.' : ''}${field.name}`,
}
}
@@ -63,6 +55,6 @@ export function getFieldPathsModified({
return {
indexPath: `${parentIndexPath ? parentIndexPath + '-' : ''}${index}`,
path: `${parentPathToUse ? parentPathToUse + '.' : ''}${indexSuffix}`,
schemaPath: `${!parentIsUnnamed && parentSchemaPathToUse ? parentSchemaPathToUse + '.' : ''}${indexSuffix}`,
schemaPath: `${!parentIsUnnamed && parentSchemaPath ? parentSchemaPath + '.' : ''}${indexSuffix}`,
}
}

View File

@@ -39,6 +39,64 @@ export const Diff: CollectionConfig = {
},
],
},
{
slug: 'CollapsibleBlock',
fields: [
{
type: 'collapsible',
label: 'Collapsible',
fields: [
{
type: 'collapsible',
label: 'Nested Collapsible',
fields: [
{
name: 'textInCollapsibleInCollapsibleBlock',
type: 'text',
},
],
},
{
type: 'row',
fields: [
{
name: 'textInRowInCollapsibleBlock',
type: 'text',
},
],
},
],
},
],
},
{
slug: 'TabsBlock',
fields: [
{
type: 'tabs',
tabs: [
{
name: 'namedTab1InBlock',
fields: [
{
name: 'textInNamedTab1InBlock',
type: 'text',
},
],
},
{
label: 'Unnamed Tab 2 In Block',
fields: [
{
name: 'textInUnnamedTab2InBlock',
type: 'text',
},
],
},
],
},
],
},
],
},
{

View File

@@ -1213,6 +1213,62 @@ describe('Versions', () => {
await expect(textInBlock.locator('tr').nth(1).locator('td').nth(3)).toHaveText('textInBlock2')
})
test('correctly renders diff for collapsibles within block fields', async () => {
await navigateToVersionFieldsDiff()
const textInBlock = page.locator(
'[data-field-path="blocks.1.textInCollapsibleInCollapsibleBlock"]',
)
await expect(textInBlock.locator('tr').nth(1).locator('td').nth(1)).toHaveText(
'textInCollapsibleInCollapsibleBlock',
)
await expect(textInBlock.locator('tr').nth(1).locator('td').nth(3)).toHaveText(
'textInCollapsibleInCollapsibleBlock2',
)
})
test('correctly renders diff for rows within block fields', async () => {
await navigateToVersionFieldsDiff()
const textInBlock = page.locator('[data-field-path="blocks.1.textInRowInCollapsibleBlock"]')
await expect(textInBlock.locator('tr').nth(1).locator('td').nth(1)).toHaveText(
'textInRowInCollapsibleBlock',
)
await expect(textInBlock.locator('tr').nth(1).locator('td').nth(3)).toHaveText(
'textInRowInCollapsibleBlock2',
)
})
test('correctly renders diff for named tabs within block fields', async () => {
await navigateToVersionFieldsDiff()
const textInBlock = page.locator(
'[data-field-path="blocks.2.namedTab1InBlock.textInNamedTab1InBlock"]',
)
await expect(textInBlock.locator('tr').nth(1).locator('td').nth(1)).toHaveText(
'textInNamedTab1InBlock',
)
await expect(textInBlock.locator('tr').nth(1).locator('td').nth(3)).toHaveText(
'textInNamedTab1InBlock2',
)
})
test('correctly renders diff for unnamed tabs within block fields', async () => {
await navigateToVersionFieldsDiff()
const textInBlock = page.locator('[data-field-path="blocks.2.textInUnnamedTab2InBlock"]')
await expect(textInBlock.locator('tr').nth(1).locator('td').nth(1)).toHaveText(
'textInUnnamedTab2InBlock',
)
await expect(textInBlock.locator('tr').nth(1).locator('td').nth(3)).toHaveText(
'textInUnnamedTab2InBlock2',
)
})
test('correctly renders diff for checkbox fields', async () => {
await navigateToVersionFieldsDiff()

View File

@@ -54,6 +54,7 @@ export type SupportedTimezones =
| 'Asia/Singapore'
| 'Asia/Tokyo'
| 'Asia/Seoul'
| 'Australia/Brisbane'
| 'Australia/Sydney'
| 'Pacific/Guam'
| 'Pacific/Noumea'
@@ -312,12 +313,30 @@ export interface Diff {
}[]
| null;
blocks?:
| {
textInBlock?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'TextBlock';
}[]
| (
| {
textInBlock?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'TextBlock';
}
| {
textInCollapsibleInCollapsibleBlock?: string | null;
textInRowInCollapsibleBlock?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'CollapsibleBlock';
}
| {
namedTab1InBlock?: {
textInNamedTab1InBlock?: string | null;
};
textInUnnamedTab2InBlock?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'TabsBlock';
}
)[]
| null;
checkbox?: boolean | null;
code?: string | null;
@@ -772,6 +791,26 @@ export interface DiffSelect<T extends boolean = true> {
id?: T;
blockName?: T;
};
CollapsibleBlock?:
| T
| {
textInCollapsibleInCollapsibleBlock?: T;
textInRowInCollapsibleBlock?: T;
id?: T;
blockName?: T;
};
TabsBlock?:
| T
| {
namedTab1InBlock?:
| T
| {
textInNamedTab1InBlock?: T;
};
textInUnnamedTab2InBlock?: T;
id?: T;
blockName?: T;
};
};
checkbox?: T;
code?: T;

View File

@@ -138,6 +138,18 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
blockType: 'TextBlock',
textInBlock: 'textInBlock',
},
{
blockType: 'CollapsibleBlock',
textInCollapsibleInCollapsibleBlock: 'textInCollapsibleInCollapsibleBlock',
textInRowInCollapsibleBlock: 'textInRowInCollapsibleBlock',
},
{
blockType: 'TabsBlock',
namedTab1InBlock: {
textInNamedTab1InBlock: 'textInNamedTab1InBlock',
},
textInUnnamedTab2InBlock: 'textInUnnamedTab2InBlock',
},
],
checkbox: true,
code: 'code',
@@ -186,6 +198,18 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
blockType: 'TextBlock',
textInBlock: 'textInBlock2',
},
{
blockType: 'CollapsibleBlock',
textInCollapsibleInCollapsibleBlock: 'textInCollapsibleInCollapsibleBlock2',
textInRowInCollapsibleBlock: 'textInRowInCollapsibleBlock2',
},
{
blockType: 'TabsBlock',
namedTab1InBlock: {
textInNamedTab1InBlock: 'textInNamedTab1InBlock2',
},
textInUnnamedTab2InBlock: 'textInUnnamedTab2InBlock2',
},
],
checkbox: false,
code: 'code2',