Skip to content

Commit 0ce6193

Browse files
authored
fix(ui): tab error badge not counting required array validation errors (#15563)
### What Fixes tab error badges not showing validation errors for required array fields. ### Why When a tab contained a required array field with 0 rows, the error badge wouldn't appear on the tab after save. This happened because `WatchChildErrors` only matched nested field paths (like `items.0.value`) but missed the array field's own validation error at `items`. ### How Updated the path matching logic in `WatchChildErrors` to check both nested paths and the field itself when the segment ends with a dot. Added e2e tests for both named and unnamed tabs with required arrays. Fixes #15561
1 parent 98a756c commit 0ce6193

4 files changed

Lines changed: 141 additions & 4 deletions

File tree

packages/ui/src/forms/WatchChildErrors/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ export const WatchChildErrors: React.FC<TrackSubSchemaErrorCountProps> = ({
3636
const segmentToMatch = [...parentPath, segment].join('.')
3737
// match fields with same parent path
3838
if (segmentToMatch.endsWith('.')) {
39-
return key.startsWith(segmentToMatch)
39+
// Match both nested fields (key starts with segmentToMatch)
40+
// and the field itself (key equals segmentToMatch without trailing dot)
41+
const pathWithoutDot = segmentToMatch.slice(0, -1)
42+
return key.startsWith(segmentToMatch) || key === pathWithoutDot
4043
}
4144
// match fields with same path
4245
return key === segmentToMatch

test/field-error-states/collections/ErrorFields/index.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,41 @@ export const ErrorFieldsCollection: CollectionConfig = {
190190
fields: errorFields,
191191
label: 'Hero',
192192
},
193+
{
194+
name: 'tabWithRequiredArray',
195+
label: 'Tab with Required Array',
196+
fields: [
197+
{
198+
name: 'requiredArray',
199+
type: 'array',
200+
required: true,
201+
fields: [
202+
{
203+
name: 'arrayText',
204+
type: 'text',
205+
required: true,
206+
},
207+
],
208+
},
209+
],
210+
},
211+
{
212+
label: 'Unnamed Tab with Required Array',
213+
fields: [
214+
{
215+
name: 'unnamedRequiredArray',
216+
type: 'array',
217+
required: true,
218+
fields: [
219+
{
220+
name: 'arrayText',
221+
type: 'text',
222+
required: true,
223+
},
224+
],
225+
},
226+
],
227+
},
193228
],
194229
},
195230
{

test/field-error-states/e2e.spec.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ describe('Field Error States', () => {
206206
})
207207

208208
describe('error field types', () => {
209-
async function prefillBaseRequiredFields(page: Page) {
209+
async function prefillHomeAndHeroTabs(page: Page) {
210210
const homeTabLocator = page.locator('.tabs-field__tab-button', {
211211
hasText: 'Home',
212212
})
@@ -225,6 +225,27 @@ describe('Field Error States', () => {
225225
await page.locator('#field-tabText').fill('Hero Tab Text')
226226
await page.locator('#field-text').fill('Hero Tab Collapsible Text')
227227
}
228+
229+
async function prefillBaseRequiredFields(page: Page) {
230+
await prefillHomeAndHeroTabs(page)
231+
232+
// fill out new tabs with required arrays
233+
const tabWithRequiredArrayButton = page.getByRole('button', {
234+
name: 'Tab with Required Array',
235+
exact: true,
236+
})
237+
await tabWithRequiredArrayButton.click()
238+
await addArrayRow(page, { fieldName: 'tabWithRequiredArray__requiredArray' })
239+
await page.locator('#field-tabWithRequiredArray__requiredArray__0__arrayText').fill('Test')
240+
241+
const unnamedTabButton = page.getByRole('button', {
242+
name: 'Unnamed Tab with Required Array',
243+
exact: true,
244+
})
245+
await unnamedTabButton.click()
246+
await addArrayRow(page, { fieldName: 'unnamedRequiredArray' })
247+
await page.locator('#field-unnamedRequiredArray__0__arrayText').fill('Test')
248+
}
228249
test('group errors', async ({ page }) => {
229250
await page.goto(errorFieldsURL.create)
230251
await waitForFormReady(page)
@@ -246,5 +267,56 @@ describe('Field Error States', () => {
246267
await expect(page.locator('#field-group .group-field__header .error-pill')).toBeHidden()
247268
await saveDocAndAssert(page, '#action-save')
248269
})
270+
271+
test('tab error badge with required array field', async ({ page }) => {
272+
await page.goto(errorFieldsURL.create)
273+
await waitForFormReady(page)
274+
await prefillHomeAndHeroTabs(page)
275+
276+
const tabWithRequiredArrayButton = page.getByRole('button', {
277+
name: 'Tab with Required Array',
278+
exact: true,
279+
})
280+
await tabWithRequiredArrayButton.click()
281+
282+
await saveDocAndAssert(page, '#action-save', 'error')
283+
284+
// should show the error badge on the tab
285+
const tabErrorBadge = page.locator('.tabs-field__tab-button--active .error-pill')
286+
await expect(tabErrorBadge).toBeVisible({ timeout: 10000 })
287+
await expect(tabErrorBadge).toContainText('1')
288+
289+
// fill out the required array
290+
await addArrayRow(page, { fieldName: 'tabWithRequiredArray__requiredArray' })
291+
await page.locator('#field-tabWithRequiredArray__requiredArray__0__arrayText').fill('Test')
292+
293+
// error badge should disappear
294+
await expect(tabErrorBadge).toBeHidden()
295+
})
296+
297+
test('tab error badge with unnamed tab and required array field', async ({ page }) => {
298+
await page.goto(errorFieldsURL.create)
299+
await waitForFormReady(page)
300+
await prefillHomeAndHeroTabs(page)
301+
302+
const unnamedTabButton = page.locator('.tabs-field__tab-button', {
303+
hasText: 'Unnamed Tab with Required Array',
304+
})
305+
await unnamedTabButton.click()
306+
307+
await saveDocAndAssert(page, '#action-save', 'error')
308+
309+
// should show the error badge on the tab
310+
const tabErrorBadge = page.locator('.tabs-field__tab-button--active .error-pill')
311+
await expect(tabErrorBadge).toBeVisible({ timeout: 10000 })
312+
await expect(tabErrorBadge).toContainText('1')
313+
314+
// fill out the required array
315+
await addArrayRow(page, { fieldName: 'unnamedRequiredArray' })
316+
await page.locator('#field-unnamedRequiredArray__0__arrayText').fill('Test')
317+
318+
// error badge should disappear
319+
await expect(tabErrorBadge).toBeHidden()
320+
})
249321
})
250322
})

test/field-error-states/payload-types.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ export interface Config {
106106
'global-validate-drafts-on': GlobalValidateDraftsOnSelect<false> | GlobalValidateDraftsOnSelect<true>;
107107
};
108108
locale: null;
109-
user: User;
109+
user: User & {
110+
collection: 'users';
111+
};
110112
jobs: {
111113
tasks: unknown;
112114
workflows: unknown;
@@ -251,6 +253,16 @@ export interface ErrorField {
251253
id?: string | null;
252254
}[]
253255
| null;
256+
tabWithRequiredArray: {
257+
requiredArray: {
258+
arrayText: string;
259+
id?: string | null;
260+
}[];
261+
};
262+
unnamedRequiredArray: {
263+
arrayText: string;
264+
id?: string | null;
265+
}[];
254266
layout?:
255267
| {
256268
tabText: string;
@@ -339,7 +351,6 @@ export interface User {
339351
}[]
340352
| null;
341353
password?: string | null;
342-
collection: 'users';
343354
}
344355
/**
345356
* This interface was referenced by `Config`'s JSON-Schema
@@ -628,6 +639,22 @@ export interface ErrorFieldsSelect<T extends boolean = true> {
628639
textarea?: T;
629640
id?: T;
630641
};
642+
tabWithRequiredArray?:
643+
| T
644+
| {
645+
requiredArray?:
646+
| T
647+
| {
648+
arrayText?: T;
649+
id?: T;
650+
};
651+
};
652+
unnamedRequiredArray?:
653+
| T
654+
| {
655+
arrayText?: T;
656+
id?: T;
657+
};
631658
layout?:
632659
| T
633660
| {

0 commit comments

Comments
 (0)