fix: duplication with localized arrays in unnamed tabs (#8236)

Fixes a case where in relational DBs, you can't duplicate documents if
you have localized arrays within unnamed tabs.

The `beforeDuplicate` hooks were not being run for fields within unnamed
tabs.
This commit is contained in:
James Mikrut
2024-09-16 11:43:36 -04:00
committed by GitHub
parent a0a1e20193
commit c460868e52
5 changed files with 304 additions and 124 deletions

View File

@@ -40,6 +40,24 @@ export const promise = async <T>({
parentSchemaPath, parentSchemaPath,
}) })
// Handle unnamed tabs
if (field.type === 'tab' && !tabHasName(field)) {
await traverseFields({
id,
collection,
context,
doc,
fields: field.fields,
overrideAccess,
path: fieldPath,
req,
schemaPath: fieldSchemaPath,
siblingDoc,
})
return
}
if (fieldAffectsData(field)) { if (fieldAffectsData(field)) {
let fieldData = siblingDoc?.[field.name] let fieldData = siblingDoc?.[field.name]
const fieldIsLocalized = field.localized && localization const fieldIsLocalized = field.localized && localization
@@ -207,20 +225,6 @@ export const promise = async <T>({
switch (field.type) { switch (field.type) {
case 'tab': case 'tab':
case 'group': { case 'group': {
if (field.type === 'tab' && !tabHasName(field)) {
await traverseFields({
id,
collection,
context,
doc,
fields: field.fields,
overrideAccess,
path: fieldPath,
req,
schemaPath: fieldSchemaPath,
siblingDoc,
})
} else {
if (typeof siblingDoc[field.name] !== 'object') { if (typeof siblingDoc[field.name] !== 'object') {
siblingDoc[field.name] = {} siblingDoc[field.name] = {}
} }
@@ -239,7 +243,6 @@ export const promise = async <T>({
schemaPath: fieldSchemaPath, schemaPath: fieldSchemaPath,
siblingDoc: groupDoc as JsonObject, siblingDoc: groupDoc as JsonObject,
}) })
}
break break
} }

View File

@@ -122,6 +122,16 @@ export default buildConfigWithDefaults({
required: true, required: true,
type: 'text', type: 'text',
}, },
{
type: 'tabs',
tabs: [
{
label: 'Main Nav',
fields: [
{
name: 'nav',
type: 'group',
fields: [
{ {
name: 'layout', name: 'layout',
blocks: [ blocks: [
@@ -139,6 +149,28 @@ export default buildConfigWithDefaults({
name: 'text', name: 'text',
type: 'text', type: 'text',
}, },
{
name: 'l2',
type: 'array',
fields: [
{
name: 'l3',
type: 'array',
fields: [
{
name: 'l4',
type: 'array',
fields: [
{
name: 'superNestedText',
type: 'text',
},
],
},
],
},
],
},
], ],
}, },
], ],
@@ -158,9 +190,10 @@ export default buildConfigWithDefaults({
required: true, required: true,
type: 'blocks', type: 'blocks',
}, },
{ ],
type: 'tabs', },
tabs: [ ],
},
{ {
name: 'myTab', name: 'myTab',
fields: [ fields: [

View File

@@ -205,10 +205,10 @@ describe('Localization', () => {
await page.goto(urlWithRequiredLocalizedFields.create) await page.goto(urlWithRequiredLocalizedFields.create)
await changeLocale(page, defaultLocale) await changeLocale(page, defaultLocale)
await page.locator('#field-title').fill(englishTitle) await page.locator('#field-title').fill(englishTitle)
await page.locator('#field-layout .blocks-field__drawer-toggler').click() await page.locator('#field-nav__layout .blocks-field__drawer-toggler').click()
await page.locator('button[title="Text"]').click() await page.locator('button[title="Text"]').click()
await page.fill('#field-layout__0__text', 'test') await page.fill('#field-nav__layout__0__text', 'test')
await expect(page.locator('#field-layout__0__text')).toHaveValue('test') await expect(page.locator('#field-nav__layout__0__text')).toHaveValue('test')
await saveDocAndAssert(page) await saveDocAndAssert(page)
const originalID = await page.locator('.id-label').innerText() const originalID = await page.locator('.id-label').innerText()
await openDocControls(page) await openDocControls(page)
@@ -256,6 +256,10 @@ describe('Localization', () => {
async function fillValues(data: Partial<LocalizedPost>) { async function fillValues(data: Partial<LocalizedPost>) {
const { description: descVal, title: titleVal } = data const { description: descVal, title: titleVal } = data
if (titleVal) {await page.locator('#field-title').fill(titleVal)} if (titleVal) {
if (descVal) {await page.locator('#field-description').fill(descVal)} await page.locator('#field-title').fill(titleVal)
}
if (descVal) {
await page.locator('#field-description').fill(descVal)
}
} }

View File

@@ -709,12 +709,14 @@ describe('Localization', () => {
const newDoc = await payload.create({ const newDoc = await payload.create({
collection: withRequiredLocalizedFields, collection: withRequiredLocalizedFields,
data: { data: {
nav: {
layout: [ layout: [
{ {
blockType: 'text', blockType: 'text',
text: 'laiwejfilwaje', text: 'laiwejfilwaje',
}, },
], ],
},
title: 'hello', title: 'hello',
}, },
}) })
@@ -723,12 +725,14 @@ describe('Localization', () => {
id: newDoc.id, id: newDoc.id,
collection: withRequiredLocalizedFields, collection: withRequiredLocalizedFields,
data: { data: {
nav: {
layout: [ layout: [
{ {
blockType: 'number', blockType: 'number',
number: 12, number: 12,
}, },
], ],
},
title: 'en espanol, big bird', title: 'en espanol, big bird',
}, },
locale: spanishLocale, locale: spanishLocale,
@@ -742,7 +746,7 @@ describe('Localization', () => {
}, },
}) })
expect(updatedDoc.layout[0].blockType).toStrictEqual('text') expect(updatedDoc.nav.layout[0].blockType).toStrictEqual('text')
const spanishDoc = await payload.findByID({ const spanishDoc = await payload.findByID({
id: newDoc.id, id: newDoc.id,
@@ -750,7 +754,7 @@ describe('Localization', () => {
locale: spanishLocale, locale: spanishLocale,
}) })
expect(spanishDoc.layout[0].blockType).toStrictEqual('number') expect(spanishDoc.nav.layout[0].blockType).toStrictEqual('number')
}) })
}) })
@@ -1126,12 +1130,15 @@ describe('Localization', () => {
// - and this needs to be done recursively for all block / array fields // - and this needs to be done recursively for all block / array fields
// 2. make sure localized arrays / blocks work inside of localized groups / tabs // 2. make sure localized arrays / blocks work inside of localized groups / tabs
// - this is covered with myTab.group.nestedArray2 // - this is covered with myTab.group.nestedArray2
// 3. the field schema for `nav` is within an unnamed tab, which tests that we
// properly recursively loop through all field structures / types
const englishText = 'english' const englishText = 'english'
const spanishText = 'spanish' const spanishText = 'spanish'
const doc = await payload.create({ const doc = await payload.create({
collection: withRequiredLocalizedFields, collection: withRequiredLocalizedFields,
data: { data: {
nav: {
layout: [ layout: [
{ {
blockType: 'text', blockType: 'text',
@@ -1139,13 +1146,40 @@ describe('Localization', () => {
nestedArray: [ nestedArray: [
{ {
text: 'hello', text: 'hello',
l2: [
{
l3: [
{
l4: [
{
superNestedText: 'hello',
},
],
},
],
},
],
}, },
{ {
text: 'goodbye', text: 'goodbye',
l2: [
{
l3: [
{
l4: [
{
superNestedText: 'goodbye',
}, },
], ],
}, },
], ],
},
],
},
],
},
],
},
myTab: { myTab: {
text: 'hello', text: 'hello',
group: { group: {
@@ -1169,6 +1203,7 @@ describe('Localization', () => {
id: doc.id, id: doc.id,
collection: withRequiredLocalizedFields, collection: withRequiredLocalizedFields,
data: { data: {
nav: {
layout: [ layout: [
{ {
blockType: 'text', blockType: 'text',
@@ -1176,13 +1211,40 @@ describe('Localization', () => {
nestedArray: [ nestedArray: [
{ {
text: 'hola', text: 'hola',
l2: [
{
l3: [
{
l4: [
{
superNestedText: 'hola',
},
],
},
],
},
],
}, },
{ {
text: 'adios', text: 'adios',
l2: [
{
l3: [
{
l4: [
{
superNestedText: 'adios',
}, },
], ],
}, },
], ],
},
],
},
],
},
],
},
title: 'hello', title: 'hello',
myTab: { myTab: {
text: 'hola', text: 'hola',
@@ -1215,10 +1277,10 @@ describe('Localization', () => {
}) })
// check fields // check fields
expect(result.layout[0].text).toStrictEqual(englishText) expect(result.nav.layout[0].text).toStrictEqual(englishText)
expect(allLocales.layout.en[0].text).toStrictEqual(englishText) expect(allLocales.nav.layout.en[0].text).toStrictEqual(englishText)
expect(allLocales.layout.es[0].text).toStrictEqual(spanishText) expect(allLocales.nav.layout.es[0].text).toStrictEqual(spanishText)
expect(allLocales.myTab.group.en.nestedText).toStrictEqual('hello') expect(allLocales.myTab.group.en.nestedText).toStrictEqual('hello')
expect(allLocales.myTab.group.en.nestedArray2[0].nestedText).toStrictEqual('hello') expect(allLocales.myTab.group.en.nestedArray2[0].nestedText).toStrictEqual('hello')

View File

@@ -26,6 +26,7 @@ export interface Config {
tabs: Tab; tabs: Tab;
'localized-sort': LocalizedSort; 'localized-sort': LocalizedSort;
'blocks-same-name': BlocksSameName; 'blocks-same-name': BlocksSameName;
'localized-within-localized': LocalizedWithinLocalized;
'payload-preferences': PayloadPreference; 'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration; 'payload-migrations': PayloadMigration;
}; };
@@ -74,6 +75,14 @@ export interface BlocksField {
blockType: 'textBlock'; blockType: 'textBlock';
}[] }[]
| null; | null;
array?:
| {
link?: {
label?: string | null;
};
id?: string | null;
}[]
| null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'blockInsideBlock'; blockType: 'blockInsideBlock';
@@ -93,6 +102,9 @@ export interface NestedArray {
blocksWithinArray?: blocksWithinArray?:
| { | {
relationWithinBlock?: (string | null) | LocalizedPost; relationWithinBlock?: (string | null) | LocalizedPost;
myGroup?: {
text?: string | null;
};
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'someBlock'; blockType: 'someBlock';
@@ -109,6 +121,7 @@ export interface NestedArray {
| null; | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
_status?: ('draft' | 'published') | null;
} }
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
@@ -195,9 +208,32 @@ export interface ArrayField {
export interface LocalizedRequired { export interface LocalizedRequired {
id: string; id: string;
title: string; title: string;
nav: {
layout: ( layout: (
| { | {
text?: string | null; text?: string | null;
nestedArray?:
| {
text?: string | null;
l2?:
| {
l3?:
| {
l4?:
| {
superNestedText?: string | null;
id?: string | null;
}[]
| null;
id?: string | null;
}[]
| null;
id?: string | null;
}[]
| null;
id?: string | null;
}[]
| null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'text'; blockType: 'text';
@@ -209,6 +245,19 @@ export interface LocalizedRequired {
blockType: 'number'; blockType: 'number';
} }
)[]; )[];
};
myTab?: {
text?: string | null;
group?: {
nestedArray2?:
| {
nestedText?: string | null;
id?: string | null;
}[]
| null;
nestedText?: string | null;
};
};
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@@ -413,6 +462,35 @@ export interface BlocksSameName {
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "localized-within-localized".
*/
export interface LocalizedWithinLocalized {
id: string;
myTab?: {
shouldNotBeLocalized?: string | null;
};
myArray?:
| {
shouldNotBeLocalized?: string | null;
id?: string | null;
}[]
| null;
myBlocks?:
| {
shouldNotBeLocalized?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'myBlock';
}[]
| null;
myGroup?: {
shouldNotBeLocalized?: string | null;
};
updatedAt: string;
createdAt: string;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences". * via the `definition` "payload-preferences".