Compare commits
9 Commits
fix/ui-imp
...
feat/forma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2cd6bbaec | ||
|
|
b2657ed12b | ||
|
|
0851ef99da | ||
|
|
3c483ab57a | ||
|
|
9bdbbb674f | ||
|
|
eb191f335b | ||
|
|
02bb176890 | ||
|
|
082c6ef44a | ||
|
|
f93cdf9364 |
@@ -6,6 +6,7 @@ import type { Field, FieldHookArgs, TabAsField, ValidateOptions } from '../../co
|
||||
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { deepMergeWithSourceArrays } from '../../../utilities/deepMerge.js'
|
||||
import { formatErrorLabels } from '../../../utilities/formatLabels.js'
|
||||
import { fieldAffectsData, tabHasName } from '../../config/types.js'
|
||||
import { getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { beforeDuplicate } from './beforeDuplicate.js'
|
||||
@@ -146,7 +147,7 @@ export const promise = async ({
|
||||
|
||||
if (typeof validationResult === 'string') {
|
||||
errors.push({
|
||||
field: fieldPath.join('.'),
|
||||
field: formatErrorLabels(fieldPath),
|
||||
message: validationResult,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -981,7 +981,7 @@ export {
|
||||
deepMergeWithSourceArrays,
|
||||
} from './utilities/deepMerge.js'
|
||||
export { default as flattenTopLevelFields } from './utilities/flattenTopLevelFields.js'
|
||||
export { formatLabels, formatNames, toWords } from './utilities/formatLabels.js'
|
||||
export { formatErrorLabels, formatLabels, formatNames, toWords } from './utilities/formatLabels.js'
|
||||
export { getCollectionIDFieldTypes } from './utilities/getCollectionIDFieldTypes.js'
|
||||
export { getObjectDotNotation } from './utilities/getObjectDotNotation.js'
|
||||
export { initTransaction } from './utilities/initTransaction.js'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { formatLabels, toWords } from './formatLabels'
|
||||
import { formatErrorLabels, formatLabels, toWords } from './formatLabels'
|
||||
|
||||
describe('formatLabels', () => {
|
||||
it('should format singular slug', () => {
|
||||
@@ -39,3 +39,11 @@ describe('formatLabels', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatErrorLabels', () => {
|
||||
it('should format array', () => {
|
||||
expect(formatErrorLabels(['test', 1, 'field', 'array', 3, 'final'])).toBe(
|
||||
'Test (1) > Field > Array (3) > Final',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,8 @@ const toWords = (inputString: string, joinWords = false): string => {
|
||||
const trimmedString = notNullString.trim()
|
||||
const arrayOfStrings = trimmedString.split(/[\s-]/)
|
||||
|
||||
const splitStringsArray = []
|
||||
const splitStringsArray: string[] = []
|
||||
|
||||
arrayOfStrings.forEach((tempString) => {
|
||||
if (tempString !== '') {
|
||||
const splitWords = tempString.split(/(?=[A-Z])/).join(' ')
|
||||
@@ -46,4 +47,23 @@ const formatNames = (slug: string): { plural: string; singular: string } => {
|
||||
}
|
||||
}
|
||||
|
||||
export { formatLabels, formatNames, toWords }
|
||||
/**
|
||||
* Formats labels for error field schema array.
|
||||
* @param fieldSchema - Array of strings and numbers.
|
||||
* @returns Formatted string.
|
||||
*
|
||||
* @example
|
||||
* formatErrorLabels(['test', 1, 'test2']) // => 'Test (1) > Test2'
|
||||
*/
|
||||
const formatErrorLabels = (fieldSchema: (number | string)[]): string => {
|
||||
return fieldSchema.reduce((acc: string, current, index) => {
|
||||
if (typeof current === 'number') {
|
||||
return index === 0 ? `(${current})` : `${acc} (${current})`
|
||||
} else {
|
||||
const formattedLabel = current.charAt(0).toUpperCase() + current.slice(1)
|
||||
return index === 0 ? `${acc}${formattedLabel}` : `${acc} > ${formattedLabel}`
|
||||
}
|
||||
}, '') as string
|
||||
}
|
||||
|
||||
export { formatErrorLabels, formatLabels, formatNames, toWords }
|
||||
|
||||
@@ -253,7 +253,9 @@ describe('access control', () => {
|
||||
await expect(page.locator('#field-name')).toHaveValue('name')
|
||||
await expect(page.locator('#action-save')).toBeVisible()
|
||||
await page.locator('#action-save').click()
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully', {
|
||||
ignoreCase: true,
|
||||
})
|
||||
await expect(page.locator('#action-save')).toBeHidden()
|
||||
await expect(page.locator('#field-name')).toBeDisabled()
|
||||
})
|
||||
@@ -280,7 +282,9 @@ describe('access control', () => {
|
||||
await documentDrawer.locator('#field-name').fill('name')
|
||||
await expect(documentDrawer.locator('#field-name')).toHaveValue('name')
|
||||
await documentDrawer.locator('#action-save').click()
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully', {
|
||||
ignoreCase: true,
|
||||
})
|
||||
await expect(documentDrawer.locator('#action-save')).toBeHidden()
|
||||
await expect(documentDrawer.locator('#field-name')).toBeDisabled()
|
||||
})
|
||||
@@ -304,7 +308,9 @@ describe('access control', () => {
|
||||
await expect(page.locator('#field-name')).toBeVisible()
|
||||
await page.locator('#field-name').fill('anonymous@email.com')
|
||||
await page.locator('#action-save').click()
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully', {
|
||||
ignoreCase: true,
|
||||
})
|
||||
await expect(page.locator('#field-name')).toBeDisabled()
|
||||
await expect(page.locator('#action-save')).toBeHidden()
|
||||
|
||||
@@ -312,7 +318,9 @@ describe('access control', () => {
|
||||
await expect(page.locator('#field-name')).toBeVisible()
|
||||
await page.locator('#field-name').fill(devUser.email)
|
||||
await page.locator('#action-save').click()
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully', {
|
||||
ignoreCase: true,
|
||||
})
|
||||
await expect(page.locator('#field-name')).toBeEnabled()
|
||||
await expect(page.locator('#action-save')).toBeVisible()
|
||||
})
|
||||
@@ -335,7 +343,9 @@ describe('access control', () => {
|
||||
await expect(documentDrawer).toBeVisible()
|
||||
await documentDrawer.locator('#field-name').fill('anonymous@email.com')
|
||||
await documentDrawer.locator('#action-save').click()
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully', {
|
||||
ignoreCase: true,
|
||||
})
|
||||
await expect(documentDrawer.locator('#field-name')).toBeDisabled()
|
||||
await documentDrawer.locator('button.doc-drawer__header-close').click()
|
||||
await expect(documentDrawer).toBeHidden()
|
||||
@@ -344,7 +354,9 @@ describe('access control', () => {
|
||||
await expect(documentDrawer2).toBeVisible()
|
||||
await documentDrawer2.locator('#field-name').fill('dev@payloadcms.com')
|
||||
await documentDrawer2.locator('#action-save').click()
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully', {
|
||||
ignoreCase: true,
|
||||
})
|
||||
await expect(documentDrawer2.locator('#field-name')).toBeEnabled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -636,8 +636,8 @@ describe('collections-graphql', () => {
|
||||
// creating twice as many records as we are querying to get a random sample
|
||||
await mapAsync([...Array(10)], () => {
|
||||
// setTimeout used to randomize the creation timestamp
|
||||
setTimeout(async () => {
|
||||
await payload.create({
|
||||
setTimeout(() => {
|
||||
void payload.create({
|
||||
collection: pointSlug,
|
||||
data: {
|
||||
// only randomize longitude to make distance comparison easy
|
||||
@@ -1150,7 +1150,7 @@ describe('collections-graphql', () => {
|
||||
})
|
||||
.then((res) => res.json())
|
||||
expect(Array.isArray(errors)).toBe(true)
|
||||
expect(errors[0].message).toEqual('The following field is invalid: min')
|
||||
expect(errors[0].message).toEqual('The following field is invalid: Min')
|
||||
expect(typeof errors[0].locations).toBeDefined()
|
||||
})
|
||||
|
||||
@@ -1198,13 +1198,13 @@ describe('collections-graphql', () => {
|
||||
expect(errors[1].extensions.data.errors[0].field).toEqual('email')
|
||||
|
||||
expect(Array.isArray(errors[2].locations)).toEqual(true)
|
||||
expect(errors[2].message).toEqual('The following field is invalid: email')
|
||||
expect(errors[2].message).toEqual('The following field is invalid: Email')
|
||||
expect(errors[2].path[0]).toEqual('test4')
|
||||
expect(errors[2].extensions.name).toEqual('ValidationError')
|
||||
expect(errors[2].extensions.data.errors[0].message).toEqual(
|
||||
'Please enter a valid email address.',
|
||||
)
|
||||
expect(errors[2].extensions.data.errors[0].field).toEqual('email')
|
||||
expect(errors[2].extensions.data.errors[0].field).toEqual('Email')
|
||||
})
|
||||
|
||||
it('should return the minimum allowed information about internal errors', async () => {
|
||||
|
||||
@@ -259,7 +259,10 @@ describe('fields - relationship', () => {
|
||||
await expect(field).toContainText(anotherRelationOneDoc.id)
|
||||
await wait(2000) // Need to wait form state to come back before clicking save
|
||||
await page.locator('#action-save').click()
|
||||
await expect(page.locator('.payload-toast-container')).toContainText(`is invalid: ${fieldName}`)
|
||||
await expect(page.locator('.payload-toast-container')).toContainText(
|
||||
`is invalid: ${fieldName}`,
|
||||
{ ignoreCase: true },
|
||||
)
|
||||
filteredField = page.locator(`#field-${fieldName} .react-select`)
|
||||
await filteredField.click({ delay: 100 })
|
||||
filteredOptions = filteredField.locator('.rs__option')
|
||||
@@ -406,7 +409,9 @@ describe('fields - relationship', () => {
|
||||
await drawerField.fill('Newly created document')
|
||||
const saveButton = documentDrawer.locator('#action-save')
|
||||
await saveButton.click()
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
|
||||
await expect(page.locator('.payload-toast-container')).toContainText('successfully', {
|
||||
ignoreCase: true,
|
||||
})
|
||||
await expect(
|
||||
page.locator('#field-relationshipHasMany .value-container .rs__multi-value'),
|
||||
).toHaveCount(1)
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('Array', () => {
|
||||
|
||||
await page.click('#action-save', { delay: 100 })
|
||||
await expect(page.locator('.payload-toast-container')).toContainText(
|
||||
'The following field is invalid: arrayWithMinRows',
|
||||
'The following field is invalid: ArrayWithMinRows',
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ describe('Block fields', () => {
|
||||
|
||||
await page.click('#action-save', { delay: 100 })
|
||||
await expect(page.locator('.payload-toast-container')).toContainText(
|
||||
'The following field is invalid: blocksWithMinRows',
|
||||
'The following field is invalid: BlocksWithMinRows',
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ describe('Number', () => {
|
||||
await page.click('#action-save', { delay: 100 })
|
||||
|
||||
await expect(page.locator('.payload-toast-container')).toContainText(
|
||||
'The following field is invalid: withMinRows',
|
||||
'The following field is invalid: WithMinRows',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -374,7 +374,7 @@ describe('relationship', () => {
|
||||
|
||||
await page.click('#action-save', { delay: 100 })
|
||||
await expect(page.locator('.payload-toast-container')).toContainText(
|
||||
'The following field is invalid: relationshipWithMinRows',
|
||||
'The following field is invalid: RelationshipWithMinRows',
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ describe('fields', () => {
|
||||
|
||||
// toast error
|
||||
await expect(page.locator('.payload-toast-container')).toContainText(
|
||||
'The following field is invalid: group.unique',
|
||||
'The following field is invalid: Group.unique',
|
||||
)
|
||||
|
||||
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('create')
|
||||
|
||||
@@ -405,7 +405,7 @@ describe('Fields', () => {
|
||||
min: 5,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: min')
|
||||
).rejects.toThrow('The following field is invalid: Min')
|
||||
})
|
||||
it('should not create number above max', async () => {
|
||||
await expect(async () =>
|
||||
@@ -415,7 +415,7 @@ describe('Fields', () => {
|
||||
max: 15,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: max')
|
||||
).rejects.toThrow('The following field is invalid: Max')
|
||||
})
|
||||
|
||||
it('should not create number below 0', async () => {
|
||||
@@ -426,7 +426,7 @@ describe('Fields', () => {
|
||||
positiveNumber: -5,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: positiveNumber')
|
||||
).rejects.toThrow('The following field is invalid: PositiveNumber')
|
||||
})
|
||||
|
||||
it('should not create number above 0', async () => {
|
||||
@@ -437,7 +437,7 @@ describe('Fields', () => {
|
||||
negativeNumber: 5,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: negativeNumber')
|
||||
).rejects.toThrow('The following field is invalid: NegativeNumber')
|
||||
})
|
||||
it('should not create a decimal number below min', async () => {
|
||||
await expect(async () =>
|
||||
@@ -447,7 +447,7 @@ describe('Fields', () => {
|
||||
decimalMin: -0.25,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: decimalMin')
|
||||
).rejects.toThrow('The following field is invalid: DecimalMin')
|
||||
})
|
||||
|
||||
it('should not create a decimal number above max', async () => {
|
||||
@@ -458,7 +458,7 @@ describe('Fields', () => {
|
||||
decimalMax: 1.5,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: decimalMax')
|
||||
).rejects.toThrow('The following field is invalid: DecimalMax')
|
||||
})
|
||||
it('should localize an array of numbers using hasMany', async () => {
|
||||
const localizedHasMany = [5, 10]
|
||||
@@ -665,7 +665,7 @@ describe('Fields', () => {
|
||||
min: 5,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: min')
|
||||
).rejects.toThrow('The following field is invalid: Min')
|
||||
|
||||
expect(doc.point).toEqual(point)
|
||||
expect(doc.localized).toEqual(localized)
|
||||
@@ -1292,7 +1292,7 @@ describe('Fields', () => {
|
||||
json: '{ bad input: true }',
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: json')
|
||||
).rejects.toThrow('The following field is invalid: Json')
|
||||
})
|
||||
|
||||
it('should validate json schema', async () => {
|
||||
@@ -1303,7 +1303,7 @@ describe('Fields', () => {
|
||||
json: { foo: 'bad' },
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: json')
|
||||
).rejects.toThrow('The following field is invalid: Json')
|
||||
})
|
||||
|
||||
it('should save empty json objects', async () => {
|
||||
|
||||
@@ -449,7 +449,7 @@ describe('Relationships', () => {
|
||||
// @ts-expect-error Sending bad data to test error handling
|
||||
customIdRelation: 1234,
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: customIdRelation')
|
||||
).rejects.toThrow('The following field is invalid: CustomIdRelation')
|
||||
})
|
||||
|
||||
it('should validate the format of number id relationships', async () => {
|
||||
@@ -458,7 +458,7 @@ describe('Relationships', () => {
|
||||
// @ts-expect-error Sending bad data to test error handling
|
||||
customIdNumberRelation: 'bad-input',
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: customIdNumberRelation')
|
||||
).rejects.toThrow('The following field is invalid: CustomIdNumberRelation')
|
||||
})
|
||||
|
||||
it('should allow update removing a relationship', async () => {
|
||||
|
||||
@@ -297,6 +297,7 @@ describe('uploads', () => {
|
||||
await page.locator('button#action-save').click()
|
||||
await expect(page.locator('.payload-toast-container .toast-error')).toContainText(
|
||||
'The following field is invalid: audio',
|
||||
{ ignoreCase: true },
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -511,7 +511,7 @@ describe('Versions', () => {
|
||||
|
||||
expect(updateManyResult.docs).toHaveLength(0)
|
||||
expect(updateManyResult.errors).toStrictEqual([
|
||||
{ id: doc.id, message: 'The following field is invalid: title' },
|
||||
{ id: doc.id, message: 'The following field is invalid: Title' },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user