From 1d865ad8836074c67d45f8621a723d7ccb363388 Mon Sep 17 00:00:00 2001 From: Saul-Mirone Date: Fri, 28 Feb 2025 04:37:12 +0000 Subject: [PATCH] test(editor): collab table test (#10506) 1. **Table UI Enhancements - Test IDs Added** - Added `data-testid` attributes to several table components for better testability: - `add-column-button` for the column addition button - `add-row-button` for the row addition button - `drag-column-handle` for column drag handles - `drag-row-handle` for row drag handles 2. **New Test Infrastructure** - Added new testing utilities in `tests/kit/src/bs/`: - `misc.ts`: Added `waitNextFrame` utility function for handling animation frame timing in tests - `table.ts`: Added comprehensive table testing utilities including: - `createTable`: Creates a new table with initial cells - `getCellText`: Retrieves text from a specific table cell - `inputToCell`: Inputs text into a specific table cell - `clickDeleteButtonInTableMenu`: Handles table deletion operations 3. **New Collaboration Test** - Added a new test file `tests/affine-local/e2e/blocksuite/table/collab.spec.ts` that tests table collaboration features: - Tests synchronization between two pages (A and B) - Verifies table operations sync correctly: - Adding columns and rows - Inputting cell content - Deleting columns and rows - Validates cell content consistency across both pages - Tests the complete table manipulation workflow in a collaborative setting 4. **Package Configuration Update** - Modified `tests/kit/package.json` to expose new test utilities: - Added new export mapping: `"./bs/*": "./src/bs/*.ts"` to make the new table testing utilities accessible This PR primarily focuses on improving table testing infrastructure and adding comprehensive collaboration tests for the table functionality, while also enhancing component testability through data-test-ids. --- .../affine/block-table/src/add-button.ts | 2 + .../affine/block-table/src/table-cell.ts | 2 + .../e2e/blocksuite/table/collab.spec.ts | 126 ++++++++++++++++++ tests/kit/package.json | 3 +- tests/kit/src/bs/misc.ts | 7 + tests/kit/src/bs/table.ts | 53 ++++++++ 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 tests/affine-local/e2e/blocksuite/table/collab.spec.ts create mode 100644 tests/kit/src/bs/misc.ts create mode 100644 tests/kit/src/bs/table.ts diff --git a/blocksuite/affine/block-table/src/add-button.ts b/blocksuite/affine/block-table/src/add-button.ts index 6fbbde780..48e01602f 100644 --- a/blocksuite/affine/block-table/src/add-button.ts +++ b/blocksuite/affine/block-table/src/add-button.ts @@ -246,6 +246,7 @@ export class AddButton extends SignalWatcher( this.hoverColumnIndex$.value === this.columns$.value.length - 1; const dragging = this.columnDragging$.value; return html`
{ + await openHomePage(pageA); + await clickNewPageButton(pageA); + await waitForEditorLoad(pageA); + await pageA.keyboard.press('Enter'); + + /** + * | Cell1 | Cell2 | + * | Cell3 | Cell4 | + */ + await createTable(pageA); + const cellsInA = pageA.locator('affine-table-cell'); + const cellCountInA = await cellsInA.count(); + expect(cellCountInA).toBe(4); + + const currentUrl = pageA.url(); + + const pageB = await context.newPage(); + await pageB.goto(currentUrl); + await waitForEditorLoad(pageB); + await pageB.keyboard.press('Enter'); + + const tableInB = pageB.locator('affine-table'); + await expect(tableInB).toBeVisible(); + const cellsInB = tableInB.locator('affine-table-cell'); + expect(await cellsInB.count()).toBe(cellCountInA); + + expect(await getCellText(pageB, 0)).toBe('Cell1'); + expect(await getCellText(pageB, 3)).toBe('Cell4'); + + await cellsInB.last().hover(); + const addColumnButton = tableInB.locator('data-testid=add-column-button'); + expect(await addColumnButton.isVisible()).toBe(true); + await addColumnButton.click(); + + expect(await cellsInA.count()).toBe(6); + expect(await cellsInB.count()).toBe(6); + + // new created cells should be synced to both page + /** + * | Cell1 | Cell2 | Cell5 | + * | Cell3 | Cell4 | Cell6 | + */ + await inputToCell(pageA, 2, 'Cell5'); + await inputToCell(pageA, 5, 'Cell6'); + expect(await getCellText(pageB, 2)).toBe('Cell5'); + expect(await getCellText(pageB, 5)).toBe('Cell6'); + + await cellsInB.last().hover(); + const addRowButton = tableInB.locator('data-testid=add-row-button'); + expect(await addRowButton.isVisible()).toBe(true); + await addRowButton.click(); + + expect(await cellsInA.count()).toBe(9); + expect(await cellsInB.count()).toBe(9); + + // new created cells should be synced to both page + /** + * | Cell1 | Cell2 | Cell5 | + * | Cell3 | Cell4 | Cell6 | + * | Cell7 | Cell8 | Cell9 | + */ + await inputToCell(pageA, 6, 'Cell7'); + await inputToCell(pageA, 7, 'Cell8'); + await inputToCell(pageA, 8, 'Cell9'); + expect(await getCellText(pageB, 6)).toBe('Cell7'); + expect(await getCellText(pageB, 7)).toBe('Cell8'); + expect(await getCellText(pageB, 8)).toBe('Cell9'); + + // delete a column + await cellsInA.nth(1).hover(); + const dragColumnHandle = cellsInA + .nth(1) + .locator('data-testid=drag-column-handle'); + expect(await dragColumnHandle.isVisible()).toBe(true); + await dragColumnHandle.click(); + + await clickDeleteButtonInTableMenu(pageA); + /** + * | Cell1 | Cell5 | + * | Cell3 | Cell6 | + * | Cell7 | Cell9 | + */ + expect(await cellsInA.count()).toBe(6); + expect(await cellsInB.count()).toBe(6); + + expect(await getCellText(pageB, 0)).toBe('Cell1'); + expect(await getCellText(pageB, 1)).toBe('Cell5'); + expect(await getCellText(pageB, 2)).toBe('Cell3'); + expect(await getCellText(pageB, 3)).toBe('Cell6'); + expect(await getCellText(pageB, 4)).toBe('Cell7'); + expect(await getCellText(pageB, 5)).toBe('Cell9'); + + // delete a row + await cellsInB.nth(0).hover(); + const dragRowHandle = cellsInB.nth(0).locator('data-testid=drag-row-handle'); + expect(await dragRowHandle.isVisible()).toBe(true); + await dragRowHandle.click(); + + await clickDeleteButtonInTableMenu(pageB); + /** + * | Cell3 | Cell6 | + * | Cell7 | Cell9 | + */ + expect(await cellsInA.count()).toBe(4); + expect(await cellsInB.count()).toBe(4); + + expect(await getCellText(pageB, 0)).toBe('Cell3'); + expect(await getCellText(pageB, 1)).toBe('Cell6'); + expect(await getCellText(pageB, 2)).toBe('Cell7'); + expect(await getCellText(pageB, 3)).toBe('Cell9'); +}); diff --git a/tests/kit/package.json b/tests/kit/package.json index 47718e922..9be18de96 100644 --- a/tests/kit/package.json +++ b/tests/kit/package.json @@ -7,7 +7,8 @@ "./electron": "./src/electron.ts", "./mobile": "./src/mobile.ts", "./playwright": "./src/playwright.ts", - "./utils/*": "./src/utils/*.ts" + "./utils/*": "./src/utils/*.ts", + "./bs/*": "./src/bs/*.ts" }, "devDependencies": { "@affine-tools/utils": "workspace:*", diff --git a/tests/kit/src/bs/misc.ts b/tests/kit/src/bs/misc.ts new file mode 100644 index 000000000..b321102f8 --- /dev/null +++ b/tests/kit/src/bs/misc.ts @@ -0,0 +1,7 @@ +import type { Page } from '@playwright/test'; + +export async function waitNextFrame(page: Page) { + await page.evaluate( + () => new Promise(resolve => requestAnimationFrame(resolve)) + ); +} diff --git a/tests/kit/src/bs/table.ts b/tests/kit/src/bs/table.ts new file mode 100644 index 000000000..bdd368bcb --- /dev/null +++ b/tests/kit/src/bs/table.ts @@ -0,0 +1,53 @@ +import { expect, type Locator, type Page } from '@playwright/test'; + +import { waitNextFrame } from './misc'; + +export async function createTable(page: Page) { + await page.keyboard.press('/'); + await expect(page.locator('affine-slash-menu .slash-menu')).toBeVisible(); + await page.keyboard.type('table'); + await page.keyboard.press('Enter'); + await waitNextFrame(page); + const table = page.locator('affine-table'); + + await expect(table).toBeVisible(); + const cells = table.locator('affine-table-cell'); + const cellCount = await cells.count(); + expect(cellCount).toBe(4); + + for (let i = 0; i < cellCount; i++) { + await inputToCell(page, i, `Cell${i + 1}`); + } +} + +export async function getCellText(page: Page, nth: number, table?: Locator) { + table = table ?? page.locator('affine-table'); + const cell = table.locator('affine-table-cell').nth(nth); + await cell.hover(); + await waitNextFrame(page); + return cell.locator('v-line').innerText(); +} + +export async function inputToCell( + page: Page, + nth: number, + text: string, + table?: Locator +) { + table = table ?? page.locator('affine-table'); + const cell = table.locator('affine-table-cell').nth(nth); + await cell.hover(); + await waitNextFrame(page); + await cell.click(); + await cell.click(); + await waitNextFrame(page); + await page.keyboard.type(text, { delay: 20 }); +} + +export async function clickDeleteButtonInTableMenu(page: Page) { + const menu = page.locator('affine-menu'); + await expect(menu).toBeVisible(); + const deleteButton = menu.getByText('Delete'); + await expect(deleteButton).toBeVisible(); + await deleteButton.click(); +}