From bacfc6186c9b6a0b91799c73956073457bbfe9c8 Mon Sep 17 00:00:00 2001 From: Luan Taraschi <130802253+luantaraschi@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:37:04 -0300 Subject: [PATCH] fix(ui): forward list filter to bulk actions when selecting all across pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ListSelection accepts a `where` prop and forwards it to EditMany, PublishMany and UnpublishMany, but the two list-view call sites never passed it. Selecting all documents across pages under an active URL filter therefore applied bulk actions to every document matching only the action's base constraint, ignoring the filter. GroupByHeader already passes `where`, and DeleteMany is unaffected because it derives baseWhere from the search params inside ListSelection itself — which is why only Edit/Publish/Unpublish showed the bug. Fixes #17670 --- .../ui/src/views/List/ListHeader/index.tsx | 3 +- packages/ui/src/views/List/index.client.tsx | 1 + test/bulk-edit/e2e.spec.ts | 60 +++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/views/List/ListHeader/index.tsx b/packages/ui/src/views/List/ListHeader/index.tsx index a20cb2a141c..01243590092 100644 --- a/packages/ui/src/views/List/ListHeader/index.tsx +++ b/packages/ui/src/views/List/ListHeader/index.tsx @@ -60,7 +60,7 @@ export const CollectionListHeader: React.FC = ({ const { config, getEntityConfig } = useConfig() const { drawerSlug, isInDrawer, selectedOption } = useListDrawerContext() const isTrashRoute = viewType === 'trash' - const { isGroupingBy } = useListQuery() + const { isGroupingBy, query } = useListQuery() if (isInDrawer) { return ( @@ -104,6 +104,7 @@ export const CollectionListHeader: React.FC = ({ label={getTranslation(collectionConfig?.labels?.plural, i18n)} showSelectAllAcrossPages={!isGroupingBy} viewType={viewType} + where={query?.where} /> ),
{enableRowSelections && typeof onBulkSelect === 'function' diff --git a/test/bulk-edit/e2e.spec.ts b/test/bulk-edit/e2e.spec.ts index 86c96f9c0cf..48834cd6488 100644 --- a/test/bulk-edit/e2e.spec.ts +++ b/test/bulk-edit/e2e.spec.ts @@ -194,6 +194,66 @@ test.describe('Bulk Edit', () => { ) }) + test('should respect the list filter when unpublishing all across pages', async () => { + await deleteAllPosts() + + const matchingDescription = 'unpublish-me' + const otherDescription = 'leave-me-alone' + + for (let i = 1; i <= 3; i++) { + await createPost({ title: `Matching post ${i}`, description: matchingDescription }) + await wait(50) + } + + for (let i = 1; i <= 3; i++) { + await createPost({ title: `Other post ${i}`, description: otherDescription }) + await wait(50) + } + + await page.goto(postsUrl.list) + // Wait until page has limit in the url, to ensure it is fully loaded + await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain('limit=') + + await addListFilter({ + page, + fieldLabel: 'Description', + operatorLabel: 'equals', + value: matchingDescription, + }) + + await page.locator('input#select-all').check() + await page.locator('button#select-all-across-pages').click() + + await page.locator('.list-selection__button[aria-label="Unpublish"]').click() + await page.locator('#unpublish-posts [data-dialog-action="confirm"]').click() + + await expect + .poll( + async () => { + const { docs } = await payload.find({ + collection: postsSlug, + where: { description: { equals: matchingDescription } }, + }) + + return docs.every((doc) => doc._status === 'draft') + }, + { timeout: POLL_TOPASS_TIMEOUT }, + ) + .toBe(true) + + // Documents outside the filter must be untouched + const { docs: otherDocs } = await payload.find({ + collection: postsSlug, + where: { description: { equals: otherDescription } }, + }) + + expect(otherDocs).toHaveLength(3) + + for (const doc of otherDocs) { + expect(doc._status).toBe('published') + } + }) + test('should update many', async () => { await deleteAllPosts()