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:
@@ -40,6 +40,24 @@ export const promise = async <T>({
|
||||
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)) {
|
||||
let fieldData = siblingDoc?.[field.name]
|
||||
const fieldIsLocalized = field.localized && localization
|
||||
@@ -207,20 +225,6 @@ export const promise = async <T>({
|
||||
switch (field.type) {
|
||||
case 'tab':
|
||||
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') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
@@ -239,7 +243,6 @@ export const promise = async <T>({
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc: groupDoc as JsonObject,
|
||||
})
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -122,6 +122,16 @@ export default buildConfigWithDefaults({
|
||||
required: true,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
label: 'Main Nav',
|
||||
fields: [
|
||||
{
|
||||
name: 'nav',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'layout',
|
||||
blocks: [
|
||||
@@ -139,6 +149,28 @@ export default buildConfigWithDefaults({
|
||||
name: '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,
|
||||
type: 'blocks',
|
||||
},
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'myTab',
|
||||
fields: [
|
||||
|
||||
@@ -205,10 +205,10 @@ describe('Localization', () => {
|
||||
await page.goto(urlWithRequiredLocalizedFields.create)
|
||||
await changeLocale(page, defaultLocale)
|
||||
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.fill('#field-layout__0__text', 'test')
|
||||
await expect(page.locator('#field-layout__0__text')).toHaveValue('test')
|
||||
await page.fill('#field-nav__layout__0__text', 'test')
|
||||
await expect(page.locator('#field-nav__layout__0__text')).toHaveValue('test')
|
||||
await saveDocAndAssert(page)
|
||||
const originalID = await page.locator('.id-label').innerText()
|
||||
await openDocControls(page)
|
||||
@@ -256,6 +256,10 @@ describe('Localization', () => {
|
||||
async function fillValues(data: Partial<LocalizedPost>) {
|
||||
const { description: descVal, title: titleVal } = data
|
||||
|
||||
if (titleVal) {await page.locator('#field-title').fill(titleVal)}
|
||||
if (descVal) {await page.locator('#field-description').fill(descVal)}
|
||||
if (titleVal) {
|
||||
await page.locator('#field-title').fill(titleVal)
|
||||
}
|
||||
if (descVal) {
|
||||
await page.locator('#field-description').fill(descVal)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,12 +709,14 @@ describe('Localization', () => {
|
||||
const newDoc = await payload.create({
|
||||
collection: withRequiredLocalizedFields,
|
||||
data: {
|
||||
nav: {
|
||||
layout: [
|
||||
{
|
||||
blockType: 'text',
|
||||
text: 'laiwejfilwaje',
|
||||
},
|
||||
],
|
||||
},
|
||||
title: 'hello',
|
||||
},
|
||||
})
|
||||
@@ -723,12 +725,14 @@ describe('Localization', () => {
|
||||
id: newDoc.id,
|
||||
collection: withRequiredLocalizedFields,
|
||||
data: {
|
||||
nav: {
|
||||
layout: [
|
||||
{
|
||||
blockType: 'number',
|
||||
number: 12,
|
||||
},
|
||||
],
|
||||
},
|
||||
title: 'en espanol, big bird',
|
||||
},
|
||||
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({
|
||||
id: newDoc.id,
|
||||
@@ -750,7 +754,7 @@ describe('Localization', () => {
|
||||
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
|
||||
// 2. make sure localized arrays / blocks work inside of localized groups / tabs
|
||||
// - 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 spanishText = 'spanish'
|
||||
const doc = await payload.create({
|
||||
collection: withRequiredLocalizedFields,
|
||||
data: {
|
||||
nav: {
|
||||
layout: [
|
||||
{
|
||||
blockType: 'text',
|
||||
@@ -1139,13 +1146,40 @@ describe('Localization', () => {
|
||||
nestedArray: [
|
||||
{
|
||||
text: 'hello',
|
||||
l2: [
|
||||
{
|
||||
l3: [
|
||||
{
|
||||
l4: [
|
||||
{
|
||||
superNestedText: 'hello',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'goodbye',
|
||||
l2: [
|
||||
{
|
||||
l3: [
|
||||
{
|
||||
l4: [
|
||||
{
|
||||
superNestedText: 'goodbye',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
myTab: {
|
||||
text: 'hello',
|
||||
group: {
|
||||
@@ -1169,6 +1203,7 @@ describe('Localization', () => {
|
||||
id: doc.id,
|
||||
collection: withRequiredLocalizedFields,
|
||||
data: {
|
||||
nav: {
|
||||
layout: [
|
||||
{
|
||||
blockType: 'text',
|
||||
@@ -1176,13 +1211,40 @@ describe('Localization', () => {
|
||||
nestedArray: [
|
||||
{
|
||||
text: 'hola',
|
||||
l2: [
|
||||
{
|
||||
l3: [
|
||||
{
|
||||
l4: [
|
||||
{
|
||||
superNestedText: 'hola',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'adios',
|
||||
l2: [
|
||||
{
|
||||
l3: [
|
||||
{
|
||||
l4: [
|
||||
{
|
||||
superNestedText: 'adios',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
title: 'hello',
|
||||
myTab: {
|
||||
text: 'hola',
|
||||
@@ -1215,10 +1277,10 @@ describe('Localization', () => {
|
||||
})
|
||||
|
||||
// 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.layout.es[0].text).toStrictEqual(spanishText)
|
||||
expect(allLocales.nav.layout.en[0].text).toStrictEqual(englishText)
|
||||
expect(allLocales.nav.layout.es[0].text).toStrictEqual(spanishText)
|
||||
|
||||
expect(allLocales.myTab.group.en.nestedText).toStrictEqual('hello')
|
||||
expect(allLocales.myTab.group.en.nestedArray2[0].nestedText).toStrictEqual('hello')
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface Config {
|
||||
tabs: Tab;
|
||||
'localized-sort': LocalizedSort;
|
||||
'blocks-same-name': BlocksSameName;
|
||||
'localized-within-localized': LocalizedWithinLocalized;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
@@ -74,6 +75,14 @@ export interface BlocksField {
|
||||
blockType: 'textBlock';
|
||||
}[]
|
||||
| null;
|
||||
array?:
|
||||
| {
|
||||
link?: {
|
||||
label?: string | null;
|
||||
};
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'blockInsideBlock';
|
||||
@@ -93,6 +102,9 @@ export interface NestedArray {
|
||||
blocksWithinArray?:
|
||||
| {
|
||||
relationWithinBlock?: (string | null) | LocalizedPost;
|
||||
myGroup?: {
|
||||
text?: string | null;
|
||||
};
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'someBlock';
|
||||
@@ -109,6 +121,7 @@ export interface NestedArray {
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
@@ -195,9 +208,32 @@ export interface ArrayField {
|
||||
export interface LocalizedRequired {
|
||||
id: string;
|
||||
title: string;
|
||||
nav: {
|
||||
layout: (
|
||||
| {
|
||||
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;
|
||||
blockName?: string | null;
|
||||
blockType: 'text';
|
||||
@@ -209,6 +245,19 @@ export interface LocalizedRequired {
|
||||
blockType: 'number';
|
||||
}
|
||||
)[];
|
||||
};
|
||||
myTab?: {
|
||||
text?: string | null;
|
||||
group?: {
|
||||
nestedArray2?:
|
||||
| {
|
||||
nestedText?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
nestedText?: string | null;
|
||||
};
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -413,6 +462,35 @@ export interface BlocksSameName {
|
||||
updatedAt: 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
|
||||
* via the `definition` "payload-preferences".
|
||||
|
||||
Reference in New Issue
Block a user