AFFiNE/blocksuite/tests-legacy/e2e/edgeless/edgeless-text.spec.ts
2025-03-10 02:04:01 +00:00

599 lines
17 KiB
TypeScript

import type { EdgelessTextBlockComponent } from '@blocksuite/affine/blocks/edgeless-text';
import { Bound } from '@blocksuite/affine/global/gfx';
import { expect, type Page } from '@playwright/test';
import {
autoFit,
captureHistory,
cutByKeyboard,
dragBetweenIndices,
enterPlaygroundRoom,
getEdgelessSelectedRect,
getPageSnapshot,
initEmptyEdgelessState,
pasteByKeyboard,
pressArrowDown,
pressArrowLeft,
pressArrowRight,
pressArrowUp,
pressBackspace,
pressEnter,
pressEscape,
selectAllByKeyboard,
setEdgelessTool,
switchEditorMode,
toViewCoord,
type,
undoByKeyboard,
waitNextFrame,
} from '../utils/actions/index.js';
import {
assertBlockChildrenIds,
assertBlockFlavour,
assertBlockTextContent,
assertRichTextInlineDeltas,
assertRichTextInlineRange,
} from '../utils/asserts.js';
import { test } from '../utils/playwright.js';
import { getFormatBar } from '../utils/query.js';
async function assertEdgelessTextModelRect(
page: Page,
id: string,
bound: Bound
) {
const realXYWH = await page.evaluate(id => {
const block = window.host.view.getBlock(id) as EdgelessTextBlockComponent;
return block?.model.xywh;
}, id);
const realBound = Bound.deserialize(realXYWH);
expect(realBound.x).toBeCloseTo(bound.x, 0);
expect(realBound.y).toBeCloseTo(bound.y, 0);
expect(realBound.w).toBeCloseTo(bound.w, 0);
expect(realBound.h).toBeCloseTo(bound.h, 0);
}
test.describe('edgeless text block', () => {
test.beforeEach(async ({ page }) => {
await enterPlaygroundRoom(page);
await initEmptyEdgelessState(page);
await switchEditorMode(page);
});
test('add text block in default mode', async ({ page }) => {
await setEdgelessTool(page, 'default');
await page.mouse.dblclick(130, 140, {
delay: 100,
});
await waitNextFrame(page);
// https://github.com/toeverything/blocksuite/pull/8574
await pressBackspace(page);
await type(page, 'aaa');
await pressEnter(page);
await type(page, 'bbb');
await pressEnter(page);
await type(page, 'ccc');
await assertBlockFlavour(page, 4, 'affine:edgeless-text');
await assertBlockFlavour(page, 5, 'affine:paragraph');
await assertBlockFlavour(page, 6, 'affine:paragraph');
await assertBlockFlavour(page, 7, 'affine:paragraph');
await assertBlockChildrenIds(page, '4', ['5', '6', '7']);
await assertBlockTextContent(page, 5, 'aaa');
await assertBlockTextContent(page, 6, 'bbb');
await assertBlockTextContent(page, 7, 'ccc');
await dragBetweenIndices(page, [1, 1], [3, 2]);
await captureHistory(page);
await pressBackspace(page);
await assertBlockChildrenIds(page, '4', ['5']);
await assertBlockTextContent(page, 5, 'ac');
await undoByKeyboard(page);
await assertBlockChildrenIds(page, '4', ['5', '6', '7']);
await assertBlockTextContent(page, 5, 'aaa');
await assertBlockTextContent(page, 6, 'bbb');
await assertBlockTextContent(page, 7, 'ccc');
const { boldBtn } = getFormatBar(page);
await boldBtn.click();
await assertRichTextInlineDeltas(
page,
[
{
insert: 'a',
},
{
insert: 'aa',
attributes: {
bold: true,
},
},
],
1
);
await assertRichTextInlineDeltas(
page,
[
{
insert: 'bbb',
attributes: {
bold: true,
},
},
],
2
);
await assertRichTextInlineDeltas(
page,
[
{
insert: 'cc',
attributes: {
bold: true,
},
},
{
insert: 'c',
},
],
3
);
await pressArrowRight(page);
await assertRichTextInlineRange(page, 3, 2);
await pressArrowUp(page);
await assertRichTextInlineRange(page, 2, 2);
});
test('edgeless text width auto-adjusting', async ({ page }) => {
await setEdgelessTool(page, 'default');
const point = await toViewCoord(page, [0, 0]);
await page.mouse.dblclick(point[0], point[1], {
delay: 100,
});
await waitNextFrame(page);
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 220, 26));
await type(page, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
await waitNextFrame(page, 1000);
// just width changed
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 323, 26));
await type(page, '\nbbb');
// width not changed, height changed
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 323, 50));
await type(page, '\nccccccccccccccccccccccccccccccccccccccccccccccccc');
await waitNextFrame(page, 1000);
// width and height changed
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 395, 74));
// blur, max width set to true
await page.mouse.click(point[0] - 50, point[1], {
delay: 100,
});
await page.mouse.dblclick(point[0], point[1], {
delay: 100,
});
// to end of line
await pressArrowDown(page, 3);
await type(page, 'dddddddddddddddddddd');
await waitNextFrame(page, 1000);
// width not changed, height changed
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 395, 98));
});
test('edgeless text width fixed when drag moving', async ({ page }) => {
// https://github.com/toeverything/blocksuite/pull/7486
await setEdgelessTool(page, 'default');
await page.mouse.dblclick(130, 140, {
delay: 100,
});
await waitNextFrame(page);
await type(page, 'aaaaaa bbbb ');
await pressEscape(page);
await waitNextFrame(page);
await page.mouse.click(130, 140);
await page.mouse.down();
await page.mouse.move(800, 800, {
steps: 15,
});
const rect = await page.evaluate(() => {
const container = document.querySelector(
'.edgeless-text-block-container'
)!;
return container.getBoundingClientRect();
});
const modelXYWH = await page.evaluate(() => {
const block = window.host.view.getBlock(
'4'
) as EdgelessTextBlockComponent;
return block.model.xywh;
});
const bound = Bound.deserialize(modelXYWH);
expect(rect.width).toBeCloseTo(bound.w);
expect(rect.height).toBeCloseTo(bound.h);
});
test('When creating edgeless text, if the input is empty, it will be automatically deleted', async ({
page,
}) => {
await setEdgelessTool(page, 'default');
await page.mouse.dblclick(130, 140, {
delay: 100,
});
await waitNextFrame(page);
let block = page.locator('affine-edgeless-text[data-block-id="4"]');
expect(await block.isVisible()).toBe(true);
await page.mouse.click(0, 0);
expect(await block.isVisible()).toBe(false);
block = page.locator('affine-edgeless-text[data-block-id="6"]');
expect(await block.isVisible()).not.toBe(true);
await page.mouse.dblclick(130, 140, {
delay: 100,
});
expect(await block.isVisible()).toBe(true);
await type(page, '\na');
expect(await block.isVisible()).toBe(true);
await page.mouse.click(0, 0);
expect(await block.isVisible()).not.toBe(false);
});
test('edgeless text should maintain selection when deleting across multiple lines', async ({
page,
}) => {
// https://github.com/toeverything/blocksuite/pull/7443
await setEdgelessTool(page, 'default');
await page.mouse.dblclick(130, 140, {
delay: 100,
});
await waitNextFrame(page);
await type(page, 'aaaa\nbbbb');
await assertBlockTextContent(page, 5, 'aaaa');
await assertBlockTextContent(page, 6, 'bbbb');
await pressArrowLeft(page);
await page.keyboard.down('Shift');
await pressArrowLeft(page, 3);
await pressArrowUp(page);
await pressArrowRight(page);
await page.keyboard.up('Shift');
await pressBackspace(page);
await assertBlockTextContent(page, 5, 'ab');
await type(page, 'sss\n');
await assertBlockTextContent(page, 5, 'asss');
await assertBlockTextContent(page, 7, 'b');
});
test('edgeless text should not blur after pressing backspace', async ({
page,
}) => {
// https://github.com/toeverything/blocksuite/pull/7555
await setEdgelessTool(page, 'default');
await page.mouse.dblclick(130, 140, {
delay: 100,
});
await waitNextFrame(page);
await type(page, 'a');
await assertBlockTextContent(page, 5, 'a');
await pressBackspace(page);
await type(page, 'b');
await assertBlockTextContent(page, 5, 'b');
});
// FIXME(@flrande): This test fails randomly on CI
test.fixme('edgeless text max width', async ({ page }) => {
await setEdgelessTool(page, 'default');
const point = await toViewCoord(page, [0, 0]);
await page.mouse.dblclick(point[0], point[1], {
delay: 100,
});
await waitNextFrame(page);
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 50, 56));
await type(page, 'aaaaaa');
await waitNextFrame(page);
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 71, 56));
await type(page, 'bbb');
await waitNextFrame(page, 200);
// height not changed
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 98, 56));
// blur
await page.mouse.click(0, 0);
// select text element
await page.mouse.click(point[0] + 10, point[1] + 10);
let selectedRect = await getEdgelessSelectedRect(page);
// move cursor to the right edge and drag it to resize the width of text
// from left to right
await page.mouse.move(
selectedRect.x + selectedRect.width,
selectedRect.y + selectedRect.height / 2
);
await page.mouse.down();
await page.mouse.move(
selectedRect.x + selectedRect.width + 30,
selectedRect.y + selectedRect.height / 2,
{
steps: 10,
}
);
await page.mouse.up();
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 128, 56));
selectedRect = await getEdgelessSelectedRect(page);
let textRect = await page
.locator('affine-edgeless-text[data-block-id="4"]')
.boundingBox();
expect(selectedRect).not.toBeNull();
expect(selectedRect.width).toBeCloseTo(textRect!.width);
expect(selectedRect.height).toBeCloseTo(textRect!.height);
expect(selectedRect.x).toBeCloseTo(textRect!.x);
expect(selectedRect.y).toBeCloseTo(textRect!.y);
// from right to left
await page.mouse.move(
selectedRect.x + selectedRect.width,
selectedRect.y + selectedRect.height / 2
);
await page.mouse.down();
await page.mouse.move(
selectedRect.x + selectedRect.width - 45,
selectedRect.y + selectedRect.height / 2,
{
steps: 10,
}
);
await page.mouse.up();
// height changed
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 83, 80));
selectedRect = await getEdgelessSelectedRect(page);
textRect = await page
.locator('affine-edgeless-text[data-block-id="4"]')
.boundingBox();
expect(selectedRect).not.toBeNull();
expect(selectedRect.width).toBeCloseTo(textRect!.width);
expect(selectedRect.height).toBeCloseTo(textRect!.height);
expect(selectedRect.x).toBeCloseTo(textRect!.x);
expect(selectedRect.y).toBeCloseTo(textRect!.y);
});
test('min width limit for embed block', async ({ page }, testInfo) => {
await setEdgelessTool(page, 'default');
const point = await toViewCoord(page, [0, 0]);
await page.mouse.dblclick(point[0], point[1], {
delay: 100,
});
await waitNextFrame(page, 2000);
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_init.json`
);
await type(page, '@');
await pressEnter(page);
await waitNextFrame(page, 200);
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_add_linked_doc.json`
);
await page.locator('affine-reference').hover();
await page.getByLabel('Switch view').click();
await page.getByTestId('link-to-card').click();
await autoFit(page);
await waitNextFrame(page);
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_link_to_card.json`
);
// blur
await page.mouse.click(0, 0);
// select text element
await page.mouse.click(point[0] + 10, point[1] + 10);
await waitNextFrame(page, 200);
const selectedRect0 = await getEdgelessSelectedRect(page);
// from right to left
await page.mouse.move(
selectedRect0.x + selectedRect0.width,
selectedRect0.y + selectedRect0.height / 2
);
await page.mouse.down();
await page.mouse.move(
selectedRect0.x,
selectedRect0.y + selectedRect0.height / 2,
{
steps: 10,
}
);
await page.mouse.up();
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_link_to_card_min_width.json`
);
const selectedRect1 = await getEdgelessSelectedRect(page);
// from left to right
await page.mouse.move(
selectedRect1.x + selectedRect1.width,
selectedRect1.y + selectedRect1.height / 2
);
await page.mouse.down();
await page.mouse.move(
selectedRect0.x + selectedRect0.width + 45,
selectedRect1.y + selectedRect1.height / 2,
{
steps: 10,
}
);
await page.mouse.up();
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_drag.json`
);
});
test('cut edgeless text', async ({ page }) => {
await setEdgelessTool(page, 'default');
await page.mouse.dblclick(130, 140, {
delay: 100,
});
await waitNextFrame(page);
await type(page, 'aaaa\nbbbb\ncccc');
const edgelessText = page.locator('affine-edgeless-text');
const paragraph = page.locator('affine-edgeless-text affine-paragraph');
expect(await edgelessText.count()).toBe(1);
expect(await paragraph.count()).toBe(3);
await page.mouse.click(50, 50, {
delay: 100,
});
await waitNextFrame(page);
await page.mouse.click(130, 140, {
delay: 100,
});
await cutByKeyboard(page);
expect(await edgelessText.count()).toBe(0);
expect(await paragraph.count()).toBe(0);
await pasteByKeyboard(page);
expect(await edgelessText.count()).toBe(1);
expect(await paragraph.count()).toBe(3);
});
test('latex in edgeless text', async ({ page }) => {
await setEdgelessTool(page, 'default');
await page.mouse.dblclick(130, 140, {
delay: 100,
});
await waitNextFrame(page);
await type(page, '$$bbb$$ ');
await assertRichTextInlineDeltas(
page,
[
{
insert: ' ',
attributes: {
latex: 'bbb',
},
},
],
1
);
await page.locator('affine-latex-node').click();
await waitNextFrame(page);
await type(page, 'ccc');
const menu = page.locator('latex-editor-menu');
const confirm = menu.locator('.latex-editor-confirm');
await confirm.click();
await assertRichTextInlineDeltas(
page,
[
{
insert: ' ',
attributes: {
latex: 'bbbccc',
},
},
],
1
);
await page.locator('affine-latex-node').click();
await page.locator('.latex-editor-hint').click();
await type(page, 'sss');
await assertRichTextInlineDeltas(
page,
[
{
insert: ' ',
attributes: {
latex: 'bbbccc',
},
},
],
1
);
await page.locator('latex-editor-unit').click();
await selectAllByKeyboard(page);
await type(page, 'sss');
await confirm.click();
await assertRichTextInlineDeltas(
page,
[
{
insert: ' ',
attributes: {
latex: 'sss',
},
},
],
1
);
});
});
test('press backspace at the start of first line when edgeless text exist', async ({
page,
}, testInfo) => {
await enterPlaygroundRoom(page);
await page.evaluate(() => {
const { doc } = window;
const rootId = doc.addBlock('affine:page', {
title: new window.$blocksuite.store.Text(),
});
doc.addBlock('affine:surface', {}, rootId);
doc.addBlock('affine:note', {}, rootId);
// do not add paragraph block
doc.resetHistory();
});
await switchEditorMode(page);
await setEdgelessTool(page, 'default');
const point = await toViewCoord(page, [0, 0]);
await page.mouse.dblclick(point[0], point[1], {
delay: 100,
});
await waitNextFrame(page, 2000);
await type(page, 'aaa');
await waitNextFrame(page);
await switchEditorMode(page);
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_note_empty.json`
);
await page.locator('.affine-page-root-block-container').click();
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_note_not_empty.json`
);
await type(page, 'bbb');
await pressArrowLeft(page, 3);
await pressBackspace(page);
await waitNextFrame(page);
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_finial.json`
);
});