Skip to content

Commit fac59c8

Browse files
authored
fix: text field validation rejecting localized object values (#15932)
## Summary Fixes #15828 When using `locale: 'all'` with a required localized text field, the `text` validator incorrectly rejects the value. This happens because: 1. With `locale: 'all'`, the value passed to the validator is an object like `{en: "Hello", es: "World"}` (locale unflattening via `mergeLocaleActions` happens *after* validation in `promise.ts`) 2. The old condition `!(typeof value === 'string' || Array.isArray(value))` evaluates to `true` for objects, triggering the required error 3. The `textarea` validator already handles this correctly by using `!value` (objects are truthy) ### Fix Changed the text validator's required check from: ```ts if (!(typeof value === 'string' || Array.isArray(value)) || value?.length === 0) ``` to: ```ts if (!value || ((typeof value === 'string' || Array.isArray(value)) && value.length === 0)) ``` This aligns with the textarea validator's approach: - `!value` catches `null`, `undefined`, and empty string `""` - The second clause catches empty arrays `[]` (truthy but length 0) - Localized objects like `{en: "Hello"}` pass because `!obj` is `false` ### Changes - **`packages/payload/src/fields/validations.ts`** — 1-line fix to the required check condition - **`packages/payload/src/fields/validations.spec.ts`** — 3 unit tests (localized object accepted, null rejected, empty string rejected) - **`test/fields/collections/Text/index.ts`** — Added `localizedRequiredText` test field - **`test/fields/int.spec.ts`** — Integration test verifying `locale: 'all'` with required localized text ## Test plan - [x] Unit tests: 88/88 passed (85 existing + 3 new) - [x] Integration tests (SQLite): 154/154 passed - [ ] CI passes on all database adapters
1 parent d2a0740 commit fac59c8

File tree

4 files changed

+49
-1
lines changed

4 files changed

+49
-1
lines changed

packages/payload/src/fields/validations.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ describe('Field Validations', () => {
8787
const result = text(val, { ...options, hasMany: true, required: true })
8888
expect(result).toBe(true)
8989
})
90+
it('should accept localized object value when required', () => {
91+
const val = { en: 'English text', es: 'Spanish text' }
92+
const result = text(val as any, { ...options, required: true })
93+
expect(result).toBe(true)
94+
})
95+
it('should reject null when required', () => {
96+
const result = text(null as any, { ...options, required: true })
97+
expect(result).toBe('validation:required')
98+
})
99+
it('should reject empty string when required', () => {
100+
const result = text('', { ...options, required: true })
101+
expect(result).toBe('validation:required')
102+
})
90103
})
91104

92105
describe('textarea', () => {

packages/payload/src/fields/validations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export const text: TextFieldValidation = (
9494
}
9595

9696
if (required) {
97-
if (!(typeof value === 'string' || Array.isArray(value)) || value?.length === 0) {
97+
if (!value || ((typeof value === 'string' || Array.isArray(value)) && value.length === 0)) {
9898
return t('validation:required')
9999
}
100100
}

test/fields/collections/Text/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ const TextFields: CollectionConfig = {
5252
type: 'text',
5353
localized: true,
5454
},
55+
{
56+
name: 'localizedRequiredText',
57+
type: 'text',
58+
localized: true,
59+
required: true,
60+
defaultValue: 'default',
61+
},
5562
{
5663
name: 'i18nText',
5764
type: 'text',

test/fields/int.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,34 @@ describe('Fields', () => {
136136
expect(localizedDoc.localizedHasMany.en).toEqual(localizedHasMany)
137137
})
138138

139+
it('should validate localized required text field with locale all', async () => {
140+
const doc = await payload.create({
141+
collection: 'text-fields',
142+
data: {
143+
text: 'required',
144+
// @ts-expect-error locale 'all' accepts object values for localized fields
145+
localizedRequiredText: {
146+
en: 'English text',
147+
es: 'Spanish text',
148+
},
149+
},
150+
locale: 'all',
151+
})
152+
153+
const allLocales = await payload.findByID({
154+
id: doc.id,
155+
collection: 'text-fields',
156+
locale: 'all',
157+
})
158+
159+
// @ts-expect-error
160+
expect(allLocales.localizedRequiredText.en).toEqual('English text')
161+
// @ts-expect-error
162+
expect(allLocales.localizedRequiredText.es).toEqual('Spanish text')
163+
164+
await payload.delete({ collection: 'text-fields', id: doc.id })
165+
})
166+
139167
it('should query hasMany in', async () => {
140168
const hit = await payload.create({
141169
collection: 'text-fields',

0 commit comments

Comments
 (0)