Files
AFFiNE-Mirror/blocksuite/tests-legacy/database/database.spec.ts
2024-12-20 11:08:21 +00:00

671 lines
19 KiB
TypeScript

import { expect } from '@playwright/test';
import {
dragBetweenCoords,
enterPlaygroundRoom,
focusDatabaseTitle,
getBoundingBox,
initDatabaseDynamicRowWithData,
initDatabaseRowWithData,
initEmptyDatabaseState,
pressArrowLeft,
pressArrowRight,
pressBackspace,
pressEnter,
pressEscape,
pressShiftEnter,
redoByKeyboard,
selectAllByKeyboard,
setInlineRangeInInlineEditor,
switchReadonly,
type,
undoByClick,
undoByKeyboard,
waitNextFrame,
} from '../utils/actions/index.js';
import {
assertBlockProps,
assertInlineEditorDeltas,
assertRowCount,
} from '../utils/asserts.js';
import { test } from '../utils/playwright.js';
import { getFormatBar } from '../utils/query.js';
import {
assertColumnWidth,
assertDatabaseCellRichTexts,
assertDatabaseSearching,
assertDatabaseTitleText,
blurDatabaseSearch,
clickColumnType,
clickDatabaseOutside,
focusDatabaseHeader,
focusDatabaseSearch,
getDatabaseBodyCell,
getDatabaseHeaderColumn,
getFirstColumnCell,
initDatabaseColumn,
switchColumnType,
} from './actions.js';
test('edit database block title and create new rows', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
const locator = page.locator('affine-database');
await expect(locator).toBeVisible();
const dbTitle = 'Database 1';
await assertBlockProps(page, '2', {
title: dbTitle,
});
await focusDatabaseTitle(page);
await selectAllByKeyboard(page);
await pressBackspace(page);
const expected = 'hello';
await type(page, expected);
await assertBlockProps(page, '2', {
title: 'hello',
});
await undoByClick(page);
await assertBlockProps(page, '2', {
title: 'Database 1',
});
await initDatabaseRowWithData(page, '');
await initDatabaseRowWithData(page, '');
await assertRowCount(page, 2);
await waitNextFrame(page, 100);
await pressEscape(page);
await undoByClick(page);
await undoByClick(page);
await assertRowCount(page, 0);
});
test('edit column title', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page, '1');
// first added column
const { column } = await getDatabaseHeaderColumn(page, 1);
expect(await column.innerText()).toBe('1');
await undoByClick(page);
expect(await column.innerText()).toBe('Column 1');
});
test('should modify the value when the input loses focus', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await switchColumnType(page, 'Number');
await initDatabaseDynamicRowWithData(page, '1', true);
await clickDatabaseOutside(page);
const cell = getFirstColumnCell(page, 'number');
const text = await cell.textContent();
expect(text?.trim()).toBe('1');
});
test('should rich-text column support soft enter', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await switchColumnType(page, 'Text');
await initDatabaseDynamicRowWithData(page, '123', true);
const cell = getFirstColumnCell(page, 'affine-database-rich-text');
await cell.click();
await pressArrowLeft(page);
await pressEnter(page);
await assertDatabaseCellRichTexts(page, { text: '123' });
await cell.click();
await pressArrowRight(page);
await pressArrowLeft(page);
await pressShiftEnter(page);
await pressEnter(page);
await assertDatabaseCellRichTexts(page, { text: '12\n3' });
});
test('should the multi-select mode work correctly', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await initDatabaseDynamicRowWithData(page, '1', true);
await pressEscape(page);
await initDatabaseDynamicRowWithData(page, '2');
await pressEscape(page);
const cell = getFirstColumnCell(page, 'select-selected');
expect(await cell.count()).toBe(2);
expect(await cell.nth(0).innerText()).toBe('1');
expect(await cell.nth(1).innerText()).toBe('2');
});
test('should database search work', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await initDatabaseRowWithData(page, 'text1');
await initDatabaseDynamicRowWithData(page, '123', false);
await pressEscape(page);
await initDatabaseRowWithData(page, 'text2');
await initDatabaseDynamicRowWithData(page, 'a', false);
await pressEscape(page);
await initDatabaseRowWithData(page, 'text3');
await initDatabaseDynamicRowWithData(page, '26', false);
await pressEscape(page);
// search for '2'
await focusDatabaseSearch(page);
await type(page, '2');
const rows = page.locator('.affine-database-block-row');
expect(await rows.count()).toBe(3);
// search for '23'
await type(page, '3');
expect(await rows.count()).toBe(1);
const cell = page.locator('.select-selected');
expect(await cell.innerText()).toBe('123');
// clear search input
const closeIcon = page.locator('.close-icon');
await closeIcon.click();
expect(await rows.count()).toBe(3);
});
test('should database search input displayed correctly', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await focusDatabaseSearch(page);
await blurDatabaseSearch(page);
await assertDatabaseSearching(page, false);
await focusDatabaseSearch(page);
await type(page, '2');
await blurDatabaseSearch(page);
await assertDatabaseSearching(page, true);
await focusDatabaseSearch(page);
await pressBackspace(page);
await blurDatabaseSearch(page);
await assertDatabaseSearching(page, false);
await focusDatabaseSearch(page);
await type(page, '2');
const closeIcon = page.locator('.close-icon');
await closeIcon.click();
await blurDatabaseSearch(page);
await assertDatabaseSearching(page, false);
await focusDatabaseSearch(page);
await type(page, '2');
await pressEscape(page);
await blurDatabaseSearch(page);
await assertDatabaseSearching(page, false);
});
test('should database title and rich-text support undo/redo', async ({
page,
}) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await switchColumnType(page, 'Text');
await initDatabaseDynamicRowWithData(page, '123', true);
await undoByKeyboard(page);
await assertDatabaseCellRichTexts(page, { text: '' });
await pressEscape(page);
await redoByKeyboard(page);
await assertDatabaseCellRichTexts(page, { text: '123' });
await focusDatabaseTitle(page);
await type(page, 'abc');
await assertDatabaseTitleText(page, 'Database 1abc');
await undoByKeyboard(page);
await assertDatabaseTitleText(page, 'Database 1');
await redoByKeyboard(page);
await assertDatabaseTitleText(page, 'Database 1abc');
});
test('should support drag to change column width', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
const headerColumns = page.locator('.affine-database-column');
const titleColumn = headerColumns.nth(0);
const normalColumn = headerColumns.nth(1);
const dragDistance = 100;
const titleColumnWidth = 260;
const normalColumnWidth = 180;
await assertColumnWidth(titleColumn, titleColumnWidth - 1);
const box = await assertColumnWidth(normalColumn, normalColumnWidth - 1);
await dragBetweenCoords(
page,
{ x: box.x, y: box.y },
{ x: box.x + dragDistance, y: box.y },
{
steps: 50,
beforeMouseUp: async () => {
await waitNextFrame(page);
},
}
);
await assertColumnWidth(titleColumn, titleColumnWidth + dragDistance);
await assertColumnWidth(normalColumn, normalColumnWidth - 1);
await undoByClick(page);
await assertColumnWidth(titleColumn, titleColumnWidth - 1);
await assertColumnWidth(normalColumn, normalColumnWidth - 1);
});
test('should display the add column button on the right side of database correctly', async ({
page,
}) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
const normalColumn = page.locator('.affine-database-column').nth(1);
const addColumnBtn = page.locator('.header-add-column-button');
const box = await getBoundingBox(normalColumn);
await dragBetweenCoords(
page,
{ x: box.x, y: box.y },
{ x: box.x + 400, y: box.y },
{
steps: 50,
beforeMouseUp: async () => {
await waitNextFrame(page);
},
}
);
await focusDatabaseHeader(page);
await expect(addColumnBtn).toBeVisible();
});
test('should support drag and drop to move columns', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page, 'column1');
await initDatabaseColumn(page, 'column2');
await initDatabaseColumn(page, 'column3');
const column1 = await focusDatabaseHeader(page, 1);
const moveIcon = column1.locator('.affine-database-column-move');
const moveIconBox = await getBoundingBox(moveIcon);
const x = moveIconBox.x + moveIconBox.width / 2;
const y = moveIconBox.y + moveIconBox.height / 2;
await dragBetweenCoords(
page,
{ x, y },
{ x: x + 100, y },
{
steps: 50,
beforeMouseUp: async () => {
await waitNextFrame(page);
const indicator = page.locator('.vertical-indicator').first();
await expect(indicator).toBeVisible();
const { box } = await getDatabaseHeaderColumn(page, 2);
const indicatorBox = await getBoundingBox(indicator);
expect(box.x + box.width - indicatorBox.x < 10).toBe(true);
},
}
);
const { text } = await getDatabaseHeaderColumn(page, 2);
expect(text).toBe('column1');
});
test('should title column support quick renaming', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await initDatabaseDynamicRowWithData(page, 'a', true);
await pressEscape(page);
await focusDatabaseHeader(page, 1);
const { textElement } = await getDatabaseHeaderColumn(page, 1);
await textElement.click();
await waitNextFrame(page);
await selectAllByKeyboard(page);
await type(page, '123');
await pressEnter(page);
expect(await textElement.innerText()).toBe('123');
await undoByClick(page);
expect(await textElement.innerText()).toBe('Column 1');
await textElement.click();
await waitNextFrame(page);
await selectAllByKeyboard(page);
await type(page, '123');
await pressEnter(page);
expect(await textElement.innerText()).toBe('123');
});
test('should title column support quick changing of column type', async ({
page,
}) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await initDatabaseDynamicRowWithData(page, 'a', true);
await pressEscape(page);
await initDatabaseDynamicRowWithData(page, 'b');
await pressEscape(page);
await focusDatabaseHeader(page, 1);
const { typeIcon } = await getDatabaseHeaderColumn(page, 1);
await typeIcon.click();
await waitNextFrame(page);
await clickColumnType(page, 'Select');
const cell = getFirstColumnCell(page, 'select-selected');
expect(await cell.count()).toBe(1);
});
test('database format-bar in header and text column', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await switchColumnType(page, 'Text');
await initDatabaseDynamicRowWithData(page, 'column', true);
await pressArrowLeft(page);
await pressEnter(page);
await type(page, 'header');
// Title | Column1
// ----------------
// header | column
const formatBar = getFormatBar(page);
await setInlineRangeInInlineEditor(page, { index: 1, length: 4 }, 1);
expect(await formatBar.formatBar.isVisible()).toBe(true);
// Title | Column1
// ----------------
// h|eade|r | column
await assertInlineEditorDeltas(
page,
[
{
insert: 'header',
},
],
1
);
await formatBar.boldBtn.click();
await assertInlineEditorDeltas(
page,
[
{
insert: 'h',
},
{
insert: 'eade',
attributes: {
bold: true,
},
},
{
insert: 'r',
},
],
1
);
await pressEscape(page);
await pressArrowRight(page);
await pressEnter(page);
await setInlineRangeInInlineEditor(page, { index: 2, length: 2 }, 2);
expect(await formatBar.formatBar.isVisible()).toBe(true);
// Title | Column1
// ----------------
// header | co|lu|mn
await assertInlineEditorDeltas(
page,
[
{
insert: 'column',
},
],
2
);
await formatBar.boldBtn.click();
await assertInlineEditorDeltas(
page,
[
{
insert: 'co',
},
{
insert: 'lu',
attributes: {
bold: true,
},
},
{
insert: 'mn',
},
],
2
);
});
test.describe('readonly mode', () => {
test('database title should not be edited in readonly mode', async ({
page,
}) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
const locator = page.locator('affine-database');
await expect(locator).toBeVisible();
const dbTitle = 'Database 1';
await assertBlockProps(page, '2', {
title: dbTitle,
});
await focusDatabaseTitle(page);
await selectAllByKeyboard(page);
await pressBackspace(page);
await type(page, 'hello');
await assertBlockProps(page, '2', {
title: 'hello',
});
await switchReadonly(page);
await type(page, ' world');
await assertBlockProps(page, '2', {
title: 'hello',
});
await pressBackspace(page, 'hello world'.length);
await assertBlockProps(page, '2', {
title: 'hello',
});
});
test('should rich-text not be edited in readonly mode', async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await switchColumnType(page, 'Text');
await initDatabaseDynamicRowWithData(page, '', true);
const cell = getFirstColumnCell(page, 'affine-database-rich-text');
await cell.click();
await type(page, '123');
await assertDatabaseCellRichTexts(page, { text: '123' });
await switchReadonly(page);
await pressBackspace(page);
await type(page, '789');
await assertDatabaseCellRichTexts(page, { text: '123' });
});
test('should hide edit widget after switch to readonly mode', async ({
page,
}) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await switchColumnType(page, 'Text');
await initDatabaseDynamicRowWithData(page, '', true);
const database = page.locator('affine-database');
await expect(database).toBeVisible();
const databaseMenu = database.locator('.database-ops');
await expect(databaseMenu).toBeVisible();
const addViewButton = database.getByTestId('database-add-view-button');
await expect(addViewButton).toBeVisible();
const titleHeader = page.locator('affine-database-header-column').filter({
hasText: 'Title',
});
await titleHeader.hover();
const columnDragBar = titleHeader.locator('.control-r');
await expect(columnDragBar).toBeVisible();
const filter = database.locator('data-view-header-tools-filter');
const search = database.locator('data-view-header-tools-search');
const options = database.locator('data-view-header-tools-view-options');
const headerAddRow = database.locator('data-view-header-tools-add-row');
await database.hover();
await expect(filter).toBeVisible();
await expect(search).toBeVisible();
await expect(options).toBeVisible();
await expect(headerAddRow).toBeVisible();
const row = database.locator('data-view-table-row');
const rowOptions = row.locator('.row-op');
const rowDragBar = row.locator('.data-view-table-view-drag-handler>div');
await row.hover();
await expect(rowOptions).toHaveCount(2);
await expect(rowOptions.nth(0)).toBeVisible();
await expect(rowOptions.nth(1)).toBeVisible();
await expect(rowDragBar).toBeVisible();
const addRow = database.locator('.data-view-table-group-add-row');
await expect(addRow).toBeVisible();
// Readonly Mode
{
await switchReadonly(page);
await expect(databaseMenu).toBeHidden();
await expect(addViewButton).toBeHidden();
await titleHeader.hover();
await expect(columnDragBar).toBeHidden();
await database.hover();
await expect(filter).toBeHidden();
await expect(search).toBeVisible(); // Note the search should not be hidden
await expect(options).toBeHidden();
await expect(headerAddRow).toBeHidden();
await row.hover();
await expect(rowOptions.nth(0)).toBeHidden();
await expect(rowOptions.nth(1)).toBeHidden();
await expect(rowDragBar).toBeHidden();
await expect(addRow).toBeHidden();
}
});
test('should hide focus border after switch to readonly mode', async ({
page,
}) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await switchColumnType(page, 'Text');
await initDatabaseDynamicRowWithData(page, '', true);
const database = page.locator('affine-database');
await expect(database).toBeVisible();
const cell = getFirstColumnCell(page, 'affine-database-rich-text');
await cell.click();
const focusBorder = database.locator(
'data-view-table-selection .database-focus'
);
await expect(focusBorder).toBeVisible();
await switchReadonly(page);
await expect(focusBorder).toBeHidden();
});
test('should hide selection after switch to readonly mode', async ({
page,
}) => {
await enterPlaygroundRoom(page);
await initEmptyDatabaseState(page);
await initDatabaseColumn(page);
await switchColumnType(page, 'Text');
await initDatabaseDynamicRowWithData(page, '', true);
const database = page.locator('affine-database');
await expect(database).toBeVisible();
const startCell = getDatabaseBodyCell(page, {
rowIndex: 0,
columnIndex: 0,
});
const endCell = getDatabaseBodyCell(page, {
rowIndex: 0,
columnIndex: 1,
});
const startBox = await getBoundingBox(startCell);
const endBox = await getBoundingBox(endCell);
const startX = startBox.x + startBox.width / 2;
const startY = startBox.y + startBox.height / 2;
const endX = endBox.x + endBox.width / 2;
const endY = endBox.y + endBox.height / 2;
await dragBetweenCoords(
page,
{ x: startX, y: startY },
{ x: endX, y: endY }
);
const selection = database.locator(
'data-view-table-selection .database-selection'
);
await expect(selection).toBeVisible();
await switchReadonly(page);
await expect(selection).toBeHidden();
});
});