From d0b014543c09b1c771eb01e1e2206eafe6831b81 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Thu, 9 Nov 2023 20:01:36 +0800 Subject: [PATCH] fix(core): select all in page list group header does not need to enable selection (#4869) --- .../page-list/components/floating-toobar.tsx | 14 +- .../src/components/page-list/page-group.tsx | 37 +++--- .../src/components/page-list/page-header.tsx | 1 + .../core/src/pages/workspace/all-page.tsx | 1 + tests/affine-local/e2e/all-page.spec.ts | 121 ++++++++++++++++++ 5 files changed, 153 insertions(+), 21 deletions(-) diff --git a/packages/frontend/component/src/components/page-list/components/floating-toobar.tsx b/packages/frontend/component/src/components/page-list/components/floating-toobar.tsx index 515452490..f6bebfdfc 100644 --- a/packages/frontend/component/src/components/page-list/components/floating-toobar.tsx +++ b/packages/frontend/component/src/components/page-list/components/floating-toobar.tsx @@ -3,6 +3,7 @@ import * as Toolbar from '@radix-ui/react-toolbar'; import clsx from 'clsx'; import { type CSSProperties, + type HTMLAttributes, type MouseEventHandler, type PropsWithChildren, type ReactNode, @@ -16,13 +17,11 @@ interface FloatingToolbarProps { open?: boolean; } -interface FloatingToolbarButtonProps { +interface FloatingToolbarButtonProps extends HTMLAttributes { icon: ReactNode; onClick: MouseEventHandler; type?: 'danger' | 'default'; label?: ReactNode; - className?: string; - style?: CSSProperties; } interface FloatingToolbarItemProps {} @@ -40,7 +39,12 @@ export function FloatingToolbar({ {/* always pop up on top for now */} - {children} + + {children} + @@ -62,6 +66,7 @@ export function FloatingToolbarButton({ className, style, label, + ...props }: FloatingToolbarButtonProps) { return (
{icon}
{label} diff --git a/packages/frontend/component/src/components/page-list/page-group.tsx b/packages/frontend/component/src/components/page-list/page-group.tsx index 29f8bf9f1..87963a921 100644 --- a/packages/frontend/component/src/components/page-list/page-group.tsx +++ b/packages/frontend/component/src/components/page-list/page-group.tsx @@ -119,19 +119,18 @@ export const PageGroupHeader = ({ id, items, label }: PageGroupProps) => { [id, setCollapseState] ); - const selectionState = useAtomValue(selectionStateAtom); + const [selectionState, setSelectionActive] = useAtom(selectionStateAtom); const selectedItems = useMemo(() => { const selectedPageIds = selectionState.selectedPageIds ?? []; return items.filter(item => selectedPageIds.includes(item.id)); }, [items, selectionState.selectedPageIds]); - const allSelected = useMemo(() => { - return items.every( - item => selectionState.selectedPageIds?.includes(item.id) - ); - }, [items, selectionState.selectedPageIds]); + const allSelected = selectedItems.length === items.length; const onSelectAll = useCallback(() => { + // also enable selection active + setSelectionActive(true); + const nonCurrentGroupIds = selectionState.selectedPageIds?.filter( id => !items.map(item => item.id).includes(id) @@ -142,12 +141,18 @@ export const PageGroupHeader = ({ id, items, label }: PageGroupProps) => { : [...nonCurrentGroupIds, ...items.map(item => item.id)]; selectionState.onSelectedPageIdsChange?.(newSelectedPageIds); - }, [items, selectionState, allSelected]); + }, [setSelectionActive, selectionState, allSelected, items]); const t = useAFFiNEI18N(); return label ? ( -
+
{
) : null}
- {selectionState.selectionActive ? ( - - ) : null} +
) : null; }; diff --git a/packages/frontend/component/src/components/page-list/page-header.tsx b/packages/frontend/component/src/components/page-list/page-header.tsx index 7d031f6f7..c69aae313 100644 --- a/packages/frontend/component/src/components/page-list/page-header.tsx +++ b/packages/frontend/component/src/components/page-list/page-header.tsx @@ -101,6 +101,7 @@ const PageListHeaderCheckbox = () => { return (
diff --git a/packages/frontend/core/src/pages/workspace/all-page.tsx b/packages/frontend/core/src/pages/workspace/all-page.tsx index 767920261..c108e742d 100644 --- a/packages/frontend/core/src/pages/workspace/all-page.tsx +++ b/packages/frontend/core/src/pages/workspace/all-page.tsx @@ -184,6 +184,7 @@ const PageListFloatingToolbar = ({ onClick={handleMultiDelete} icon={} type="danger" + data-testid="page-list-toolbar-delete" /> ); diff --git a/tests/affine-local/e2e/all-page.spec.ts b/tests/affine-local/e2e/all-page.spec.ts index 12f2bf77c..488abb265 100644 --- a/tests/affine-local/e2e/all-page.spec.ts +++ b/tests/affine-local/e2e/all-page.spec.ts @@ -194,3 +194,124 @@ test('allow creation of filters by tags', async ({ page }) => { await selectTag(page, 'B'); expect(await getPagesCount(page)).toBe(pageCount + 1); }); + +test('enable selection and use ESC to disable selection', async ({ page }) => { + await openHomePage(page); + await waitForEditorLoad(page); + await clickSideBarAllPageButton(page); + await waitForAllPagesLoad(page); + + // there should be no checkbox in the page list by default + expect( + await page + .locator('[data-testid="page-list-item"] [data-testid="affine-checkbox"]') + .count() + ).toBe(0); + + // by clicking [data-testid="page-list-header-selection-checkbox"], checkboxes should appear + await page + .locator('[data-testid="page-list-header-selection-checkbox"]') + .click(); + + // there should be checkboxes in the page list now + expect( + await page + .locator('[data-testid="page-list-item"] [data-testid="affine-checkbox"]') + .count() + ).toBeGreaterThan(0); + + // by ESC, checkboxes should NOT disappear (because it is too early) + await page.keyboard.press('Escape'); + + expect( + await page + .locator('[data-testid="page-list-item"] [data-testid="affine-checkbox"]') + .count() + ).toBeGreaterThan(0); + + // wait for 300ms + await page.waitForTimeout(300); + + // esc again, checkboxes should disappear + await page.keyboard.press('Escape'); + + expect( + await page + .locator('[data-testid="page-list-item"] [data-testid="affine-checkbox"]') + .count() + ).toBe(0); +}); + +test('select two pages and delete', async ({ page }) => { + await openHomePage(page); + await waitForEditorLoad(page); + await clickSideBarAllPageButton(page); + await waitForAllPagesLoad(page); + + const pageCount = await getPagesCount(page); + + // by clicking [data-testid="page-list-header-selection-checkbox"], checkboxes should appear + await page + .locator('[data-testid="page-list-header-selection-checkbox"]') + .click(); + + // select the first two pages + await page + .locator('[data-testid="page-list-item"] [data-testid="affine-checkbox"]') + .nth(0) + .click(); + + await page + .locator('[data-testid="page-list-item"] [data-testid="affine-checkbox"]') + .nth(1) + .click(); + + // the floating popover should appear + await expect(page.locator('[data-testid="floating-toolbar"]')).toBeVisible(); + await expect(page.locator('[data-testid="floating-toolbar"]')).toHaveText( + '2 selected' + ); + + // click delete button + await page.locator('[data-testid="page-list-toolbar-delete"]').click(); + + // the confirm dialog should appear + await expect(page.getByText('Delete 2 pages?')).toBeVisible(); + + await page.getByRole('button', { name: 'Delete' }).click(); + + // check the page count again + await page.waitForTimeout(300); + + expect(await getPagesCount(page)).toBe(pageCount - 2); +}); + +test('select a group of items by clicking "Select All" in group header', async ({ + page, +}) => { + await openHomePage(page); + await waitForEditorLoad(page); + await clickSideBarAllPageButton(page); + await waitForAllPagesLoad(page); + + // Select All will appear when hovering the header + await page.hover('[data-testid="page-list-group-header"]'); + + // click Select All + await page.getByRole('button', { name: 'Select All' }).click(); + + const selectedItemCount = await page + .locator('[data-testid="page-list-group-header"]') + .getAttribute('data-group-selected-items-count'); + + const selectedGroupItemTotalCount = await page + .locator('[data-testid="page-list-group-header"]') + .getAttribute('data-group-items-count'); + + expect(selectedItemCount).toBe(selectedGroupItemTotalCount); + + // check the selected count is equal to the one displayed in the floating toolbar + await expect(page.locator('[data-testid="floating-toolbar"]')).toHaveText( + `${selectedItemCount} selected` + ); +});