599 lines
17 KiB
TypeScript
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`
|
|
);
|
|
});
|