From 5c5c9f8dcd21b3ce1eb37c794fb7f937076924ba Mon Sep 17 00:00:00 2001 From: zzj3720 <17165520+zzj3720@users.noreply.github.com> Date: Fri, 28 Feb 2025 12:06:28 +0000 Subject: [PATCH] fix(editor): insertion position of new rows and columns is incorrect in table block (#10516) fix: BS-2714 --- .../affine/block-table/src/table-cell.ts | 12 +- .../blocksuite/table/edge-insertion.spec.ts | 200 ++++++++++++++++++ .../e2e/blocksuite/table/insertion.spec.ts | 198 +++++++++++++++++ 3 files changed, 406 insertions(+), 4 deletions(-) create mode 100644 tests/affine-local/e2e/blocksuite/table/edge-insertion.spec.ts create mode 100644 tests/affine-local/e2e/blocksuite/table/insertion.spec.ts diff --git a/blocksuite/affine/block-table/src/table-cell.ts b/blocksuite/affine/block-table/src/table-cell.ts index d865364cb..85d5b83dd 100644 --- a/blocksuite/affine/block-table/src/table-cell.ts +++ b/blocksuite/affine/block-table/src/table-cell.ts @@ -180,14 +180,16 @@ export class TableCell extends SignalWatcher( name: 'Insert Left', prefix: InsertLeftIcon(), select: () => { - this.dataManager.insertColumn(columnIndex - 1); + this.dataManager.insertColumn( + columnIndex > 0 ? columnIndex - 1 : undefined + ); }, }), menu.action({ name: 'Insert Right', prefix: InsertRightIcon(), select: () => { - this.dataManager.insertColumn(columnIndex + 1); + this.dataManager.insertColumn(columnIndex); }, }), menu.action({ @@ -304,14 +306,16 @@ export class TableCell extends SignalWatcher( name: 'Insert Above', prefix: InsertAboveIcon(), select: () => { - this.dataManager.insertRow(rowIndex - 1); + this.dataManager.insertRow( + rowIndex > 0 ? rowIndex - 1 : undefined + ); }, }), menu.action({ name: 'Insert Below', prefix: InsertBelowIcon(), select: () => { - this.dataManager.insertRow(rowIndex + 1); + this.dataManager.insertRow(rowIndex); }, }), menu.action({ diff --git a/tests/affine-local/e2e/blocksuite/table/edge-insertion.spec.ts b/tests/affine-local/e2e/blocksuite/table/edge-insertion.spec.ts new file mode 100644 index 000000000..105666a9e --- /dev/null +++ b/tests/affine-local/e2e/blocksuite/table/edge-insertion.spec.ts @@ -0,0 +1,200 @@ +import { waitNextFrame } from '@affine-test/kit/bs/misc'; +import { createTable, getCellText } from '@affine-test/kit/bs/table'; +import { test } from '@affine-test/kit/playwright'; +import { openHomePage } from '@affine-test/kit/utils/load-page'; +import { + clickNewPageButton, + waitForEditorLoad, +} from '@affine-test/kit/utils/page-logic'; +import { expect } from '@playwright/test'; + +/** + * This test suite validates the edge case functionality of table insertion operations. + * It tests insertion at boundaries of the table, such as: + * - Insert Left at the first column + * - Insert Above at the first row + * - Insert Right at the last column + * - Insert Below at the last row + */ +test.describe('Table edge case insertion operations', () => { + test('should correctly insert at edges', async ({ page }) => { + // Setup: Create a new page with a table + await openHomePage(page); + await clickNewPageButton(page); + await waitForEditorLoad(page); + await page.keyboard.press('Enter'); + + // Create a simple table with default 2x2 cells + await createTable(page); + const table = page.locator('affine-table'); + await expect(table).toBeVisible(); + + // Wait for table rendering to complete + await waitNextFrame(page); + + // Initialize all cell contents + const cells = table.locator('affine-table-cell'); + expect(await cells.count()).toBe(4); + + // Clear and set cell contents + for (let i = 0; i < 4; i++) { + const cell = cells.nth(i); + await cell.click({ clickCount: 3 }); // Select all content in the cell + await page.keyboard.press('Backspace'); // Clear content + await page.keyboard.type(`Cell${i + 1}`); // Input new content + await waitNextFrame(page); + } + + // Verify initial cell contents + expect(await getCellText(page, 0)).toBe('Cell1'); + expect(await getCellText(page, 1)).toBe('Cell2'); + + // Edge case 1: Insert to the left of the first column + // Hover over the first column to make the column options button visible + await cells.nth(0).hover(); + await waitNextFrame(page); + + // Click on the column options button to open the column options menu + const columnOptionButton = table + .locator('[data-testid="drag-column-handle"]') + .first(); + await expect(columnOptionButton).toBeVisible(); + await columnOptionButton.click(); + + // Click on the "Insert Left" option in the menu + const menu = page.locator('affine-menu'); + await expect(menu).toBeVisible(); + await menu.getByText('Insert Left').click(); + await waitNextFrame(page); + + // Verify: Now we should have 6 cells (3x2 table) + const cellsAfterLeftEdgeInsert = table.locator('affine-table-cell'); + expect(await cellsAfterLeftEdgeInsert.count()).toBe(6); + + // Input text to verify insertion position + const leftEdgeCell = cellsAfterLeftEdgeInsert.nth(0); + await leftEdgeCell.click({ clickCount: 3 }); // Select all content + await page.keyboard.press('Backspace'); // Clear content + await page.keyboard.type('Left Edge'); + await waitNextFrame(page); + + // Verify first row values: Left Edge, Cell1, Cell2 + expect(await getCellText(page, 0)).toBe('Left Edge'); + + // Fix: Re-get and verify second column content + const secondCell = cellsAfterLeftEdgeInsert.nth(1); + await secondCell.click({ clickCount: 3 }); // Select all content + await page.keyboard.press('Backspace'); // Clear content + await page.keyboard.type('Cell1'); // Re-input + await waitNextFrame(page); + + expect(await getCellText(page, 1)).toBe('Cell1'); + + // Fix: Re-get and verify third column content + const thirdCell = cellsAfterLeftEdgeInsert.nth(2); + await thirdCell.click({ clickCount: 3 }); // Select all content + await page.keyboard.press('Backspace'); // Clear content + await page.keyboard.type('Cell2'); // Re-input + await waitNextFrame(page); + + expect(await getCellText(page, 2)).toBe('Cell2'); + + // Edge case 2: Insert above the first row + // Hover over the first row to make the row options button visible + await cellsAfterLeftEdgeInsert.nth(0).hover(); + await waitNextFrame(page); + + // Click on the row options button to open the row options menu + const rowOptionButton = table + .locator('[data-testid="drag-row-handle"]') + .first(); + await expect(rowOptionButton).toBeVisible(); + await rowOptionButton.click(); + + // Click on the "Insert Above" option in the menu + await expect(menu).toBeVisible(); + await menu.getByText('Insert Above').click(); + await waitNextFrame(page); + + // Verify: Now we should have 9 cells (3x3 table) + const cellsAfterAboveEdgeInsert = table.locator('affine-table-cell'); + expect(await cellsAfterAboveEdgeInsert.count()).toBe(9); + + // Input text to verify insertion position + const aboveEdgeCell = cellsAfterAboveEdgeInsert.nth(0); + await aboveEdgeCell.click({ clickCount: 3 }); // Select all content + await page.keyboard.press('Backspace'); // Clear content + await page.keyboard.type('Above Edge'); + await waitNextFrame(page); + + // Verify first row first column value + expect(await getCellText(page, 0)).toBe('Above Edge'); + + // Verify second row first column value (previously the first row first column) + expect(await getCellText(page, 3)).toBe('Left Edge'); + + // Edge case 3: Insert to the right of the last column + // Hover over the last column to make the column options button visible + const lastColumnCell = cellsAfterAboveEdgeInsert.nth(2); + await lastColumnCell.hover(); + await waitNextFrame(page); + + // Click on the last column's options button + const lastColumnOptionButton = table + .locator('[data-testid="drag-column-handle"]') + .nth(2); + await expect(lastColumnOptionButton).toBeVisible(); + await lastColumnOptionButton.click(); + + // Click on the "Insert Right" option in the menu + await expect(menu).toBeVisible(); + await menu.getByText('Insert Right').click(); + await waitNextFrame(page); + + // Verify: Now we should have 12 cells (4x3 table) + const cellsAfterRightEdgeInsert = table.locator('affine-table-cell'); + expect(await cellsAfterRightEdgeInsert.count()).toBe(12); + + // Input text to verify insertion position + const rightEdgeCell = cellsAfterRightEdgeInsert.nth(3); + await rightEdgeCell.click({ clickCount: 3 }); // Select all content + await page.keyboard.press('Backspace'); // Clear content + await page.keyboard.type('Right Edge'); + await waitNextFrame(page); + + // Verify first row last column value + expect(await getCellText(page, 3)).toBe('Right Edge'); + + // Edge case 4: Insert below the last row + // Find the first cell of the last row + const lastRowCell = cellsAfterRightEdgeInsert.nth(8); // Row 3, Column 1 + await lastRowCell.hover(); + await waitNextFrame(page); + + // Click on the last row's options button + const lastRowOptionButton = table + .locator('[data-testid="drag-row-handle"]') + .nth(2); + await expect(lastRowOptionButton).toBeVisible(); + await lastRowOptionButton.click(); + + // Click on the "Insert Below" option in the menu + await expect(menu).toBeVisible(); + await menu.getByText('Insert Below').click(); + await waitNextFrame(page); + + // Verify: Now we should have 16 cells (4x4 table) + const cellsAfterBelowEdgeInsert = table.locator('affine-table-cell'); + expect(await cellsAfterBelowEdgeInsert.count()).toBe(16); + + // Input text to verify insertion position + const belowEdgeCell = cellsAfterBelowEdgeInsert.nth(12); // Row 4, Column 1 + await belowEdgeCell.click({ clickCount: 3 }); // Select all content + await page.keyboard.press('Backspace'); // Clear content + await page.keyboard.type('Below Edge'); + await waitNextFrame(page); + + // Verify last row first column value + expect(await getCellText(page, 12)).toBe('Below Edge'); + }); +}); diff --git a/tests/affine-local/e2e/blocksuite/table/insertion.spec.ts b/tests/affine-local/e2e/blocksuite/table/insertion.spec.ts new file mode 100644 index 000000000..21b238130 --- /dev/null +++ b/tests/affine-local/e2e/blocksuite/table/insertion.spec.ts @@ -0,0 +1,198 @@ +import { waitNextFrame } from '@affine-test/kit/bs/misc'; +import { createTable, getCellText } from '@affine-test/kit/bs/table'; +import { test } from '@affine-test/kit/playwright'; +import { openHomePage } from '@affine-test/kit/utils/load-page'; +import { + clickNewPageButton, + waitForEditorLoad, +} from '@affine-test/kit/utils/page-logic'; +import { expect } from '@playwright/test'; + +/** + * This test suite validates the correct functionality of table insertion operations. + * It tests Insert Left, Insert Right, Insert Above, and Insert Below operations + * to verify they correctly insert rows and columns in the expected positions. + */ +test.describe('Table insertion operations', () => { + test('should correctly insert columns', async ({ page }) => { + // Setup: Create a new page with a table + await openHomePage(page); + await clickNewPageButton(page); + await waitForEditorLoad(page); + await page.keyboard.press('Enter'); + + // Create a simple table with default 2x2 cells + await createTable(page); + const table = page.locator('affine-table'); + await expect(table).toBeVisible(); + + // Verify initial table structure (2x2) + const initialCells = table.locator('affine-table-cell'); + expect(await initialCells.count()).toBe(4); + + // Hover over the first cell to make the column options button visible + await initialCells.nth(0).hover(); + await waitNextFrame(page); + + // Click on the column options button (three dots icon) to open the column options menu + const columnOptionButton = table + .locator('[data-testid="drag-column-handle"]') + .first(); + await expect(columnOptionButton).toBeVisible(); + await columnOptionButton.click(); + + // Click on the "Insert Right" option in the menu + const menu = page.locator('affine-menu'); + await expect(menu).toBeVisible(); + await menu.getByText('Insert Right').click(); + await waitNextFrame(page); + + // Verify: Now we should have 6 cells (3x2 table) + const cellsAfterRightInsert = table.locator('affine-table-cell'); + expect(await cellsAfterRightInsert.count()).toBe(6); + + // Input text in the newly inserted column to verify its position + // After inserting right of column 0, our new cell should be at position 1 + await getCellText(page, 0); // Wait for cell to be ready + const newColumnCell = cellsAfterRightInsert.nth(1); + await newColumnCell.hover(); + await waitNextFrame(page); + await newColumnCell.click(); + await page.keyboard.type('New Right'); + + // Verify values in row 0: Cell1, New Right, Cell2 + expect(await getCellText(page, 0)).toBe('Cell1'); + expect(await getCellText(page, 1)).toBe('New Right'); + expect(await getCellText(page, 2)).toBe('Cell2'); + + // Hover over the first column again to make the column options button visible + await cellsAfterRightInsert.nth(0).hover(); + await waitNextFrame(page); + + // Click on the column options button to open the column options menu + await expect(columnOptionButton).toBeVisible(); + await columnOptionButton.click(); + + // Click on the "Insert Left" option in the menu + await expect(menu).toBeVisible(); + await menu.getByText('Insert Left').click(); + await waitNextFrame(page); + + // Verify: Now we should have 8 cells (4x2 table) + const cellsAfterLeftInsert = table.locator('affine-table-cell'); + expect(await cellsAfterLeftInsert.count()).toBe(8); + + // Input text in the newly inserted column + await getCellText(page, 0); // Wait for cell to be ready + const leftColumnCell = cellsAfterLeftInsert.nth(0); + await leftColumnCell.hover(); + await waitNextFrame(page); + await leftColumnCell.click(); + await page.keyboard.type('New Left'); + + // Verify values in row 0: New Left, Cell1, New Right, Cell2 + expect(await getCellText(page, 0)).toBe('New Left'); + expect(await getCellText(page, 1)).toBe('Cell1'); + expect(await getCellText(page, 2)).toBe('New Right'); + expect(await getCellText(page, 3)).toBe('Cell2'); + }); + + test('should correctly insert rows', async ({ page }) => { + // Setup: Create a new page with a table + await openHomePage(page); + await clickNewPageButton(page); + await waitForEditorLoad(page); + await page.keyboard.press('Enter'); + + // Create a simple table with default 2x2 cells + await createTable(page); + const table = page.locator('affine-table'); + await expect(table).toBeVisible(); + + // Verify initial table structure (2x2) + const initialCells = table.locator('affine-table-cell'); + expect(await initialCells.count()).toBe(4); + + // Hover over the first cell to make the row options button visible + await initialCells.nth(0).hover(); + await waitNextFrame(page); + + // Click on the row options button (three dots icon) to open the row options menu + const rowOptionButton = table + .locator('[data-testid="drag-row-handle"]') + .first(); + await expect(rowOptionButton).toBeVisible(); + await rowOptionButton.click(); + + // Click on the "Insert Below" option in the menu + const menu = page.locator('affine-menu'); + await expect(menu).toBeVisible(); + await menu.getByText('Insert Below').click(); + await waitNextFrame(page); + + // Verify: Now we should have 6 cells (2x3 table) + const cellsAfterBelowInsert = table.locator('affine-table-cell'); + expect(await cellsAfterBelowInsert.count()).toBe(6); + + // Input text in the newly inserted row + await getCellText(page, 2); // Wait for cell to be ready + const newRowCell1 = cellsAfterBelowInsert.nth(2); + await newRowCell1.hover(); + await waitNextFrame(page); + await newRowCell1.click(); + await page.keyboard.type('New Below 1'); + + await getCellText(page, 3); // Wait for cell to be ready + const newRowCell2 = cellsAfterBelowInsert.nth(3); + await newRowCell2.hover(); + await waitNextFrame(page); + await newRowCell2.click(); + await page.keyboard.type('New Below 2'); + + // Verify values: First row: Cell1, Cell2; Second row: New Below 1, New Below 2; Third row: Cell3, Cell4 + expect(await getCellText(page, 0)).toBe('Cell1'); + expect(await getCellText(page, 1)).toBe('Cell2'); + expect(await getCellText(page, 2)).toBe('New Below 1'); + expect(await getCellText(page, 3)).toBe('New Below 2'); + expect(await getCellText(page, 4)).toBe('Cell3'); + expect(await getCellText(page, 5)).toBe('Cell4'); + + // Hover over the first row again to make the row options button visible + await cellsAfterBelowInsert.nth(0).hover(); + await waitNextFrame(page); + + // Click on the row options button to open the row options menu + await expect(rowOptionButton).toBeVisible(); + await rowOptionButton.click(); + + // Click on the "Insert Above" option in the menu + await expect(menu).toBeVisible(); + await menu.getByText('Insert Above').click(); + await waitNextFrame(page); + + // Verify: Now we should have 8 cells (2x4 table) + const cellsAfterAboveInsert = table.locator('affine-table-cell'); + expect(await cellsAfterAboveInsert.count()).toBe(8); + + // Input text in the newly inserted row + await getCellText(page, 0); // Wait for cell to be ready + const aboveRowCell1 = cellsAfterAboveInsert.nth(0); + await aboveRowCell1.hover(); + await waitNextFrame(page); + await aboveRowCell1.click(); + await page.keyboard.type('New Above 1'); + + await getCellText(page, 1); // Wait for cell to be ready + const aboveRowCell2 = cellsAfterAboveInsert.nth(1); + await aboveRowCell2.hover(); + await waitNextFrame(page); + await aboveRowCell2.click(); + await page.keyboard.type('New Above 2'); + + // Verify the first row contains our new values + expect(await getCellText(page, 0)).toBe('New Above 1'); + expect(await getCellText(page, 1)).toBe('New Above 2'); + expect(await getCellText(page, 2)).toBe('Cell1'); + expect(await getCellText(page, 3)).toBe('Cell2'); + }); +});