Skip to content

Commit 70ecfa3

Browse files
committed
fix(ui): preserve URL where filter on bulk publish/unpublish/edit
The List view did not forward useListQuery()'s `where` to <ListSelection>, so Select-all-N + bulk Publish/Unpublish/Edit dropped the filter and hit every doc matching the action's base constraint. Pass query?.where into <ListSelection>. Downstream merging via combineWhereConstraints already handles it. Adds E2E coverage. Refs #16325
1 parent 419a8e3 commit 70ecfa3

2 files changed

Lines changed: 142 additions & 1 deletion

File tree

packages/ui/src/views/List/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export function DefaultListView(props: ListViewClientProps) {
8383
} = useConfig()
8484
const router = useRouter()
8585

86-
const { data, isGroupingBy } = useListQuery()
86+
const { data, isGroupingBy, query } = useListQuery()
8787

8888
const { openModal } = useModal()
8989
const { drawerSlug: bulkUploadDrawerSlug, setCollectionSlug, setOnSuccess } = useBulkUpload()
@@ -327,6 +327,7 @@ export function DefaultListView(props: ListViewClientProps) {
327327
disableBulkEdit={disableBulkEdit}
328328
label={collectionLabel}
329329
showSelectAllAcrossPages={!isGroupingBy}
330+
where={query?.where}
330331
/>
331332
<div className={`${baseClass}__list-selection-actions`}>
332333
{enableRowSelections && typeof onBulkSelect === 'function'

test/bulk-edit/e2e.spec.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,146 @@ test.describe('Bulk Edit', () => {
415415
await expect(page.locator('.row-1 .cell-title')).toContainText(updatedTitle)
416416
})
417417

418+
test.describe('preserves URL where filter on bulk actions across pages', () => {
419+
// Regression coverage for the URL `where` filter being dropped on
420+
// "Select all N across pages" + bulk Publish / Unpublish / Edit.
421+
// Existing "filters and across pages" tests use the search bar (?search=),
422+
// which propagates via a separate code path. The structured ?where[...]
423+
// param was silently dropped between the List view and the bulk drawers.
424+
const filteredTitlePrefix = 'ATM'
425+
const otherTitlePrefix = 'Other'
426+
const seedCount = 6 // exceeds defaultLimit of 5, forces the across-pages button
427+
428+
async function seedFilteredAndOtherPosts({
429+
filteredDraft = false,
430+
otherDraft = false,
431+
}: { filteredDraft?: boolean; otherDraft?: boolean } = {}) {
432+
await deleteAllPosts()
433+
for (let i = 1; i <= seedCount; i++) {
434+
await createPost({ title: `${filteredTitlePrefix} page ${i}` }, { draft: filteredDraft })
435+
await wait(50)
436+
}
437+
for (let i = 1; i <= seedCount; i++) {
438+
await createPost({ title: `${otherTitlePrefix} page ${i}` }, { draft: otherDraft })
439+
await wait(50)
440+
}
441+
}
442+
443+
const filteredListUrl = () =>
444+
`${postsUrl.list}?where[and][0][title][like]=${encodeURIComponent(filteredTitlePrefix)}`
445+
446+
test('should preserve URL where filter on bulk unpublish across pages', async () => {
447+
await seedFilteredAndOtherPosts()
448+
449+
await page.goto(filteredListUrl())
450+
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('where')
451+
await expect(page.locator('.table table > tbody > tr')).toHaveCount(5)
452+
453+
await page.locator('input#select-all').check()
454+
await page.locator('button#select-all-across-pages').click()
455+
456+
await page.locator('.list-selection__button[aria-label="Unpublish"]').click()
457+
await page.locator('#unpublish-posts #confirm-action').click()
458+
459+
await expect(page.locator('.payload-toast-container .toast-success')).toContainText(
460+
`Updated ${seedCount} Posts successfully.`,
461+
)
462+
463+
const stillPublishedOthers = await payload.find({
464+
collection: postsSlug,
465+
where: {
466+
and: [{ title: { like: otherTitlePrefix } }, { _status: { equals: 'published' } }],
467+
},
468+
})
469+
expect(stillPublishedOthers.totalDocs).toBe(seedCount)
470+
471+
const draftedFiltered = await payload.find({
472+
collection: postsSlug,
473+
where: {
474+
and: [{ title: { like: filteredTitlePrefix } }, { _status: { equals: 'draft' } }],
475+
},
476+
})
477+
expect(draftedFiltered.totalDocs).toBe(seedCount)
478+
})
479+
480+
test('should preserve URL where filter on bulk publish across pages', async () => {
481+
await seedFilteredAndOtherPosts({ filteredDraft: true, otherDraft: true })
482+
483+
await page.goto(filteredListUrl())
484+
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('where')
485+
await expect(page.locator('.table table > tbody > tr')).toHaveCount(5)
486+
487+
await page.locator('input#select-all').check()
488+
await page.locator('button#select-all-across-pages').click()
489+
490+
await page.locator('.list-selection__button[aria-label="Publish"]').click()
491+
await page.locator('#publish-posts #confirm-action').click()
492+
493+
await expect(page.locator('.payload-toast-container .toast-success')).toContainText(
494+
`Updated ${seedCount} Posts successfully.`,
495+
)
496+
497+
const stillDraftOthers = await payload.find({
498+
collection: postsSlug,
499+
where: {
500+
and: [{ title: { like: otherTitlePrefix } }, { _status: { equals: 'draft' } }],
501+
},
502+
})
503+
expect(stillDraftOthers.totalDocs).toBe(seedCount)
504+
505+
const publishedFiltered = await payload.find({
506+
collection: postsSlug,
507+
where: {
508+
and: [{ title: { like: filteredTitlePrefix } }, { _status: { equals: 'published' } }],
509+
},
510+
})
511+
expect(publishedFiltered.totalDocs).toBe(seedCount)
512+
})
513+
514+
test('should preserve URL where filter on bulk edit across pages', async () => {
515+
await seedFilteredAndOtherPosts()
516+
const editedDescription = 'Edited via filtered bulk edit'
517+
518+
await page.goto(filteredListUrl())
519+
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('where')
520+
await expect(page.locator('.table table > tbody > tr')).toHaveCount(5)
521+
522+
await page.locator('input#select-all').check()
523+
await page.locator('button#select-all-across-pages').click()
524+
525+
const { field, modal } = await selectFieldToEdit(page, {
526+
fieldID: 'description',
527+
fieldLabel: 'Description',
528+
})
529+
await field.fill(editedDescription)
530+
await modal.locator('.form-submit button[type="submit"].edit-many__publish').click()
531+
532+
await expect(page.locator('.payload-toast-container .toast-success')).toContainText(
533+
`Updated ${seedCount} Posts successfully.`,
534+
)
535+
536+
const editedFiltered = await payload.find({
537+
collection: postsSlug,
538+
where: { description: { equals: editedDescription } },
539+
})
540+
expect(editedFiltered.totalDocs).toBe(seedCount)
541+
expect(editedFiltered.docs.every((doc) => doc.title?.startsWith(filteredTitlePrefix))).toBe(
542+
true,
543+
)
544+
545+
const untouchedOthers = await payload.find({
546+
collection: postsSlug,
547+
where: {
548+
and: [
549+
{ title: { like: otherTitlePrefix } },
550+
{ description: { not_equals: editedDescription } },
551+
],
552+
},
553+
})
554+
expect(untouchedOthers.totalDocs).toBe(seedCount)
555+
})
556+
})
557+
418558
test('should not override un-edited values if it has a defaultValue', async () => {
419559
await deleteAllPosts()
420560

0 commit comments

Comments
 (0)