feat(core): add multiDelete to trash page (#6798)
close #6739 https://github.com/toeverything/AFFiNE/assets/102217452/b1779cdf-f617-4188-ad29-70ec1695af1b
This commit is contained in:
@@ -1,4 +1,9 @@
|
|||||||
import { CloseIcon, DeleteIcon } from '@blocksuite/icons';
|
import {
|
||||||
|
CloseIcon,
|
||||||
|
DeleteIcon,
|
||||||
|
DeletePermanentlyIcon,
|
||||||
|
ResetIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
import { FloatingToolbar } from './floating-toolbar';
|
import { FloatingToolbar } from './floating-toolbar';
|
||||||
@@ -9,23 +14,34 @@ export const ListFloatingToolbar = ({
|
|||||||
onClose,
|
onClose,
|
||||||
open,
|
open,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
onRestore,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
content: ReactNode;
|
content: ReactNode;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onDelete: () => void;
|
onDelete?: () => void;
|
||||||
|
onRestore?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<FloatingToolbar className={styles.floatingToolbar} open={open}>
|
<FloatingToolbar className={styles.floatingToolbar} open={open}>
|
||||||
<FloatingToolbar.Item>{content}</FloatingToolbar.Item>
|
<FloatingToolbar.Item>{content}</FloatingToolbar.Item>
|
||||||
<FloatingToolbar.Button onClick={onClose} icon={<CloseIcon />} />
|
<FloatingToolbar.Button onClick={onClose} icon={<CloseIcon />} />
|
||||||
<FloatingToolbar.Separator />
|
<FloatingToolbar.Separator />
|
||||||
<FloatingToolbar.Button
|
{!!onRestore && (
|
||||||
onClick={onDelete}
|
<FloatingToolbar.Button
|
||||||
icon={<DeleteIcon />}
|
onClick={onRestore}
|
||||||
type="danger"
|
icon={<ResetIcon />}
|
||||||
data-testid="list-toolbar-delete"
|
data-testid="list-toolbar-restore"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{!!onDelete && (
|
||||||
|
<FloatingToolbar.Button
|
||||||
|
onClick={onDelete}
|
||||||
|
icon={onRestore ? <DeletePermanentlyIcon /> : <DeleteIcon />}
|
||||||
|
type="danger"
|
||||||
|
data-testid="list-toolbar-delete"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</FloatingToolbar>
|
</FloatingToolbar>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,3 +22,4 @@ export * from './use-filtered-page-metas';
|
|||||||
export * from './utils';
|
export * from './utils';
|
||||||
export * from './view';
|
export * from './view';
|
||||||
export * from './virtualized-list';
|
export * from './virtualized-list';
|
||||||
|
export * from './virtualized-trash-list';
|
||||||
|
|||||||
@@ -82,3 +82,13 @@ export const editTagWrapper = style({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const deleteIcon = style({
|
||||||
|
color: cssVar('iconColor'),
|
||||||
|
selectors: {
|
||||||
|
'&:not(.without-hover):hover': {
|
||||||
|
color: cssVar('errorColor'),
|
||||||
|
background: cssVar('backgroundErrorColor'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
ConfirmModal,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
Menu,
|
Menu,
|
||||||
MenuIcon,
|
MenuIcon,
|
||||||
@@ -227,7 +226,21 @@ export const TrashOperationCell = ({
|
|||||||
onRestorePage,
|
onRestorePage,
|
||||||
}: TrashOperationCellProps) => {
|
}: TrashOperationCellProps) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const [open, setOpen] = useState(false);
|
const { openConfirmModal } = useConfirmModal();
|
||||||
|
|
||||||
|
const onConfirmPermanentlyDelete = useCallback(() => {
|
||||||
|
openConfirmModal({
|
||||||
|
title: `${t['com.affine.trashOperation.deletePermanently']()}?`,
|
||||||
|
description: t['com.affine.trashOperation.deleteDescription'](),
|
||||||
|
cancelText: t['Cancel'](),
|
||||||
|
confirmButtonOptions: {
|
||||||
|
type: 'error',
|
||||||
|
children: t['com.affine.trashOperation.delete'](),
|
||||||
|
},
|
||||||
|
onConfirm: onPermanentlyDeletePage,
|
||||||
|
});
|
||||||
|
}, [onPermanentlyDeletePage, openConfirmModal, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColWrapper flex={1}>
|
<ColWrapper flex={1}>
|
||||||
<Tooltip content={t['com.affine.trashOperation.restoreIt']()} side="top">
|
<Tooltip content={t['com.affine.trashOperation.restoreIt']()} side="top">
|
||||||
@@ -248,28 +261,12 @@ export const TrashOperationCell = ({
|
|||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
data-testid="delete-page-button"
|
data-testid="delete-page-button"
|
||||||
onClick={() => {
|
onClick={onConfirmPermanentlyDelete}
|
||||||
setOpen(true);
|
className={styles.deleteIcon}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<DeletePermanentlyIcon />
|
<DeletePermanentlyIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<ConfirmModal
|
|
||||||
title={`${t['com.affine.trashOperation.deletePermanently']()}?`}
|
|
||||||
description={t['com.affine.trashOperation.deleteDescription']()}
|
|
||||||
cancelText={t['com.affine.confirmModal.button.cancel']()}
|
|
||||||
confirmButtonOptions={{
|
|
||||||
type: 'error',
|
|
||||||
children: t['com.affine.trashOperation.delete'](),
|
|
||||||
}}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={setOpen}
|
|
||||||
onConfirm={() => {
|
|
||||||
onPermanentlyDeletePage();
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ColWrapper>
|
</ColWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import { toast, useConfirmModal } from '@affine/component';
|
||||||
|
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
|
||||||
|
import { useBlockSuiteDocMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||||
|
import { Trans } from '@affine/i18n';
|
||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import type { DocMeta } from '@blocksuite/store';
|
||||||
|
import { useService, WorkspaceService } from '@toeverything/infra';
|
||||||
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { usePageHelper } from '../blocksuite/block-suite-page-list/utils';
|
||||||
|
import { ListFloatingToolbar } from './components/list-floating-toolbar';
|
||||||
|
import { usePageHeaderColsDef } from './header-col-def';
|
||||||
|
import { TrashOperationCell } from './operation-cell';
|
||||||
|
import { PageListItemRenderer } from './page-group';
|
||||||
|
import { ListTableHeader } from './page-header';
|
||||||
|
import type { ItemListHandle, ListItem } from './types';
|
||||||
|
import { useFilteredPageMetas } from './use-filtered-page-metas';
|
||||||
|
import { VirtualizedList } from './virtualized-list';
|
||||||
|
|
||||||
|
export const VirtualizedTrashList = () => {
|
||||||
|
const currentWorkspace = useService(WorkspaceService).workspace;
|
||||||
|
const docCollection = currentWorkspace.docCollection;
|
||||||
|
const { restoreFromTrash, permanentlyDeletePage } =
|
||||||
|
useBlockSuiteMetaHelper(docCollection);
|
||||||
|
const pageMetas = useBlockSuiteDocMeta(docCollection);
|
||||||
|
const filteredPageMetas = useFilteredPageMetas(pageMetas, {
|
||||||
|
trash: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { isPreferredEdgeless } = usePageHelper(docCollection);
|
||||||
|
|
||||||
|
const listRef = useRef<ItemListHandle>(null);
|
||||||
|
const [showFloatingToolbar, setShowFloatingToolbar] = useState(false);
|
||||||
|
const [selectedPageIds, setSelectedPageIds] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const { openConfirmModal } = useConfirmModal();
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
const pageHeaderColsDef = usePageHeaderColsDef();
|
||||||
|
|
||||||
|
const filteredSelectedPageIds = useMemo(() => {
|
||||||
|
const ids = filteredPageMetas.map(page => page.id);
|
||||||
|
return selectedPageIds.filter(id => ids.includes(id));
|
||||||
|
}, [filteredPageMetas, selectedPageIds]);
|
||||||
|
|
||||||
|
const hideFloatingToolbar = useCallback(() => {
|
||||||
|
listRef.current?.toggleSelectable();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleMultiDelete = useCallback(() => {
|
||||||
|
filteredSelectedPageIds.forEach(pageId => {
|
||||||
|
permanentlyDeletePage(pageId);
|
||||||
|
});
|
||||||
|
hideFloatingToolbar();
|
||||||
|
toast(t['com.affine.toastMessage.permanentlyDeleted']());
|
||||||
|
}, [filteredSelectedPageIds, hideFloatingToolbar, permanentlyDeletePage, t]);
|
||||||
|
|
||||||
|
const handleMultiRestore = useCallback(() => {
|
||||||
|
filteredSelectedPageIds.forEach(pageId => {
|
||||||
|
restoreFromTrash(pageId);
|
||||||
|
});
|
||||||
|
hideFloatingToolbar();
|
||||||
|
toast(
|
||||||
|
t['com.affine.toastMessage.restored']({
|
||||||
|
title: filteredSelectedPageIds.length > 1 ? 'docs' : 'doc',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [filteredSelectedPageIds, hideFloatingToolbar, restoreFromTrash, t]);
|
||||||
|
|
||||||
|
const onConfirmPermanentlyDelete = useCallback(() => {
|
||||||
|
openConfirmModal({
|
||||||
|
title: `${t['com.affine.trashOperation.deletePermanently']()}?`,
|
||||||
|
description: t['com.affine.trashOperation.deleteDescription'](),
|
||||||
|
cancelText: t['Cancel'](),
|
||||||
|
confirmButtonOptions: {
|
||||||
|
type: 'error',
|
||||||
|
children: t['com.affine.trashOperation.delete'](),
|
||||||
|
},
|
||||||
|
onConfirm: handleMultiDelete,
|
||||||
|
});
|
||||||
|
}, [handleMultiDelete, openConfirmModal, t]);
|
||||||
|
|
||||||
|
const pageOperationsRenderer = useCallback(
|
||||||
|
(item: ListItem) => {
|
||||||
|
const page = item as DocMeta;
|
||||||
|
const onRestorePage = () => {
|
||||||
|
restoreFromTrash(page.id);
|
||||||
|
toast(
|
||||||
|
t['com.affine.toastMessage.restored']({
|
||||||
|
title: page.title || 'Untitled',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const onPermanentlyDeletePage = () => {
|
||||||
|
permanentlyDeletePage(page.id);
|
||||||
|
toast(t['com.affine.toastMessage.permanentlyDeleted']());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TrashOperationCell
|
||||||
|
onPermanentlyDeletePage={onPermanentlyDeletePage}
|
||||||
|
onRestorePage={onRestorePage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
[permanentlyDeletePage, restoreFromTrash, t]
|
||||||
|
);
|
||||||
|
const pageItemRenderer = useCallback((item: ListItem) => {
|
||||||
|
return <PageListItemRenderer {...item} />;
|
||||||
|
}, []);
|
||||||
|
const pageHeaderRenderer = useCallback(() => {
|
||||||
|
return <ListTableHeader headerCols={pageHeaderColsDef} />;
|
||||||
|
}, [pageHeaderColsDef]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<VirtualizedList
|
||||||
|
ref={listRef}
|
||||||
|
selectable="toggle"
|
||||||
|
items={filteredPageMetas}
|
||||||
|
rowAsLink
|
||||||
|
isPreferredEdgeless={isPreferredEdgeless}
|
||||||
|
onSelectionActiveChange={setShowFloatingToolbar}
|
||||||
|
docCollection={currentWorkspace.docCollection}
|
||||||
|
operationsRenderer={pageOperationsRenderer}
|
||||||
|
itemRenderer={pageItemRenderer}
|
||||||
|
headerRenderer={pageHeaderRenderer}
|
||||||
|
selectedIds={filteredSelectedPageIds}
|
||||||
|
onSelectedIdsChange={setSelectedPageIds}
|
||||||
|
/>
|
||||||
|
<ListFloatingToolbar
|
||||||
|
open={showFloatingToolbar && filteredSelectedPageIds.length > 0}
|
||||||
|
onDelete={onConfirmPermanentlyDelete}
|
||||||
|
onClose={hideFloatingToolbar}
|
||||||
|
onRestore={handleMultiRestore}
|
||||||
|
content={
|
||||||
|
<Trans
|
||||||
|
i18nKey="com.affine.page.toolbar.selected"
|
||||||
|
count={filteredSelectedPageIds.length}
|
||||||
|
>
|
||||||
|
<div style={{ color: 'var(--affine-text-secondary-color)' }}>
|
||||||
|
{{ count: filteredSelectedPageIds.length } as any}
|
||||||
|
</div>
|
||||||
|
selected
|
||||||
|
</Trans>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ export const trashTitle = style({
|
|||||||
gap: 8,
|
gap: 8,
|
||||||
padding: '0 8px',
|
padding: '0 8px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
|
userSelect: 'none',
|
||||||
});
|
});
|
||||||
export const body = style({
|
export const body = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
import { toast } from '@affine/component';
|
|
||||||
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
|
||||||
import type { ListItem } from '@affine/core/components/page-list';
|
|
||||||
import {
|
import {
|
||||||
ListTableHeader,
|
|
||||||
PageListItemRenderer,
|
|
||||||
TrashOperationCell,
|
|
||||||
useFilteredPageMetas,
|
useFilteredPageMetas,
|
||||||
VirtualizedList,
|
VirtualizedTrashList,
|
||||||
} from '@affine/core/components/page-list';
|
} from '@affine/core/components/page-list';
|
||||||
import { usePageHeaderColsDef } from '@affine/core/components/page-list/header-col-def';
|
|
||||||
import { Header } from '@affine/core/components/pure/header';
|
import { Header } from '@affine/core/components/pure/header';
|
||||||
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
|
|
||||||
import { useBlockSuiteDocMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
import { useBlockSuiteDocMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import { DeleteIcon } from '@blocksuite/icons';
|
import { DeleteIcon } from '@blocksuite/icons';
|
||||||
import type { DocMeta } from '@blocksuite/store';
|
|
||||||
import { useService, WorkspaceService } from '@toeverything/infra';
|
import { useService, WorkspaceService } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { ViewBodyIsland, ViewHeaderIsland } from '../../modules/workbench';
|
import { ViewBodyIsland, ViewHeaderIsland } from '../../modules/workbench';
|
||||||
import { EmptyPageList } from './page-list-empty';
|
import { EmptyPageList } from './page-list-empty';
|
||||||
@@ -47,44 +37,6 @@ export const TrashPage = () => {
|
|||||||
trash: true,
|
trash: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { restoreFromTrash, permanentlyDeletePage } =
|
|
||||||
useBlockSuiteMetaHelper(docCollection);
|
|
||||||
const { isPreferredEdgeless } = usePageHelper(docCollection);
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
const pageHeaderColsDef = usePageHeaderColsDef();
|
|
||||||
|
|
||||||
const pageOperationsRenderer = useCallback(
|
|
||||||
(item: ListItem) => {
|
|
||||||
const page = item as DocMeta;
|
|
||||||
const onRestorePage = () => {
|
|
||||||
restoreFromTrash(page.id);
|
|
||||||
toast(
|
|
||||||
t['com.affine.toastMessage.restored']({
|
|
||||||
title: page.title || 'Untitled',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const onPermanentlyDeletePage = () => {
|
|
||||||
permanentlyDeletePage(page.id);
|
|
||||||
toast(t['com.affine.toastMessage.permanentlyDeleted']());
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TrashOperationCell
|
|
||||||
onPermanentlyDeletePage={onPermanentlyDeletePage}
|
|
||||||
onRestorePage={onRestorePage}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
[permanentlyDeletePage, restoreFromTrash, t]
|
|
||||||
);
|
|
||||||
const pageItemRenderer = useCallback((item: ListItem) => {
|
|
||||||
return <PageListItemRenderer {...item} />;
|
|
||||||
}, []);
|
|
||||||
const pageHeaderRenderer = useCallback(() => {
|
|
||||||
return <ListTableHeader headerCols={pageHeaderColsDef} />;
|
|
||||||
}, [pageHeaderColsDef]);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewHeaderIsland>
|
<ViewHeaderIsland>
|
||||||
@@ -93,15 +45,7 @@ export const TrashPage = () => {
|
|||||||
<ViewBodyIsland>
|
<ViewBodyIsland>
|
||||||
<div className={styles.body}>
|
<div className={styles.body}>
|
||||||
{filteredPageMetas.length > 0 ? (
|
{filteredPageMetas.length > 0 ? (
|
||||||
<VirtualizedList
|
<VirtualizedTrashList />
|
||||||
items={filteredPageMetas}
|
|
||||||
rowAsLink
|
|
||||||
isPreferredEdgeless={isPreferredEdgeless}
|
|
||||||
docCollection={currentWorkspace.docCollection}
|
|
||||||
operationsRenderer={pageOperationsRenderer}
|
|
||||||
itemRenderer={pageItemRenderer}
|
|
||||||
headerRenderer={pageHeaderRenderer}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<EmptyPageList
|
<EmptyPageList
|
||||||
type="trash"
|
type="trash"
|
||||||
|
|||||||
@@ -228,6 +228,65 @@ test('select two pages and delete', async ({ page }) => {
|
|||||||
|
|
||||||
expect(await getPagesCount(page)).toBe(pageCount - 2);
|
expect(await getPagesCount(page)).toBe(pageCount - 2);
|
||||||
});
|
});
|
||||||
|
test('select two pages and permanently delete', async ({ page }) => {
|
||||||
|
await openHomePage(page);
|
||||||
|
await waitForEditorLoad(page);
|
||||||
|
await clickNewPageButton(page);
|
||||||
|
await clickSideBarAllPageButton(page);
|
||||||
|
await waitForAllPagesLoad(page);
|
||||||
|
|
||||||
|
const pageCount = await getPagesCount(page);
|
||||||
|
|
||||||
|
await page.keyboard.down('Shift');
|
||||||
|
await page.locator('[data-testid="page-list-item"]').nth(0).click();
|
||||||
|
|
||||||
|
await page.locator('[data-testid="page-list-item"]').nth(1).click();
|
||||||
|
await page.keyboard.up('Shift');
|
||||||
|
|
||||||
|
// the floating popover should appear
|
||||||
|
await expect(page.locator('[data-testid="floating-toolbar"]')).toBeVisible();
|
||||||
|
await expect(page.locator('[data-testid="floating-toolbar"]')).toHaveText(
|
||||||
|
'2 doc(s) selected'
|
||||||
|
);
|
||||||
|
|
||||||
|
// click delete button
|
||||||
|
await page.locator('[data-testid="list-toolbar-delete"]').click();
|
||||||
|
|
||||||
|
// the confirm dialog should appear
|
||||||
|
await expect(page.getByText('Delete 2 docs?')).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Delete' }).click();
|
||||||
|
|
||||||
|
// check the page count again
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
|
||||||
|
expect(await getPagesCount(page)).toBe(pageCount - 2);
|
||||||
|
|
||||||
|
await page.getByTestId('trash-page').click();
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
const trashPageCount = await getPagesCount(page);
|
||||||
|
|
||||||
|
expect(trashPageCount).toBe(2);
|
||||||
|
|
||||||
|
await page.keyboard.down('Shift');
|
||||||
|
await page.locator('[data-testid="page-list-item"]').nth(0).click();
|
||||||
|
|
||||||
|
await page.locator('[data-testid="page-list-item"]').nth(1).click();
|
||||||
|
await page.keyboard.up('Shift');
|
||||||
|
|
||||||
|
await expect(page.locator('[data-testid="floating-toolbar"]')).toBeVisible();
|
||||||
|
await expect(page.locator('[data-testid="floating-toolbar"]')).toHaveText(
|
||||||
|
'2 doc(s) selected'
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.locator('[data-testid="list-toolbar-delete"]').click();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Delete' }).click();
|
||||||
|
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
|
||||||
|
expect(await getPagesCount(page)).toBe(trashPageCount - 2);
|
||||||
|
});
|
||||||
|
|
||||||
test('select a group of items by clicking "Select All" in group header', async ({
|
test('select a group of items by clicking "Select All" in group header', async ({
|
||||||
page,
|
page,
|
||||||
|
|||||||
Reference in New Issue
Block a user